90ccfb01545d79915282d1adde7a071c6e8d18d4
7 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
56b3058136 |
docs(browser): the arguments for the page bar, the totals and the delegation
Self-review findings, applied.
The word `Total` is now spelled twice — `src/ui/grid.rs` and
`web/shell.js::gridPlan` — which fails design-principles §5's own test: would a
third front end write this again? Yes. It is kept local on the `indicator()`
precedent (the collapse glyph is spelled in both ends while the FACT is shared)
rather than built into a protocol string table for one word, and it is now
NAMED as debt at both the call site and in `context/code/clients.md` instead of
being left for the next reader to find.
`frameCostOf` now counts the totals strip, and that is a change to a rule
`context/code/clients.md` already states — so the section says why the strip is
reserved where the inline group rows deliberately are not: it is present
exactly once, never interleaved with data, so counting it is neither the
feedback loop nor the ratchet that argument rules out. The page bar needs no
rule at all, being outside the box `extentOf` measures.
The evidence file claimed three injections into `dispatch.test.js` that had not
been run. They have been now, and the claim is rewritten to match what actually
failed rather than what was planned — the third was `navigable: true`
unconditionally, not the `position !== ""` reading the text described.
`doctrine(an_injected_fault_must_be_the_fault_you_named)` applies to the
description as much as to the fault.
Injections run and read, all against the served copy or the source:
gridPlan takes the table row index -> both round-trip tests fail
whole() drops its empty-string guard -> both "means nothing" tests fail
navigable: true -> the Filter test alone fails
grid bound to `click` -> 4 Playwright tests fail, including
the pre-existing improvise-0x9u guard
paintPages never called -> the page bar assertion fails
the tfoot block never runs -> the totals assertion fails
page_coords deleted from render_model -> the Rust page-bar test fails
totals_strip returns Vec::new() -> the Rust totals test fails, and the
new cache-parity guard reports it
PageAxis::cycle replaced by items.is_some() -> the cycle index test fails
Two residuals named rather than discovered later: the `Total` spelling above,
and the page bar being display-only — clicking an entry does nothing, because
composing a page change from a click needs a `click_page` on the wasm surface
that does not exist.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
|
||
|
|
a93d668e5e |
feat(browser): paint the page bar and the totals strip, and delegate the pointer
improvise-p7lh: `GridCache::page_coords` and `GridCache::totals` were projected to every session and folded correctly by every client, and NO front end read either. The shipped front page — `web/demo.improv` is `Quarter: page, Q1` — drew a grid filtered to Q1 with nothing saying so, and `[`/`]` resolve locally off that same `page_coords`, so a user could page through quarters watching every number change with no way to tell which one they were looking at. `RenderModel` gains `page_coords` and `totals`, fed straight off `state.grid`. The Page/Filter split is asked of `PageAxis::cycle` rather than re-derived from `items.is_some()`, which disagrees with it on an empty item list (gotcha 27). The totals are RELAYED, never summed in the client: the sum runs over every row including ones a viewport scrolled past (gotcha 44). `web/tests/render_surface.test.js` gains the direction that would have caught this when improvise-n10 added the fields. The existing guard is closed under what `RenderModel` chose to expose, so a cache field that never reaches it is invisible to it; the new one asks that every `pub` field of every widget cache is read somewhere the browser runs, or is named as debt with a reason. `grid.col_widths` is the one entry, pointing at improvise-pcje. improvise-b8ot: the five clickable surfaces moved from one listener per node, reallocated inside the render loops on every repaint, to one handler per stable delegation root — the user's ruling. The operands are carried on the node as data attributes and read back by a pure `cellAction`/`indexAction` in `web/shell.js`, so the handler is a LOOKUP rather than a second derivation of which data row was clicked: `gridPlan` counts data rows because a group header is not one, and walking the table back up would disagree the moment one appears. The editor's own keydown/blur stay direct — one transient node, one at a time, and `blur` does not bubble. All four documented behaviours a naive delegation loses are preserved and each is now asserted in `harness/tests/contract.spec.js`: mousedown rather than click (asserted by dispatching mousedown ALONE), `preventDefault` and only over a cell, the conditionally skipped repaint (asserted by the editor's survival), and closing a live editor before composing the click. Gates: 1598 passed / 0 failed / 7 ignored over 37 binaries (baseline 1595); node --test 47 over 6 files (baseline 36 over 5); Playwright 47 passed / 4 skipped (baseline 43 / 4); clippy, fmt and --check-evidence clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
12714c7527 |
feat(browser): a boot failure is a state with a reason, not silence
improvise-1wft and the five beads it blocked were one failure: nothing
carried a boot failure's REASON from the worker to the page. The channel
is one rule -- a protocol frame crosses the port as a STRING, a shell
notice as an OBJECT -- so it needs no protocol change and the
discriminator is total.
A boot failure is a STATE, not a rejected promise. A document another
context holds the exclusive handle to now OPENS read-only: measured first
(harness probe, Chromium + Firefox), getFile() reads the bytes while the
handle is held, with the second-handle refusal asserted so the probe
cannot pass vacuously.
- improvise-1wft the channel: Notice/NoticeKind, boot_notice, take_notice
- improvise-i0v9 read-only boot over vfs::ReadOnly + OpfsFs::read_unattached
- improvise-86nw start() rejecting for any reason now posts BootFailed
- improvise-9cma boot::document_name -- and a/b/c.improv was ACCEPTED as
c.improv, not rejected, which is worse than the report
- improvise-iobc vfs::Origin: a seed error is a notice only when the
document really had to be created without it
- improvise-vep8 the watchdog arms BEFORE transport.ready; its message
stopped claiming the connection was accepted
- improvise-7gip DedicatedWorkerGlobalScope (needs a web-sys feature)
- improvise-zre1 a persist that fails raises a notice; the trigger is the
dirty flag surviving, observed rather than predicted
- improvise-7gf8 the render breaker trips once instead of retrying forever
- improvise-ociy non-OPEN sends are loud; a viewport announcement is not
- improvise-y01k the three ws-server drops are logged
- improvise-r4nf serde failures throw instead of reading as "nothing to send"
Every notice routes through shell.fail, so gotcha 48's disarm half is not
re-broken. Two comments in shell.spec.js stated the opposite of the new
arming order and were corrected rather than left.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
|
||
|
|
22bb621dd8 |
test(harness): close the stale-assemble hole, and decide the dropped assertion
improvise-669b. PR #65 single-homed HANDSHAKE_TIMEOUT_MS in web/shell.js, but the home is single in the TREE: the spec imports web/shell.js and the page loads harness/serve/app/shell.js. createShell now exposes timeoutMs() beside timesArmed(), and outlastTheWatchdog requires the running page's value to equal the one it imported. Injected with the ticket's recipe (8000 -> 100 in source, no rebuild): 5 passed / 1 failed where it was previously 6/6 green. Also in scope, the same file's stale line: HANDSHAKE_TIMEOUT_MS restates nothing and one test outlasts it, not two. improvise-qf57. The formula-row click's `#grid input` toHaveCount(0) stays dropped, and now deliberately. The reviewer's argument was that the exact `#mode` assertion subsumes it; measuring gives a better one. app.js creates a grid <input> in exactly one place, the grid cell's own mousedown, so no panel handler can reach it — and an invented formula-row activation opens the MINIBUFFER, not a grid editor. Injecting ChangeMode(formula_edit()) into click_formula_row: the toHaveCount(0) probe PASSES and the run fails on the #mode assertion below it. Restoring it would add an assertion that cannot fail, which context/test-evidence/clients.md already records as the reason a sibling assert! was removed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
628f43121d |
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 |
||
|
|
89f67afd4c |
fix(harness): make a vacuous watchdog denial fail, and single-home the window
PR #65's review found this PR had planted a fresh instance of the defect it exists to repair. Both blocking findings fixed, each demonstrated. FINDING 1. "a peer that cannot be reached is named, and the watchdog stays out of it" could not see the property its comment claimed. `shell.arm` runs only after `await transport.ready`, which never resolves on a socket that never opens — so the watchdog is never armed there and denying "sent no document" denies an unreachable event. Reverting PR #21's routing left that clause passing. The aggravating half: `context/test-evidence/clients.md` already said so, and this PR EDITED that paragraph — inserting "runFor(10s) advances the watchdog" immediately above the sentence explaining it is never armed. Not text the PR left alone; text the PR walked past mid-sentence. The clause is gone and the test keeps the naming property it really guards: a `close` on a socket that never opened must not read as "disconnected". And the shape cannot return — `outlastTheWatchdog` now asks the page `timesArmed() > 0` before jumping, so a watchdog denial re-added to that path FAILS BY NAME. It asks *ever armed* rather than *armed now* on purpose: by the time the disconnect test jumps, a correct page has already disarmed, which is the property, so the obvious spelling would have denied the passing case. Building the guard is what surfaced that distinction; reading had not. FINDING 2. The spec kept its own `HANDSHAKE_TIMEOUT_MS` beside a comment saying a silent disagreement "would pass for the wrong reason". It did: the spec at 1000 against the page's 8000 made the suite green over the reverted routing, because the jump landed inside the window. The constant is now exported from `web/shell.js` and imported by both, so the disagreement is unconstructible. Shrinking it in its one home still catches the routing revert — that is the demonstration the guard did not go quiet. Filed, not fixed: improvise-78v6 (#61 also deleted the help-overlay spec and nothing replaced it — a PR about a dropped guard that did not diff the dropping PR's test names) and improvise-5ray (--check-evidence collects only `### ` and resolves only Rust fns, so every browser entry's `####` is invisible; appending one that names nothing exits 0, which is how the dangling heading cleared the gate for a release). Verified on the merged tree: node 26/26, bundle 3/3, Playwright 17 passed + 3 skipped, cargo test --workspace 1378 passed, comment-runs clean, --check-evidence 501 resolving, all cited ids resolve, no .beads diff. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
36ade35240 |
test(web): a Node suite between the boundaries, Playwright only on them
E2E and integration tests are rare and pin boundary contracts; unit tests assume those contracts and test between them (user, 2026-09-20). The browser half has four boundaries — RenderModel→DOM, JS→wasm, page→peer, worker→OPFS — and everything between them needed no browser at all. `web/shell.js` and `web/serial.js` are the seams: pure modules that RETURN A DESCRIPTION rather than take a DOM double. `gridPlan(model)` answers every question `gridTable` used to answer while creating elements — the data-row index across group headers, what a group header spans, a null group label against an empty one, an empty edit buffer against no editor — and `app.js` is now a loop that builds elements from it. Painting stays untested by Node; what to paint stopped being untestable. 26 tests in `node --test web/tests` (0.12s, no Rust toolchain, no wasm bundle, no npm install), plus three in `web/tests/bundle/` that read the generated .d.ts and so run in the browser job. Playwright goes from 29 tests/36.9s to 16/15.5s on one machine: `widgets.spec.js` and `pointer.spec.js` are gone, replaced by `contract.spec.js`, which asserts that EVERY field the render model projects reaches the screen. The four widgets those specs were written for were one failure at one boundary, and one assertion catches the fifth. It caught it: `doc_name`/`doc_dirty`/`doc_stored_as` reach no element (improvise-5jna, already open). They are named in `UNPAINTED` with the ticket, so the list is the debt written down rather than an exemption. Thirteen faults injected into the Node suite and eight into the served page, each watched to fail the test it names; the last of those was strengthened because the first version passed. `web/assemble.sh` grew an import-completeness guard whose own first version was blind to the multi-line import it exists for. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |