Skip to main content

max / goingson

Name the escaping floor in escape.js, and ratchet its consumers Zero `esc()` was never reachable by porting screens, and the count was the number people read progress off, so a residue read as remaining work. escape.js now says what stays and why: host-bound JS (OAuth, credentials, invoke), shared chrome with no screen to belong to, and the compose window, which is the one group with a planned exit (3fb2526a, blocked on the upload vocabulary). Measured 2026-08-18 with the method stated beside it, since a bare number is what made this ambiguous: 664 escaper calls across 52 files, of which 167 across 20 are the floor and 497 across 32 fall as screens flip. The older bare-`esc(` figure is 329 across 49, unchanged since 2026-08-17. The enforcement gate keeps scanning every file -- a file still shipping is still a risk -- and gains a consumer ratchet, because a gate that has become vacuous reads exactly like a gate that is passing. The file count may fall to the floor and never rise: a new consumer is new hand-built markup.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-18 21:29 UTC
Signed with PGP, not checked
Commit: 7ee0a1cce901457ccbbfc9f5001b67033a3fb05a
Parent: c4ebabb
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, '&quot;')` 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.