fix(worker): report an error with its cause, not just its label
CI / Test and lint (host) (push) Has been cancelled
CI / wasm32 check (browser bundle crates) (push) Has been cancelled
CI / Knowledge base consistency (push) Has been cancelled
CI / Browser bundle + static site (push) Has been cancelled
CI / Deploy to GitHub Pages (push) Has been cancelled

The first real browser run of the deployed page produced 'Cannot open
demo.improv' and nothing else. That string is the CONTEXT vfs::open attaches;
anyhow's Display prints only the outermost context, so mapping through
e.to_string() kept the label and discarded the Storage or parse failure
underneath.

The worst possible place for it: bindings.rs is wasm-gated and is the one
module no test here can reach, so a devtools console is the only instrument
pointed at it -- and it was the code path discarding its own diagnostics.

report() renders with {:#}, which walks the chain. It lives in lib.rs rather
than bindings.rs so it can be tested at all. The test is written against the
old rendering and pins anyhow's Display as the trap, so the helper stops
being needed if that ever changes.

This does not fix the underlying failure; it makes the next reload name it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
This commit is contained in:
Edward Langley
2026-09-19 20:55:27 -07:00
parent 4e1a79fe5f
commit 62bc9bab51
3 changed files with 64 additions and 15 deletions
+1
View File
@@ -1,3 +1,4 @@
{"_type":"issue","id":"improvise-oai9","title":"[bug] The worker's errors reach the console with their cause stripped","description":"FOUND BY THE FIRST REAL BROWSER RUN of the deployed page (2026-09-20), and it is the reason that run produced no actionable information.\n\nThe console said:\n\n improvise worker: failed to start\n Error: Cannot open demo.improv\n\n\"Cannot open demo.improv\" is the CONTEXT vfs::open attaches with with_context. anyhow Display prints ONLY THE OUTERMOST CONTEXT, so bindings::start mapping through e.to_string() kept the label and threw away the cause -- the Storage failure or the parse failure that actually happened.\n\nWHY THIS IS THE WORST POSSIBLE PLACE FOR IT: bindings.rs is cfg(target_arch = wasm32) and is the one module in the crate that NO TEST IN THE REPOSITORY CAN REACH. A devtools console is the only instrument pointed at it. So the single code path with no test coverage was also the one discarding its own diagnostics.\n\nFIXED: a report() helper rendering with the alternate form {:#}, which walks the chain joining with \": \". It lives in lib.rs rather than bindings.rs SO IT CAN BE TESTED AT ALL -- bindings is wasm-gated. Five call sites (start, on_message, flush, documents, refresh).\n\nTEST WRITTEN AGAINST THE OLD RENDERING FIRST: a_reported_error_carries_its_cause_not_just_its_label asserts that anyhow to_string() IS just the label (pinning the trap, so the helper stops being needed if that ever changes) and that report() contains BOTH the label and the cause.\n\nTHIS DOES NOT FIX THE UNDERLYING FAILURE -- it makes the next reload name it. Redeploy and read the console again.","status":"open","priority":1,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-20T03:55:27Z","created_by":"Edward Langley","updated_at":"2026-09-20T03:55:27Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"improvise-vvl3","title":"A headless browser harness: run storage_contract and the shell assertions under Playwright","description":"THE CONDITION THAT DEFERRED improvise-xf1 HAS BEEN MET. The user deferred browser testing on 2026-09-20 with: \"I am happy just putting off the browser test for now: WE HAVE TO HAVE SOMETHING TO TEST.\" After improvise-9x6, djm, hwpi and the demo model, we do -- a complete self-contained application whose ENTIRE untested surface is the part a harness would cover.\n\nTHE EVIDENCE THAT IT HAS FLIPPED, not just an argument that it would be nice:\n * Three of improvise-hwpi four fixes are ONE-ASSERTION tests (status text after a bad frame, status text with no peer, one repaint on commit). That ticket itself said \"consider whether the right first move is a headless DOM -- otherwise the fixes are as unverified as the bugs.\" The fixes are now shipped, so that sentence is true of them.\n * The first-frame watchdog was found BY READING, and only because someone went looking for it. The next one will not be.\n * OPFS is the specific thing MemoryFs cannot stand in for, and it is now on the critical path of the DEFAULT deployment rather than a future one. storage_contract in storage.rs exists and is written down precisely so it can be run against OpfsFs in a browser; NOTHING BUT A BROWSER CAN RUN IT.\n * Persistence across reload is the one improvise-bck acceptance criterion no test in this repo can structurally reach.\n\nSCOPE, deliberately small: python3 -m http.server --directory site, plus one Playwright spec, reusing the assemble step CI already performs. Two parts -- (a) storage_contract against OpfsFs, which is the thing improvise-xf1 is actually about; (b) three shell assertions from hwpi.\n\nShould block improvise-bck sign-off. Un-defer or close improvise-xf1 into this, whichever the user prefers -- xf1 predates the worker and is narrower than what is now needed.","status":"open","priority":1,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-09-20T02:29:52Z","created_by":"Edward Langley","updated_at":"2026-09-20T02:29:52Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"improvise-gv9r","title":"[bug] A wire model command never marks the document dirty","description":"Found by improvise-9x6 (2026-09-20) the hard way: the worker first persist trigger was `if dirty`, and it SILENTLY NEVER FIRED.\n\nreduce::model_effects maps ModelCommand::SetCell to effect::SetCell, which writes the cell and does NOT touch model_state.dirty. Dirtiness is raised by a SEPARATE MarkDirty effect that only the command/key path emits -- so on every transport, a client edit leaves the authoritative document reporting CLEAN.\n\nTwo consequences, and the second is the one that will bite someone:\n * \"Save if dirty\" is a trigger that never fires. Any host built on it loses every client edit, silently.\n * Anything else that reads model_state.dirty about a client-edited document gets the wrong answer -- an unsaved-changes prompt, an autosave, a UI marker.\n\nWHY IT HAPPENED: MarkDirty being a separate effect is deliberate and reasonable on the key path, where a command composes the pair. The derivation that turned effects into ModelCommands (improvise-kmgs) took the effects ONE AT A TIME, so the pairing was lost -- the wire has SetCell without its MarkDirty companion. That is the interesting shape: a derivation that is correct per element and wrong for a set.\n\nCANDIDATE FIXES, and this wants deciding alongside improvise-9x6 sibling ticket about Save: make model_effects emit the MarkDirty companion where the key path does; or fold dirtiness into the effects that change the document so it cannot be forgotten (the unrepresentable-not-validated answer, and probably right); or decide that dirty is a host-local concept the wire has no opinion on, and then say so where someone will read it.","status":"open","priority":1,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-20T02:10:13Z","created_by":"Edward Langley","updated_at":"2026-09-20T02:10:13Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"improvise-368h","title":"[bug] No client on any transport can ask its host to save","description":"Found by improvise-9x6 (2026-09-20) while building the worker persist trigger, and it affects improvise-ws-server too -- which has shipped without anyone noticing.\n\neffect::Save is EffectKind::ViewOnly, and CORRECTLY so: it writes to THIS HOST file path and clears THIS HOST dirty flag, mutating no document state. But the wire vocabulary is DERIVED from the model effects (gotcha 38), so the derivation cannot reach it. There is no Command::Save and no ModelCommand::Save.\n\nConsequence: a connected client can edit the document and can never cause it to be written. On the socket deployment that means a ws-server session edits in memory and the file on disk never changes unless a human is at a TUI that is not there.\n\nTHIS IS A PROTOCOL DECISION, NOT A PATCH. Two coherent answers:\n * ADD Command::Save. But Save is genuinely ViewOnly and genuinely per-host -- a client asking a host to write to a path the client cannot see is a different thing from a client mutating the document, and it needs an answer for \"which path\" and \"what if there is none\" (improvise-lae already exists for the no-path case).\n * SAY EXPLICITLY THAT HOSTS OWN PERSISTENCE, and make each host trigger on its own terms. That is what improvise-9x6 did for the worker, and in a browser it is arguably RIGHT rather than a workaround: there is no file the user owns and no Ctrl-S semantics -- the document IS the OPFS entry. But then ws-server needs its own answer, and today it has none.\n\nDecide which. The gap is that neither was chosen, so the socket server silently does nothing.","status":"open","priority":1,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-20T02:10:11Z","created_by":"Edward Langley","updated_at":"2026-09-20T02:10:11Z","dependency_count":0,"dependent_count":0,"comment_count":0}