From ac86d2718a8cfc6991bd6aa37b6c307ba11051dc Mon Sep 17 00:00:00 2001 From: Elmehdi Aitbrahim Date: Mon, 24 Aug 2026 08:41:49 -0400 Subject: [PATCH] feat(web): the client shell, in the four modules the spec names (#536) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first JavaScript keel ships. Seven more views inherit this shape (#537), so what matters here is the shape: plain ES modules served exactly as authored, no framework, no bundler, no transpile, no minification, no source maps, and nothing under `js/external/` — a directory this commit creates, because it did not exist before, along with the test that keeps it empty. WHERE IT MOUNTS, AND WHY NOT `/` Under `/static/`, because `/` and the seven paths beside it are still rendered in Python and are deleted at step 7 of the spec's build order (#540), not here. Serving this at `/` today would mean deleting a working page to install a shell with one view in it. The prefix is spelled in three files and a test pins that the three agree, so that move is a mechanical edit rather than a hunt. The router is the History API and nothing else. Hash routing was the alternative — it needs no server cooperation at all — and was rejected for putting a `#` in every URL an operator copies, when the cooperation it avoids is `staticfiles.resolve_client_route`: fifteen lines that serve the shell for a CLOSED list of seven names. Closed, not a wildcard, and the difference is not stylistic: with a wildcard a missing `.js` stops being a 404 and becomes a 200 of HTML that the browser then refuses to execute under `nosniff`, reported as a MIME-type error naming the module rather than as "that file is not there." PROVING `render` DERIVES NOTHING, RATHER THAN PROMISING IT The spec asks for `render` as its own file so a reviewer can confirm the absence of arithmetic by reading one file. Reading is the point; a gate is what stops the property decaying between readings. `_code_only` strips comments and string literals with a four-state lexer and the scan rejects `+ - * / %`, `++`, `--`, `Number`, `parseInt`, `Math`, `toFixed`, `NumberFormat` and friends. The lexer can be that small only because `render.js` contains no template literals and no regex literals — the two things that make lexing JavaScript genuinely hard. Both rules are written down in the file so nobody removes them as pointless style, and the second one turns out to buy more than it cost: with no template literals and no `+`, there is no way to build an HTML string, so the client has no `innerHTML` anywhere and therefore no escaping to get right and no injection sink to audit. `render.py` needs `esc()` and ninety disciplined call sites for the same property. THE ARITHMETIC SCAN ALONE WAS NOT ENOUGH, AND MUTATION IS WHAT SHOWED IT Appending `return v.value < 0 ? "bad" : "good"` to `render.js` passed the arithmetic test cleanly. No operator, no numeric identifier — and precisely the forbidden thing: a judgement re-derived in the client from a sign, which is what `payload.py`'s closed `state` vocabulary and #532's glyphs exist to make unnecessary. Two more rules close it, and the test that states them carries the mutation that motivated them: `render.js` never reads `Field.value`, and contains no relational comparison. Six other mutations (client-side money formatting, a template literal, `innerHTML`, a vendored module, a nudged palette value, a route added to one table only) were each confirmed to fail the suite. PARITY IS ON THE INFORMATION, NOT ON FIVE WRONG OUTPUTS Measured against the running code rather than read off the source: today's `render_status` reaches for five attributes that do not exist on the report dataclasses, through `getattr(..., default)` calls that swallow the mismatch. `/` currently prints autonomy "off" for a deployment placing orders unattended, prints rail 17 "fresh" in green for an EXPIRED withdrawal attestation, and renders every entry price, every live-rule name and both subscription columns blank. This view shows the values the API sends, which are the correct ones, and `statusView`'s docstring records each divergence. Reproducing them would mean writing code whose only purpose is to be wrong in the same way. ACCESSIBILITY, AND ONE PIECE OF RESTRAINT One `aria-live` region, on the engine banner, `aria-atomic` so it is heard as a sentence. Deliberately not on the data: this page re-reads itself every 15 seconds, and a live region around the tables would re-announce the whole dashboard twice a minute, which is not an accessibility feature. What changes MEANING underneath a reader is whether keel is running and when it was last read. Beside that: a skip link, `aria-current="page"` serving as both the assistive signal and the CSS hook so they cannot drift, focusable named scroll regions for tables, `scope="col"` headers, focus moved to `
` on a route change, one `:focus-visible` rule for everything, and a `