max / goingson
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
2 files changed,
+65 insertions,
-0 deletions
| @@ -7,6 +7,41 @@ | |||
| 7 | 7 | * implementation of each escaper and the enforcement gate in js/tests/run.js | |
| 8 | 8 | * covers every caller. Load this before utils.js (main app) and before | |
| 9 | 9 | * compose-form.js (compose window). | |
| 10 | + | * | |
| 11 | + | * # This file is a floor, not a backlog | |
| 12 | + | * | |
| 13 | + | * The quasi port empties the escapers out of screen JS as each screen flips, and | |
| 14 | + | * it does not reach zero. Three groups keep an escaper whatever the port does, | |
| 15 | + | * and they are named here so the next reader does not read a residual count as | |
| 16 | + | * remaining work (settled 2026-08-17, goingson task 4bfa93cc): | |
| 17 | + | * | |
| 18 | + | * - HOST-BOUND, staying JS by a separate decision: settings-sync, | |
| 19 | + | * settings-sharing, email-accounts (OAuth, credentials, `invoke(`) and | |
| 20 | + | * search.js (settled 2026-08-13, quasicoherent d52884b0). | |
| 21 | + | * - SHARED CHROME AND BEHAVIOUR, with no screen to belong to, so no port | |
| 22 | + | * reaches it: components, components-modal, form-modal, autocomplete, | |
| 23 | + | * snooze, attachments, bulk-actions, import, import-external, export, | |
| 24 | + | * groups, shared-updater, whats-new. | |
| 25 | + | * - THE COMPOSE WINDOW: compose-form, compose-page, address-highlight. This | |
| 26 | + | * is the one group with a planned exit -- goingson `3fb2526a` makes compose | |
| 27 | + | * a widget of primitives, blocked on the upload vocabulary (`f7261a5a`) -- | |
| 28 | + | * so it is a floor that is expected to move, unlike the two above. | |
| 29 | + | * | |
| 30 | + | * MEASURED 2026-08-18, and state the method with the number or it is ambiguous. | |
| 31 | + | * Counting every escaper call (the per-file `esc` / `escAttr` aliases plus | |
| 32 | + | * direct `escapeHtml` / `escapeAttrValue` / `escapeHandlerArg` / `safeUrl`), | |
| 33 | + | * excluding this file, utils.js and js/tests: | |
| 34 | + | * | |
| 35 | + | * total 664 calls across 52 files | |
| 36 | + | * floor 167 across 20 (host-bound 75/4, chrome 75/13, compose 17/3) | |
| 37 | + | * screen-bound 497 across 32 -- these fall as screens flip | |
| 38 | + | * | |
| 39 | + | * Counting the bare `esc(` alias alone, which is the older number and the one | |
| 40 | + | * to compare against 2026-08-17's: 329 across 49 files, unchanged since. | |
| 41 | + | * | |
| 42 | + | * Recount rather than trusting either figure: a described screen keeps its JS | |
| 43 | + | * counterpart while the `quasi` feature is off, so both exist and both count, | |
| 44 | + | * and nothing falls until a flip. | |
| 10 | 45 | */ | |
| 11 | 46 | (function() { | |
| 12 | 47 | 'use strict'; |
| @@ -658,6 +658,36 @@ | |||
| 658 | 658 | `attribute escaper inside a quoted JS-string handler arg (use escapeHandlerArg):\n ${offenders.join('\n ')}`); | |
| 659 | 659 | }); | |
| 660 | 660 | ||
| 661 | + | // Consumer ratchet: the escapers may only be used by fewer files over time. | |
| 662 | + | // | |
| 663 | + | // The scans above deliberately cover EVERY file, and that does not change: | |
| 664 | + | // a file still shipping is still a risk whether or not its screen has been | |
| 665 | + | // described. What the scans cannot say is whether they still have anything | |
| 666 | + | // to guard. As screens flip, their JS goes with them and the gate quietly | |
| 667 | + | // scans less; without a number attached, a gate that has become vacuous | |
| 668 | + | // reads exactly like a gate that is passing. | |
| 669 | + | // | |
| 670 | + | // So the file count is the ratchet. It may fall to the floor named in | |
| 671 | + | // escape.js's header (host-bound JS, shared chrome, the compose window) and | |
| 672 | + | // no further, and it may never rise: a new consumer means new hand-built | |
| 673 | + | // markup, which is what the port exists to stop. | |
| 674 | + | const ESCAPER_CONSUMER_BUDGET = 52; | |
| 675 | + | const ESCAPER_CALL = /\b(esc|escAttr|escArg|escAttrVal|escapeHtml|escapeAttrValue|escapeHandlerArg|safeUrl)\s*\(/; | |
| 676 | + | test(`escaper consumers stay at or below ${ESCAPER_CONSUMER_BUDGET} files`, () => { | |
| 677 | + | const consumers = files.filter((f) => { | |
| 678 | + | if (f === 'escape.js' || f === 'utils.js') return false; | |
| 679 | + | return fs.readFileSync(path.join(srcDir, f), 'utf8') | |
| 680 | + | .split('\n').some((line) => ESCAPER_CALL.test(line)); | |
| 681 | + | }); | |
| 682 | + | assert(consumers.length <= ESCAPER_CONSUMER_BUDGET, | |
| 683 | + | `escaper consumers rose to ${consumers.length} (budget ${ESCAPER_CONSUMER_BUDGET}). ` + | |
| 684 | + | `A new consumer is new hand-built markup; describe the screen instead.`); | |
| 685 | + | if (consumers.length < ESCAPER_CONSUMER_BUDGET) { | |
| 686 | + | console.log(` escaper consumers: ${consumers.length} (budget ${ESCAPER_CONSUMER_BUDGET}). ` + | |
| 687 | + | `Lower ESCAPER_CONSUMER_BUDGET in js/tests/run.js to lock the gain in.`); | |
| 688 | + | } | |
| 689 | + | }); | |
| 690 | + | ||
| 661 | 691 | // The hand-rolled attribute escaper `.replace(/"/g, '"')` is the exact | |
| 662 | 692 | // shape the compose window used to duplicate outside this gate's reach. | |
| 663 | 693 | // escape.js is the single source of truth; no other file may re-implement it. |