feat(web): the title bar is painted, a click survives an editor, and the twin door is gone

improvise-5jna, improvise-0x9u, improvise-seik — three browser-page defects with
one shape: a thing that looks done from every check except running it.

improvise-5jna. `doc_name`/`doc_dirty`/`doc_stored_as` were projected by z754 and
painted by nothing. `render_surface.test.js` could not see it — it is a SUBSTRING
SEARCH over the page's source, so a field named by a function nothing calls reads
as painted, which is also how `model.minibuffer` shipped dead. So the guard is a
harness assertion that loads the page and reads `#doc-name`, proved by writing it
first (`element(s) not found`). The joining is `shell.js::documentTitle`, the
TUI's own `name (stored_as) [+]` minus the chrome; the `[+]` is a node test, not a
harness one, because this deployment persists on every accepted batch and clears
the flag in the same round trip, so the marker is transient. UNPAINTED is now
empty.

improvise-0x9u. From an EDITING state a cell click did nothing at all: the
mousedown moved focus, the input's blur ran `close`, `close` repainted,
`replaceChildren` destroyed the `<td>` the mousedown landed on, and no `click`
was ever dispatched. The old spec pressed Escape between the two clicks, so it
only ever clicked from a non-editing page — this repo's characteristic defect.

Worth recording: moving to `mousedown` is NECESSARY AND NOT SUFFICIENT, and the
first attempt failed the same assertion with the same symptom. Removing a focused
`<input>` fires `blur` SYNCHRONOUSLY, so `close` rebuilt the table from inside
itself and detached the `<td>` again. `close` therefore no longer repaints, the
`blur` arm checks `closed` first, and the handler `preventDefault()`s so no focus
change happens at all — otherwise the default action focuses `#grid` after
`input.focus()` and blurs the new editor. `close` restores the DRAWN text, since
nothing repaints behind it any more.

improvise-seik. `Client::viewport` was a wasm-bindgen door with no caller, kept
for a reason `web/app.js` falsified in the same wave. It is one letter from
`set_viewport` and fails SILENTLY — identical frame, forgotten window, every
cursor key an upstream black hole. Deleted, along with the exported `init()` free
function that duplicated `new Client()`. The guard is not the deletion:
`wasm_surface.test.js` now reads the generated `.d.ts` and requires every
declared method to be one `app.js` calls or to be named in `UNCALLED` with its
reason. Proved by injecting a spare `static viewport(...)` into the built `.d.ts`
and watching it fail by name.

Tests: 1405 Rust (unchanged), 27 node + 4 bundle (was 26 + 3), 24 chromium
harness (was 23).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
This commit is contained in:
Edward Langley
2026-09-20 20:37:44 -07:00
parent 8a559b2686
commit 628f43121d
12 changed files with 232 additions and 46 deletions
+23
View File
@@ -86,6 +86,29 @@ test("the page still reaches the client through a handful of calls", () => {
);
});
/// Methods on `Client` the page does not call, each with the reason it is
/// still exported. A door with no caller and no entry here is improvise-seik:
/// the dangerous kind, because calling the wrong one of a pair is silent.
const UNCALLED = {
free: "wasm-bindgen's own destructor, not ours to remove",
initSync: "wasm-bindgen's own module loader, beside the default export",
revision: "on_frame returns it; a page that stores it can skip a repaint",
};
test("the bundle exports no client door the page has no reason to hold", () => {
const declared = declarations("pkg/improvise_wasm_client.d.ts");
const called = new Set(callsOn("app.js", "client").map((c) => c.name));
// `constructor` is `new Client()`, which `callsOn` cannot see as a call.
const spare = [...declared.keys()].filter(
(name) => name !== "constructor" && !called.has(name),
);
assert.deepEqual(
spare.sort(),
Object.keys(UNCALLED).sort(),
"an exported method nothing calls — delete it, or add it to UNCALLED with the reason",
);
});
test("every worker call web/worker.js makes is declared, with that arity", () => {
const declared = declarations("worker-pkg/improvise_worker_server.d.ts");
const calls = callsOn("worker.js", "server");