Three facts from Wave N's Codex round.
The first is the one that generalises. improvise-26dm's round 1 found its OWN
defect — the right-hand side of a completeness diff was a second hand-written
list, not the derived enumeration its prose claimed — and shipped an honest
correction of the prose. Codex asked for the fix instead and was right: the
disclosure was accurate and still the weaker artefact, because the fix was
four lines of plumbing. Correcting prose is the fallback, not the deliverable.
Two traps, both of which caught someone:
cfg(test) and pub(crate) are different walls, and only one is a visibility
keyword. The follow-up said 'pub(crate) -> pub' — filed by the agent that
wrote it and repeated by me when I authorised 'one token' — because both of us
read the item line and not the attribute above it.
And cargo tree shows DEV edges by default, so asking whether a new feature
leaked serde_json into the wasm bundle reports it PRESENT. --edges normal
reports 0. I verified both readings myself. Same class as the difftastic grep
trap: a command that looks like it answers the question, answering a
neighbouring one, in the direction that reads as a finding.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
Root cause of PR #91's blocker 1, and it was mine. The row 'Change what a
browser resolves without the host' named improvise-wasm-client/src/keys.rs.
That file is 187 lines holding only BrowserKey — a KeyboardEvent flattened to
an improvise key. The resolve function and KeyOutcome live in
improvise-protocol/src/resolve.rs and are shared by both front ends.
So the author consulted the map, opened the file it named, grepped it
honestly, and wrote a true-and-irrelevant negative claim into context/ — the
one place design-principles §6 says a reason must not be wrong, because
nothing can find it later. A stale pointer does not merely fail to help. It
manufactures confident false claims, because the reader has done exactly the
work the map told them to do.
The whole wasm-client inventory block was stale with it: keys.rs listed at
1597 lines against an actual 187 with a description of what resolve.rs does,
render.rs 669 against 888, socket.rs 444 against 289, lockstep 606 against
1126 — and state.rs listed at all, when ClientState moved to
improvise-protocol/src/client.rs. My earlier line-count refresh missed every
one of them for the reason I documented at the time: the wasm-client/
shorthand prefix resolves to no path, so a path-walking refresh skips the
block silently.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
Wave K's own prose said improvise-zv8t cost nothing — "no document that
fitted before stops fitting" — reasoning that a tree splitting on a numeric
category was refused as an artefact anyway. False: the refusal is conditioned
on used_features, so a fit that never split on the column SUCCEEDED, and the
wide columns are exactly the ones a fit has least reason to split on. Measured
with a throwaway probe on both sides. The sentence sat one paragraph below the
"permanent refusal" claim the previous wave had just corrected, in the same
section, green on either reading.
So prose_that_calls_something_permanent_forecloses_the_ticket has a sibling. A
cost claim is as checkable as a permanence claim and is checked the same way:
construct the document the claim says does not exist. Filed improvise-e8no for
the cost, with a note not to close it by making the forcing conditional on
cardinality — that is inference by another name.
The branch's own adversarial round found a bug the branch introduced: forcing
by column NAME while budgeting by column INDEX, and a document may legally
name a category and a measure the same thing. Repaired as a data dependency
rather than a moved line, so the injection is a compile error.
GridLayout::new has now caused FOUR bugs. improvise-iot6 was the last
production instance — every remaining call site is test code, which I verified
rather than took on report: two of them sit inside cfg(test) blocks well below
the module head and neither was named in the hand-back.
Suite re-measured here: 1464 over 35 binaries, 1 ignored.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
Five facts from Wave J. The one worth reading is the first: check_the_checker
is written as "inject the fault and watch it fail", and the case where it
PASSES is the more informative result, because it is evidence about the test
rather than about the code. On improvise-sihr, forcing
ReturnToNormalUnlessMoved alone back to Normal left the characterization test
green — which is how the untested path was found. An empty colon line takes
ExecuteCommand's is_empty branch and settles through ChangeMode, so the effect
settling every colon line that actually runs had no test. There are two tests
rather than one because of it.
Also records why ChangeMode could hide the bug at all: it was a simple_effect!,
which declares a one-field newtype writing one place, so there was nowhere for
a rule to live and the exit mode had to be a constant in the keymap and in
each commit command. Gotcha 16 already warns the macro's kind argument must be
mandatory; this is a second cost of the same shape.
And that improvise-9c9o's ticket pre-diagnosed its own fix as a protocol
change, and was wrong — the basis is App's own bookkeeping, so it was achieved
by moving where the re-base happens. A pre-diagnosis is a hypothesis wearing
the authority of a decision.
Inventory: three protocol files were absent entirely — help.rs (925),
client.rs (255) and resolve.rs, which at 2477 lines and 46 tests is the
largest file in the crate.
Filed improvise-e5pf: Command::ChangeMode(Normal) is on the wire and
reduce_view folds it without the records resolution. Latent — keys.rs emits no
ChangeMode and a view-half command never reaches the host's slice, both
verified rather than assumed — and closeable only by enriching the projection.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
improvise-864a diagnosed over all 100 push-to-main runs rather than the three
the ticket named — and the three-run framing was mine, a sampling artefact
that made a two-day, sixteen-failure pattern look like a concurrency race
between queued deploys. 12/12 artifact-not-found failures had another run's
SUCCESSFUL deploy complete inside their own upload-to-deploy window, against
2/56 successes.
The decisive observation was a negative one already sitting in a log: a run
seeing its own harness-serve and wasm-bundles but not its own github-pages.
Same run, same token, same call, same retention — which kills flakiness,
eventual consistency, retention, token scope and a v3/v4 mismatch together.
One clause of the ticket's own correction is falsified: PR #46's guard aimed
at the right quantity for the wrong reason. It still must not ship, because
the association is not a mechanism and one run survived a foreign deploy in
its own window.
improvise-ijql closed additive. The author argued against the precedent I
pointed it at and was right: a provenance line qualifies an answer the run
gave, a disclaimer qualifies one it did not, and exit 0 is what CI branches
on. The rule is now stated over the exit code.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
Gotcha 24 rewritten. Since improvise-ni6k the cursor is invalidated by
OBSERVING the grid's frame in apply_effects, inside the loop; no effect calls
reset_cursor at all, where this paragraph said thirteen did two days ago and
two before that. Total over EffectKind::Model(_) by construction rather than
by a maintained list — but totality is conditional on classification, and
nothing makes classification total.
Records the two-arm asymmetry and why it exists: narrowing the CellData arm
to one view measured as free and silently lost a multi-client invalidation.
The absolute is stated beside the ratio.
Two beads filed from the re-review, neither blocking the merge:
improvise-iot6 (P2) — render_csv builds GridLayout::new, so CSV export writes
rows the view prunes. FOURTH instance of that trap and the first in
production a user can see; reachable from :export and the CLI, and none of
export.rs's three tests mentions pruning, which is why it has been shipping.
improvise-9c9o (P2) — a geometry effect immediately after a mid-batch
SwitchView skips the check entirely. Passes on pre-PR main and on round 1,
fails at the merged head. Under-reporting, which gotcha 35 says is the
expensive direction.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
The ticket blamed an unbounded grammar walk. Instrumenting the halves
separately: load_grammar() x1000 = 2.50s against a 15.7ms walk, so the walk
was 0.6% of the cost and improv_file() was re-parsing improv.pest once per
generated case. A OnceLock took improvise-io's lib from 1.42s to 0.21s with
the strategy's input space unchanged; constraining it would have moved 15.7ms
and shipped as the fix.
Re-measured here rather than taking the hand-back's figure, which differed:
1445 tests over 34 binaries, longest single binary 0.6-1.0s across two runs on
one machine. That spread, on one machine in one sitting, is its own argument
for reading your own run.
Also corrects the repo map's bug-fix workflow line, which said to document a
bug in the test's doc comment — the two-line comment cap forbids exactly that
and design-principles §7 says the opposite. A fact stated twice, changed by an
edit rather than a grep.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
Facts from PR #83, including two that replace text I had not yet applied —
the earlier phrasing said evidence names are "leaf names", which the merged
work makes wrong.
The finding worth carrying is one the review did not contain and the author
found while fixing it: NEITHER HALF SUFFICES. Refusing an ambiguous leaf does
not survive a rename — rename one of a colliding pair and the leaf is unique
again and resolves silently to the survivor. Qualification is the fix; the
refusal is only what forces someone to write it. Verified here independently:
un-qualifying the cited heading exits 1 naming both candidates, restoring it
exits 0.
improvise-ijql is NOT closed. The docs half is done, but line 1010 still
early-returns on the flag, so --check-evidence still selects rather than adds
and still says nothing about the cap it skipped. Recorded on the ticket with
the two candidate shapes.
Filed improvise-sxsf: `cargo test -- --list` does not name the binary, so two
crates sharing a module path remain indistinguishable. Not reachable today,
not guarded, and "verified latent" ages the moment anyone adds a crate.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
improvise-ahdf. Gotchas 36 and 43 named Sessions::apply as the production
door. It is not pub, it delegates to apply_checked after Checked::against,
and its only call sites are tests — Host calls apply_checked. Verified by
reading the signatures rather than the prose: apply at project.rs:878 has no
pub, apply_checked at :887 does. design-principles.md §5 already said
apply_checked, so the repo map was the stale copy of a fact stated twice.
Two of the four remaining mentions were live claims and are corrected; one is
historical ("demonstrated against ... before removal") and stands, and one is
the new sentence saying apply is not pub.
Also closes 26 tickets whose fixes are in merged main, each verified against
the code or against an evidence entry rather than against a PR body. Left
open deliberately: h074 (reported half-done), s17o (a decision in #78's
ground), mcq4 (fixed to 1.9x, residual documented), lltt (P1, detector only).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
Gotcha 49 records improvise-kc7n: a websocket handshake is exempt from the
same-origin policy, so no network control can see cross-site hijacking — an
IP allow-list, a private network and loopback binding all answer "may this
client reach the endpoint" and the honest answer is yes. Loopback is the
least protected case, not the safest. Records why all three arms are decided
rather than defaulted, and that none of it is authentication.
The File Inventory line counts were audited: 26 of 61 resolvable entries had
drifted past 10%, several by a third. Refreshed, and relabelled as a dated
snapshot with the method, because false precision in a carefully-checked
document is worse than an obvious estimate. Two traps found while doing it,
both recorded: a path-walking refresh resolves a bare name like lib.rs
against the first matching crate and rewrites the wrong number, and the
ws-server/ entries use a shorthand prefix no such walk resolves at all.
The code-review skill's gate list now runs BOTH spellings of
check-comment-runs.py. The flag selects rather than adds, so --check-evidence
never runs the comment cap and does not say so; three separate agents hit it
in one campaign (improvise-ijql).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
PR #82 disproved the replacement claim this orchestrator had handed its
author, not only the original: of the four prepare steps documented as
"the reason the test is not vacuous", only DeleteView and ShowItem are.
ToggleGroup::apply toggles collapse unconditionally — the membership check
gates only reset_cursor — and TransposeAxes' step restates a precondition
View::on_category_added already establishes. Verified against the source
before recording.
Also records the detector half of the N-1-guarded-edits shape, named by
the PR #79 reviewer: a comment explaining why something is correct is weak
evidence that anything checks it, and the more carefully argued the
comment, the more likely it is standing in for a test. Five instances
across two independent reviews.
The two new keys are dated slugs rather than improvise- ids, because bd
issued none. An ID-shaped non-ID is worse than a dead id: the diagnosis is
identical and the repair is not.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
Verifying the correction before merging it found the claim true but
understated. The reset_cursor set and the LayoutGeometry set are both
thirteen and differ THREE EACH WAY: CreateView, DeleteView and
SetNumberFormat declare the kind and never reset; AddItem, AddItemInGroup
and RemoveItem reset while declaring CellData, because adding or removing
an item renumbers the grid even though the change is data.
The naive awk over LayoutGeometry reported fourteen, picking up
HelpPageSet — the exact artefact this document already warns about two
paragraphs away, walked into while writing the correction. That warning
is now attached to the derivation it applies to.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
repo-map gotcha 24 named TransposeAxes and CycleAxis as the only effects
calling reset_cursor, and listed nine others as renumbering the grid without
resetting. improvise-9mo9 closed that the day before: all of them reset,
thirteen in total, SetPageSelection conditionally. The paragraph named as
absent the exact set that was by then present, so it read as a survey of
outstanding work. Re-derived by brace-matching impl Effect blocks, and the
derivation is published beside the list so the next reader can re-run it
rather than trust it.
gotcha 44's group_of sentence said "nearest preceding header ... changing
that means changing AxisEntry and is filed, not smuggled in". improvise-gd83
landed that change in PR #77: AxisEntry::GroupEnd exists and group_of answers
membership. Names the two that are still open beside it, h0iv and 1wl0.
Three doctrines added. The one worth the space is
name_the_test_that_fails_when_only_this_edit_is_reverted: three independent
reviews in one wave found the same shape, a repair of N edits where N-1 are
guarded and the last carries a careful comment instead of a test — and the
unguarded edit is reliably the one the author understood best.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
The batched instruction-file edits owed by improvise-jytm (PR #48), applied now
that it has merged. Text supplied by the WU-E agent and audited before applying:
97 ticket IDs across both files, all resolve.
repo-map gotcha 29 is replaced entirely — the two copies of "which view is
active" are now ALLOWED to differ, and the gotcha said the opposite. Gotcha 36's
`apply_switch` half is inverted: it now takes a `SessionId` and is addressed to
exactly one session, where the paragraph said its taking no SessionId "is the
statement". Gotcha 38's counts move 29 → 26 model effects and 28 → 24
ModelCommand variants, and its `ViewBack`/`ViewForward` sentence is deleted for
being wrong on all three clauses. Gotchas 3 and 44 gain a clause each.
One paragraph needed more than a correction. Gotcha 36's `ToOthers` trap
describes a mechanism this PR deletes — but the lesson outlives the mechanism,
so it is rewritten in the past tense with the two rules that survive: assert the
`Broadcast` ITSELF, because the audience is the message rather than something to
infer from folded state; and seat a bystander where a wrong relay can move it
FROM, because a bystander already on the relayed value cannot see a wrongly
WIDENED audience — the wrong relay folds to a no-op. Review demonstrated that
vacuity twice on #48, in tests whose evidence file had stated the risk in one
sentence and claimed the assertion covered it in the next.
kb.pl: four clauses replaced, two added. `lockstep_binds_every_session_not_the_asker`
changes shape rather than wording — active_view was the example that taught it
and is now out of its scope, so what remains under it is the document half and
the doctrine becomes a test for whether a value is shared AT ALL before it is a
test for who hears about it.
The addition worth reading is `an_injection_that_did_not_compile_reads_as_a_clean_run`,
caught by #48's reviewer in its OWN tooling: a grep for `^error[` that misses a
bare `error:` turns a failed compile into a green read — the defect injection
exists to catch, one level up in the instrument. The rule is deliberately not
"widen the grep", since a pattern can always be one case short: require a
POSITIVE signal that the injected build ran. A count cannot be silently empty.
Counted before and after, on the predicate that has been silently wiped before:
doctrines 55 → 56, decisions 47 → 48, invariants 51 → 51. kb_consistent exits 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Found by adversarial review of PR #47; I verified the chain rather than taking
it. shell.spec.js's "the standalone deployment" loads the app with no ?ws=,
which spawns the worker (app.js:112), which imports the glue (worker.js:4),
which calls bindings.rs::start (:30) and OpfsFs::open (:35) — and the spec then
asserts a grid draws and a cell is editable. So the whole chain has executed
under Chromium in CI since improvise-vvl3 landed the harness.
Five places claimed it never runs: gotcha 45, the File Inventory line, two
kb.pl facts, and improvise-bck's grooming note. Two of those are corrected here;
kb.pl goes with the wave-end batch, and the bead belongs to the WU-P agent.
Every copy was written before the harness landed and none was re-read after —
gotcha 48's rule ("when a fact is stated in more than one place, changing it is
a grep, not an edit") aimed at exactly this and did not fire, because nobody
grepped for a claim they had no reason to doubt.
The sharper lesson is in the gotcha now: a "never run" written in prose is
unfalsifiable by any check this repo has. kb.pl's CI gate verifies the knowledge
base is CONSISTENT, not that it is TRUE, so a false invariant passes it forever.
And this one understated coverage, so nothing failed and nobody looked — the
comfortable direction to be wrong in, and the reason it lasted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four findings, all verified against the tree before acting.
F10 — this PR's own edit to design-principles §5 made a sibling note in
context/code/command.md false: it said §5 "still names Sessions::apply" and
recommended apply_checked. A PR whose whole subject is stale prose had left a
new stale copy behind, which is gotcha 48 exactly ("changing it is a grep, not
an edit"). Note deleted; the verified claim above it stands.
F11 — §5 named both apply and apply_checked as "the one door". apply has no
production caller anywhere in the workspace; Host calls apply_checked. Naming
two weakens the claim §5 makes, so it now names the one production uses.
F12 — gotcha 17 said "four call sites". There are six uses across the two
evaluators: a div/0 guard plus the Eq and Ne arms in each. In a file agents
follow literally, a reader who greps and finds six cannot tell whether the
prose or the code moved.
F13 — gotcha 17's sibling said the Dirty::of doc comment "keeps only the
one-line reason LayoutGeometry is spelled out rather than EVERYTHING". It says
no such thing: it points at context/code/command.md, and the EVERYTHING reason
is a two-line comment inside the match body. A correction to prose that
asserted the negation of the code should not itself misdescribe the code.
Two findings deliberately not fixed here and filed instead: improvise-ahdf
(repo-map gotchas 36 and 43 carry the same stale Sessions::apply claim —
pre-existing, and inside long paragraphs that need reading rather than a sed)
and improvise-rm4s (--check-evidence accepts headings naming tests that never
run).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Orchestrator doc pass over the comment wave. Each claim was verified against
the tree rather than taken from the queue.
Gotcha 17 recorded the *opposite* of the code: it said float equality was
"currently mixed", with `=`/`!=` on a 1e-10 epsilon and the div-by-zero guard
checking `rv == 0.0` exactly. Both now go through FLOAT_EQ_EPSILON, at four
call sites across eval_expr and its aggregated twin. Rewritten to say so and
to point at context/code/core.md §4.
Gotcha 35 called the doc comment on `Dirty::of` "the only record" of its
derivation. The comment wave moved that reasoning into
context/code/command.md and left the comment holding one line, so the
instruction to re-read on reclassification now names the prose.
design-principles §5 named `Sessions::apply` as dispatch; `apply_checked` is
what `Host` actually calls since improvise-955n answered a whole frame at
once. Both are real, so the row names both.
improvise-mbmf: the_ranking_is_ordered_shares_with_the_unused_features_dropped
asserted a length of 2 from three features — the zero-importance one dropped —
under the message "the zero-importance feature is listed". The message is read
only on failure, which is precisely when it would have described the wrong
behaviour as the requirement. The test name and doc comment were already right.
cargo test --workspace: 1311 passing, unchanged.
Closes improvise-mbmf.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review findings on PR #30, all three applied.
1 [P1]. The claim that the handshake watchdog overwrites a genuine mid-session
disconnect 8s later is FALSE on any tree that has PR #21. `close` routes BOTH
arms through `setShellFailure` (app.js:248-250), which clears the timer
synchronously (app.js:165-169).
It was believed because it was measured honestly — against a tree predating
PR #21 — and then reported as "surviving" that PR on the strength of a
simulation that re-created only the `opened` flag and not the other half of the
fix, the routing. A simulated fix that is not the fix you named reproduces a
bug the real code does not have, and reads exactly like a confirmation.
Rather than delete the claim, execute the property it denied: the
open-then-dropped test now runs past HANDSHAKE_TIMEOUT_MS + 2s and asserts
"sent no document" never appears. Proved discriminating by removing
`setShellFailure`'s `clearTimeout` — the assertion then fails with exactly the
message xps8 described. gotcha 48 and the spec's comment block corrected; the
ticket is cited in neither. Recorded as
doctrine(a_simulated_fix_must_be_the_fix_you_named), the sibling of
an_injected_fault_must_be_the_fault_you_named that fails the other way: a wrong
fault proves a check that cannot fire, a wrong simulated fix proves a bug that
does not exist.
2 [P1]. The previous commit created the contradiction it was meant to remove:
design-principles recorded the measured ~2.2s while kb.pl still said "under 2
seconds". The ambiguity between per-binary and wall clock is what let it drift,
so both now state two numbers — no single binary over 2s, wall clock is the
sum, measured 1309 tests / 33 binaries / ~2.2s / longest 1.42s — in kb.pl,
design-principles and repo-map alike. Also 16 -> 17 in doctrine(binding_dump_diff).
3 [P2]. The new audit exclusion was unanchored, so `improvise-gp` was a prefix
match that would silently exclude a real future ticket. Anchored `^…$`. It
immediately caught the example id the explanatory paragraph itself spelled out
— a recipe is code, twice over.
CLAUDE.md: declined, with evidence. Both copies of the duplicated tail sit
inside `<!-- BEGIN BEADS INTEGRATION v:1 profile:minimal hash:ca08a54f -->`
markers written by `bd setup claude` and `bd setup codex` and refreshed against
that hash. A process change there is one tool invocation, not two hand edits,
and a pointer makes `bd setup <recipe> --check` report it modified. Recorded as
kb_issue_found(claude_md_tail_is_generated_not_duplicated) so it is not
re-proposed.
kb.pl: doctrine 54->55, kb_issue_found 11->12; invariant 51, decision 47,
location 34 unchanged. kb_consistent passes.
cargo test --workspace: 1309 passed, 0 failed, 2 ignored.
harness (Playwright): 12 passed, 3 skipped (webkit has no OPFS).
Dead-ID audit with the anchored recipe: 157 tokens, all resolve.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The fold rule is intact under the new field names (improvise-anr9 renamed
the GridUpdate arms to RowFrame/ColFrame and GridCache's fields to
row_frame/col_frame), but three sentences still named the old ones.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An independent review checked every factual claim in the docs against the
code at 22d43b2, after eight PRs landed in one wave. This applies its
replacement text.
context/repo-map.md
- gotcha 44 rewritten: the cache DOES frame the grid now (improvise-anr9,
AxisFrame/FrameEntry/header_level_widths). The surviving half — the TUI
builds at offsets zero, so a cache position is absolute — is kept,
because four code comments cite gotcha 44 for exactly that.
- gotcha 45: ClientFrame::Save exists (improvise-368h); MarkDirty is gone
and dirtiness is read off EffectKind::Model(_); Rc<T>: Storage lives in
improvise-io (improvise-hmws); OpfsFs::list joins dir (improvise-vhcz);
OpfsFs is run by harness/ and bindings.rs is now the dark corner.
- gotcha 47: Host::commands answers the frame whole (improvise-955n); the
open hole is clause 4, not a per-command Checked. set_viewport "must"
-> "does".
- gotcha 43: the frame, not the command, is what is answered; two of three
tiers hoist.
- gotcha 21 widened: three effects abort, and tidy-up goes at the head.
- gotcha 48: web/app.js is executed by harness/tests/shell.spec.js;
improvise-m18f -> improvise-taxx (fixed); the open half is improvise-xps8.
- gotcha 42: the never-rendered / improvise-hwpi sentence pair deleted.
- gotcha 18: live_records and analyze_records are both decided now.
- gotcha 1: the records columns are [coords…, +cat, measures…, +].
- AppMode is 17 variants, ModeKey is 17, eight text-entry modes; the keymap
table moved to improvise-protocol/src/keymap.rs (six references).
- the Wasm section: the mirror is gone; the wasm gate covers eight crates.
- the File Inventory regenerated rather than patched (57/79 rows were
stale); the duplicate ws-server/host.rs collapsed to a pointer at the
23-line shim; harness/, protocol keymap.rs/viewmodel.rs, improvise-gp
fit.rs/dataset.rs, cmd/fit.rs, model/record.rs and the integration tests
added.
- the suite speed re-measured rather than re-asserted.
README.md — the four "no browser here" sites narrowed to what is still true;
17 modes; the browser section names the Playwright harness.
AGENTS.md — the dead-ID audit recipe was itself defective: two paths, a
character class blind to a commit SHA, and a two-name exclusion list. Widened,
made runnable under the worktree shell guard, and its own prose no longer
spells any id.
prolog-sessions/kb.pl — seven invariants replaced in place (they were false),
five location/3 facts re-sited plus three new, six doctrines and seven
invariants added. doctrine 48->54, invariant 44->51, decision 47 unchanged,
location 31->34. kb_consistent passes.
Comment-only Rust changes: a duplicated sentence in worker.rs, records_columns'
doc omitting NewCategory, improvise-709f2df -> commit 709f2df, and three more
copies of "OpfsFs has never been run".
cargo test --workspace: 1309 passed, 0 failed, 2 ignored — unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
User ruling 2026-09-20: minimise the amount of new code a new UI framework
needs. Centralise logic in a redux-like store so a second or third front end
is a function from state to pixels plus a wire for events, not a second
implementation of how the application behaves.
Names the pieces that already exist as what they are -- Command is the
action, reduce_view and WidgetCache::apply are the reducer, ViewState plus
the caches are the store, Sessions::apply is dispatch -- and gives the
question to ask of any front-end code: would a third front end have to write
this again?
Three things make it concrete: the measure is lines of framework-specific
code and should trend to 'paint and wire'; a derivation done twice is the
defect even when both copies are correct today (improvise-edp is the worked
example, gotcha 44 is the same defect still open); and sharing a table beats
testing two copies of it, which is why the keymap is shared rather than
mirrored. Also says what legitimately stays local: painting, event wiring,
and a semantic-value-to-framework-type lookup like AxisColor -> axis_fg.
Sections 5-7 renumbered; the §6 testing references in repo-map.md and the
code-review skill updated with them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
The browser JavaScript is argued, not tested, and the two rules it can break
are both about WHEN: an async message listener does not serialize, and a
watchdog must be disarmed rather than pre-empted. Both found by review
rather than by a test, because nothing here runs a browser.
Plus the third thing no test can see: three comments in one range said 'not
built yet' about work that landed in that same range, each because a
follow-up commit updated one copy of an argument and not its siblings.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
The 'Change a browser<->server frame' row still named
improvise-ws-server/src/wire.rs, which improvise-xks6 deleted -- the first
place the next agent looks. Flagged by a reviewer and missed twice.
improvise-worker-server appeared nowhere in the map at all: not the crate
list, not the how-to-find table, not the inventory, and the Wasm section
still said six bundle crates.
Gotchas 45-47 for the three undocumented waves -- the worker host (and the
three gaps it found in the SHARED layer, which the socket server also has),
the page's two transports plus the assertion the demo was built against, and
client-side key resolution. Each was proposed by its agent and never applied.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
Gotcha 43 and ADR 001 both described apply_command/reduce_full as a
deliberate exemption with a hole in it. improvise-ondj closed the hole with
a type: Checked<'_>, whose only constructor runs the check. The principle is
unchanged -- a host holding the App is the truth, not a claimant -- but the
unchecked path is now unconstructible rather than discouraged.
Records why visibility was the weaker option (reduce_full has no production
caller, so pub(crate) would have made it dead code), that the regression
guard is the signature rather than a test, and the residual hole: a Checked
does not name which App it was checked against.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
improvise-xks6 consolidated three envelope vocabularies into one pair, so
the 'socket.rs is not wire.rs' half of gotcha 42 is now history. Replaced
with the question that had to be answered first -- do the worker and the
socket want the same vocabulary -- and the test applied to each difference:
whether the ARGUMENT for it names the thing they differ in. None did.
Also records why deleting both mirror tests was the measure of success: what
they checked is now type identity, and a surviving compatibility test after
a merge is evidence the merge did not happen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
Six improvise-* IDs cited in repo-map and ADR 001 never existed -- filed
with output suppressed and then written into the docs from memory. A dead ID
is worse than no ID, because it reads as a promise the follow-up exists. All
six corrected to the real tickets, and AGENTS.md gains the audit loop.
AGENTS.md also gains what PR #12 learned: requestReviews takes botIds and not
userIds, gh is shell-aliased to a different tool here, a Copilot review takes
about six minutes, and a finding can be CORRECT and still be wrong to fix in
that PR -- #12 applied a truncate panic (byte-identical on ASCII, so it cost
no evidence) and declined a padding bug in the same file that would have
rewritten the characterization tests which were the PR's only evidence.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
From the wave review. Gotcha 36 gains why every relay out of apply_model is
ToAll and why copying apply_switch's ToOthers is invisible from the
bystander's side -- a test asserting through a 'what do the others hear'
helper looks away from the asker by construction, which is how the bug
shipped. Gotcha 24 gains the incompleteness of the reset_cursor calls the
epoch observes. Gotcha 43 gains what Basis does and does not buy, and that
apply_command/reduce_full are a second DOOR rather than an exemption: the
struct variant made the basis impossible to omit from the message, not
impossible to ignore on arrival, and '..' is the syntax for ignoring it.
ADR 001 gains a 'what the check is not' section saying the same plainly, so
the unforgeability claim is not relied on later.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
Gotchas 43 (a batch carries its basis, and the server had to publish it
first) and 44 (the TUI grid draws from its cache, but the cache does not
frame it -- the browser shows Food/Alice where the TUI shows two columns).
Amendments: 24 gains the geometry half of the mirror lockstep and why doing
only the server half is worse than doing nothing; 32 gains the wizard's
answer (a cache with deliberately no projection); 33 gains totals and
page_coords; 36's broadcast becomes Vec<Broadcast>; 39 gains the third
Applied case. Plus measured inventory counts.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
Gotcha 18 gains matching_cells/matching_values and the reason the old hash
walk was not a tidiness issue -- the drill snapshot froze its records in that
order while pending_edits are keyed by row index, so a second process
addressed different records.
Gotcha 37 gains (d): a fit is storable only when the document can name its
features, the check asks the document rather than the name's shape, and this
is an interim because remedy 3 needs a scalar coordinate predicate the
formula language does not have.
Also corrects improvise-gp's dependency line, stale since improvise-4s8.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
Three corrections. The selection is reconciled by ADDING the offset to the
render index, not subtracting it from the selection -- which is why there is
no underflow path; and the test that earns its place uses nonzero, unequal
offsets so it separates 'reconciles' from 'ignores' and from 'swaps'.
The mirrored framing is kept honest by TWO tests: the round trip catches a
rename, and an exhaustive match over the server's enums catches an addition
the hand-written fixture list would miss. And the check-the-checker was
itself broken -- it injected malformed JSON, so is_err() held for a syntax
error and would have held against an enum that really did have the variant.
Plus what a careful read of web/app.js established and what it could not:
filed as improvise-jm5v.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
Neither improvise-ws-server nor improvise-wasm-client appeared anywhere in
the map, which is a gap in the two newest crates and the two the browser MVP
is made of. Adds both to the crate list, the how-to-find table and the file
inventory, plus web/.
Gotcha 42 records why the client's render decisions are in Rust (the DOM half
is untested JavaScript), the three answers that would be silently wrong in JS,
and why socket.rs duplicates ws-server's enums -- with the note that there are
three envelopes for two deployments and no Key variant on the socket.
Also: gotcha 34's 'section headers are unquoted on both sides' clause was
made false by improvise-pczv and is replaced by what header_name now does;
the Wasm section said four crates and now says six.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
Accepted but explicitly not a priority: the goal is the OPTION of using
improvise as a DB client. Written down now so the option stays open, since
several decisions in flight could foreclose it by accident.
The consequence worth the ADR: a live-backed document splits into our
configuration and their data, which .improv does not distinguish — and read
through the DB-client framing that is the natural shape, a saved query plus a
pivot configuration, i.e. a report definition. Also flags that an external
writer voids a guarantee ADR 001 currently states.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
improvise-9ix declined on measurements: 22% larger than .improv, 4.7x gzipped,
160x at the floor, and still rewritten whole on every save. The generalisable
half is that a second serialization format duplicates the grammar and its own
round-trip test cannot detect drift from the first — the missing cross-format
equality test is what made the ticket look cheap.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
An opaque surrogate on CellDisplay whose absence means the cell is an
aggregate, so a client cannot compose SetCell for something unsettable.
Records why _Index cannot serve: VirtualIndex is positional over the matching
set, so a stale one names the wrong record silently — but what it points at,
record identity, is ADR 001's unit, and one surrogate can serve both.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
Gotcha 38 justified keeping ApplyAndClearDrill and AcceptWizardModel on the
wire because 'the command is portable even where the operand is not'. That was
the wrong criterion: gotcha 36 already states the right one — an unaddressed
command is correct exactly when the change is addressed to no one session, and
these are addressed to precisely one, never the sender.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
gotcha 34 still claimed improvise-io carried a third pipe-quoting copy, which
improvise-i2m5 deleted, and did not record that the keyword guard is a
user-confirmed decision rather than an agent's caution.
gotcha 38 records that the awk recipe for deriving ModelCommand reports 30 and
the truth is 29 — HelpPageSet is ViewOnly and bleeds across the #[cfg(test)]
boundary. Brace-match the impl blocks.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
Copilot's PR #10 finding showed the code moved only the addressing session's
mirror; the gotcha described it the same wrong way. Records apply_switch
taking no SessionId and Applied::broadcast carrying the accepted command to
the other clients' own slices.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
kb.pl: added crate/layer/depends/forbidden facts for gp_tree and
improvise_gp (the review found the kb modelled 6 of 8 workspace crates
and so had no opinion on the new one); corrected the improvise_protocol
description, which still said RenderCache. Two new doctrines, both
proven by fault injection during the review:
fold_tests_must_test_order_not_the_axiom
a_macro_must_not_supply_a_forced_classification
No illegal dependency; kb_consistent passes under trealla.
repo-map: gotcha 32 now says bootstrap is a compaction, not a replay;
new gotcha 33 for the labels-frame-cells contract; gotcha 25 notes the
new Clone/PartialEq derives; protocol inventory counts re-measured from
the files rather than copied; registry.rs test count corrected.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH