Commit Graph

96 Commits

Author SHA1 Message Date
Edward Langley 381a2f55d3 docs: the two thirteens differ in both directions, and my own awk proved why
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
2026-09-20 21:17:31 -07:00
Edward Langley 2afbf37b67 docs: correct two stale gotchas, and record the N-1-guarded-edits shape
CI / Build all bundles (push) Has been cancelled
CI / Test and lint (host) (push) Has been cancelled
CI / wasm32 check (browser bundle crates) (push) Has been cancelled
CI / Browser JS unit tests (node --test) (push) Has been cancelled
CI / Knowledge base consistency (push) Has been cancelled
CI / Lint the harness crate (push) Has been cancelled
CI / Browser harness (OPFS contract + shell) (push) Has been cancelled
CI / Deploy to GitHub Pages (push) Has been cancelled
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
2026-09-20 21:11:56 -07:00
Ed Langley 6b4689443c Merge pull request #45 from fiddlerwoaroof/docs/orchestrator-doc-pass
docs: correct two stale gotchas, and an assertion message that lied
2026-09-20 14:01:43 -07:00
Edward Langley ddfc06ed66 docs: follow the active_view split through repo-map and kb.pl
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>
2026-09-20 11:55:35 -07:00
Edward Langley 86fd5dcb2a docs: bindings.rs and web/worker.js are run, and five places said otherwise
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>
2026-09-20 11:22:13 -07:00
Edward Langley dc2f4a06ad docs: complete the corrections, per adversarial review of this PR
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>
2026-09-20 11:06:10 -07:00
Edward Langley 56ff51e444 docs: correct two stale gotchas, and an assertion message that lied
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>
2026-09-20 10:50:30 -07:00
Edward Langley 6f9efcf2e8 docs: improvise-xps8 describes a bug that does not exist — turn it into a guard
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>
2026-09-20 01:11:46 -07:00
Edward Langley 34e5bd5902 docs: gotcha 33/47 still spelled the retired RowLabels/ColLabels
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>
2026-09-20 00:58:04 -07:00
Edward Langley f12a2d3517 docs: reconcile the docs with the code after wave 1
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>
2026-09-20 00:56:33 -07:00
Edward Langley d175b9934f docs(principles): a front end displays state; it does not reimplement behaviour
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
2026-09-19 21:31:58 -07:00
Edward Langley b7dcde56d5 docs: gotcha 48, and the gh graphql workaround
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 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
2026-09-19 20:36:15 -07:00
Edward Langley 190c440be6 docs(repo-map): three waves of debt, and a row pointing at a deleted file
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
2026-09-19 20:22:32 -07:00
Edward Langley 4f5894db66 docs: the basis exemption is no longer a door
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
2026-09-19 18:54:44 -07:00
Edward Langley 5a6c35d4b6 docs(repo-map): gotcha 42 loses the mirror it was written to explain
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
2026-09-19 18:52:59 -07:00
Edward Langley 1f4e5354de docs: six invented ticket IDs, and what the PR loop actually needs
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
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
2026-09-19 18:26:59 -07:00
Edward Langley cba60893a9 docs: the audience follows the optimistic fold; the basis is coordination, not authentication
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
2026-09-19 18:05:32 -07:00
Edward Langley b16d57d629 docs(repo-map): the basis, the geometry lockstep, and the grid's unframed cache
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
2026-09-19 17:59:44 -07:00
Edward Langley 188583f522 docs(repo-map): improvise-gp line count
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
2026-09-19 17:45:43 -07:00
Edward Langley 594e19607f docs(repo-map): the index narrows and the primary map orders; a fit names only what the document holds
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
2026-09-19 17:45:33 -07:00
Edward Langley cad914faab docs(repo-map): gotcha 42 corrected by the review
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
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
2026-09-19 17:40:36 -07:00
Edward Langley 02c794fa2b docs(repo-map): the browser host and client enter the map; gotcha 42
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
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
2026-09-19 17:39:01 -07:00
Edward Langley c000f3a5e7 chore(beads): wave closures and findings
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
2026-09-19 17:35:04 -07:00
Edward Langley f171a7e9ad docs(repo-map): gotcha 7 gains the unanimity rule for non-numeric aggregation
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
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
2026-09-19 17:24:56 -07:00
Edward Langley d97a3afdaf docs: ADR 003 — a model may be backed by a live query source
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
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
2026-09-19 17:16:29 -07:00
Edward Langley 770d35f127 docs(repo-map): gotcha 41 — Storage is not a VFS, and that closes the SQLite question
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
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
2026-09-19 17:08:47 -07:00
Edward Langley 666423d5c8 docs: ADR 002 — how a client names a cell
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
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
2026-09-19 16:53:50 -07:00
Edward Langley 52ecd9b675 docs(repo-map): gotcha 40 (the three basis tiers); correct gotcha 38's criterion
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
2026-09-19 16:35:06 -07:00
Edward Langley 4c174e4795 docs(repo-map): list ADR 001 in the context docs
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
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
2026-09-19 16:10:45 -07:00
Edward Langley 1699c001a8 docs(repo-map): gotcha 39; gotcha 3 gains the mapping trap
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
2026-09-19 15:24:41 -07:00
Edward Langley 8377fe1324 docs(repo-map): gotcha 38; de-stale gotcha 34; gotcha 37 gains the row-slice clause
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
2026-09-19 15:12:14 -07:00
Edward Langley 871c61d063 docs(repo-map): gotcha 36 — a view switch binds every mirror, not the asker's
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
2026-09-19 15:01:20 -07:00
Edward Langley 69afbb41b5 docs(repo-map): gotcha 37, gp-tree inventory, find-things rows
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
2026-09-19 14:23:17 -07:00
Edward Langley c4de6f8895 docs(repo-map): gotchas 35 and 36; g0f answered in gotcha 29
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
2026-09-19 14:11:41 -07:00
Edward Langley 8e68f17f46 docs(repo-map): gotcha 33(c) — both arms re-fit, not just the label arms
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
2026-09-19 14:00:51 -07:00
Edward Langley da93886fb8 docs(repo-map): prune_axis_items now takes a predicate
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
2026-09-19 13:52:21 -07:00
Edward Langley 361ee11a50 docs(repo-map): record the formula-target fix and the quoting consolidation
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
2026-09-19 13:50:40 -07:00
Edward Langley 74e6dbe639 docs(repo-map): record the wave-1 review fixes
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
2026-09-19 13:48:46 -07:00
Edward Langley b759599c6f docs(kb,repo-map): record the new crates and the wave-1 review lessons
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
2026-09-19 13:47:11 -07:00
Edward Langley 55ee619ae6 Merge: gp-tree formula emission, library preprocessing, seeded RNG (improvise-izk, 0yr, lzn)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
2026-09-19 13:38:02 -07:00
Edward Langley 3d1bf16984 feat(gp-tree): vendor a seeded SplitMix64 and surface the seed on every fit
improvise-lzn. `Rng` is a trait so the crate pulls in no generator, and every
caller then wrote out the same SplitMix64 — gp-tree's tests, fit_csv, and next
improvise. It is now `gp_tree::SplitMix64`, ~20 lines and still no dependency.
Taking on `rand` would buy statistical quality this does not need: the point is
that a fit, which writes a rule into a user's document, re-runs identically on
the same data, and that a bug report is reconstructible from its seed.

So the seed is not hidden. `SplitMix64::seed()` keeps the value the run started
from (a generator that has advanced past its seed cannot answer that), fit_csv
takes `--seed <n>` (decimal or 0x-hex), prints it on every fit, and `--save`
records it in `data::Model`. That field is `#[serde(default)]`, so a model
saved before this still loads.

Default-seed output is unchanged apart from the new seed line — diffed against
the pre-change baseline.

Tests: gp-tree's own tests now use SplitMix64 (12 more via data.rs, unchanged
otherwise), and improvise-gp asserts the acceptance property directly — a small
GA fit run twice under one seed emits a character-identical formula, and a
different seed emits a different one, so "reproducible" cannot be satisfied by
a search that never moved.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
2026-09-19 13:37:35 -07:00
Edward Langley d8fd7ae713 Merge: split persistence/mod.rs into six focused modules (improvise-nr5)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
2026-09-19 13:36:04 -07:00
Edward Langley e914959760 refactor(gp-tree): promote fit_csv's preprocessing into the library
improvise-0yr. The example carried the only code that turns a table of raw
string cells into the fixed-width f32 vector a Tree eats — per-column type
inference, one-hot encoding, mean-imputation with a learnable `col.missing`
flag, and the Model pairing fitted trees with the encoding plan. None of it
was reachable by a library consumer, and improvise needs all of it: a pivot
model's dimensions are categorical by default, not by exception.

It is now `gp_tree::data`, and fit_csv.rs is a CLI over it (1261 → 787 lines,
with flag parsing, the demo dataset, the GA loop, CV and the reports left).
The module is data-shaped, not domain-shaped, so the crate stays as
problem-agnostic as before.

Two shape changes while moving:

* `preprocess` returned a five-tuple; it now returns a `Dataset` struct. A
  positional five-tuple is not a public API.
* `build_date` and `encode_row` each spelled out the same seven date parts;
  they now share `date_parts`. Identical values, one definition.

Behaviour is unchanged, checked by diffing the example's output against
pre-refactor baselines: the demo dataset's fit, a mixed
numeric/date/categorical CSV with a missing cell, the saved model JSON, and
the --predict run are all byte-identical. The only visible difference is one
panic message, which stopped saying "predict CSV" now that it is library code.

Tests: 12 in gp_tree::data covering numeric/categorical/date/datetime
expansion, the missing-cell indicator (present when a column had one, absent
when it did not), forced types beating inference, ignored columns, class-code
targets, and encode_row replaying the training expansion — including an unseen
category encoding as all-zeros rather than widening the layout.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
2026-09-19 13:35:55 -07:00
Edward Langley 87f86d7eb5 refactor(io): split persistence/mod.rs into focused sub-modules (improvise-nr5)
Pure relocation, no behaviour change. 2783 lines in one file became:

  mod.rs              the document lifecycle (save/load/gzip/legacy-JSON over
                      Storage) plus the pub facade re-exporting the rest
  quoting.rs          CL-style |…| quoting, the one thing format and parse share
  format.rs           format_md + format_number, persisted_initial_view, coord_str
  parse.rs            parse_md + ImprovParser/Rule, parse_number_cell, and the
                      parser property/edge-case tests and grammar generator
  export.rs           export_csv / export_csv_to / render_csv
  roundtrip_tests.rs  the format_md <-> parse_md characterization suite, which
                      belongs to neither side on its own

Every function moved with its tests where ownership was clear; the 34 tests
that exercise both directions went to roundtrip_tests.rs. `coord` and
`two_cat_model`, previously copied per test module, are now one `test_helpers`
module in mod.rs.

No test changed meaning: all 97 persistence tests still pass, and each moved
function body is identical modulo indentation and the visibility keyword on the
five quoting helpers (now pub(super)).

The public API is unchanged — save, load, save_to, load_from, autosave_path,
format_md, parse_md, export_csv and export_csv_to all still resolve through
`crate::persistence`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
2026-09-19 13:35:39 -07:00
Edward Langley 87677d6321 Merge: command-layer cleanups and two bug fixes (improvise-s13, 8fy, gdl, djn, 1c9)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
2026-09-19 13:35:05 -07:00
Edward Langley 83cc69093d docs(repo-map): record simple_effect!, write_buffer and the enter-mode rule
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
2026-09-19 13:34:37 -07:00
Edward Langley 2a0b937447 feat(improvise-gp): emit a fitted gp-tree Tree as improvise formula text
improvise-izk. New crate `improvise-gp` — the seam between gp-tree and the
formula language. It cannot live in gp-tree (that inverts the dependency) and
it cannot live in improvise-formula (which must not know GP exists).

`formula_text(tree, spec, target)` turns Split{feat,thresh,lo,hi} into
`IF(<name> < <thresh>, <lo>, <hi>)` and a Leaf into its number;
`fitted_formula` hands back the parsed Formula, so the parser is the emitter's
own contract check.

Two things the emitter owes its caller:

* Names round-trip. Feature names come from user data — spaces, operators,
  pipes, keyword collisions, the one-hot encoder's own `col=value` labels —
  so every name goes through CL-style pipe quoting, and a keyword is quoted
  even though it would parse bare.
* Thresholds route identically. A threshold is emitted as the f32's *exact*
  decimal, not its shortest round-trip form. `0.7f32` is 0.699999988…, and
  "0.7" parses as an f64 strictly greater than it, so a row whose feature
  value equals the threshold would fall on the other side of the split in
  formula evaluation than it does in `gp_tree::eval`. There is a test for
  that boundary, and it fails against the naive rendering.

Tests: parse round-trip for leaf/nested/awkward-name/awkward-target cases, the
threshold-boundary regression, and the acceptance property — over random trees
and random feature vectors, `Model::evaluate` of the emitted formula equals
`gp_tree::eval`, exactly (f32→f64 is lossless, so no tolerance is needed).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
2026-09-19 13:34:01 -07:00
Edward Langley ee1bd615ae Merge: per-widget render caches folded from projection events (improvise-rkeq follow-through)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
2026-09-19 13:33:47 -07:00
Edward Langley c9edab1112 refactor(protocol): per-widget render caches folded from projections
RenderCache was one flat, grid-only type. improvise-rkeq decided the
opposite shape: each widget owns its own cache and folds its own stream
of projections, and state synchronizes by events rather than snapshots.

render.rs now holds a WidgetCache trait (Default + one Update type +
apply) and one cache per widget whose reads I could enumerate from the
ratatui widgets: GridCache (the old RenderCache, unchanged in content),
CategoryTreeCache, FormulaCache, ViewListCache, TileBarCache. Projection
is the envelope a widget subscribes by, and Command::Project carries it,
replacing the five flat projection variants.

reduce_view no longer takes a cache: it folds ViewState and passes
Command::Project through. Snapshot keeps its bootstrap-only job, but its
payload is now a Vec<Projection> seed, so bootstrapping is folding and
there is no second whole-state shape to keep in step.

Nothing is defined for the help overlay (static content; help_page is
already ViewState) or the import wizard (its shape waits on
improvise-764). No caches existed outside this crate, so nothing else
moved.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
2026-09-19 13:33:02 -07:00
Edward Langley cd1a98fbfc refactor(layout): collapse the row/col duplication in view/layout.rs
improvise-ai7. Four free functions over `&[AxisEntry]` replace eight
near-identical row/col blocks in GridLayout:

- `prune_axis_items(items, keep)` — the two prune loops in `prune_empty`,
  each of which tracked `data_idx` by hand against a `keep` vector whose
  parallel-length invariant was checked nowhere. It is now a
  `debug_assert_eq!` in one place.
- `data_items(items)` — the four copies of the `filter_map(DataItem)`
  iterator (row_label/col_label/cell_key x2) plus row_count/col_count.
- `axis_label(items, idx)` — row_label/col_label, previously
  character-identical.
- `data_index_to_visual(items, idx)` — data_row_to_visual /
  data_col_to_visual, previously character-identical.

Records mode is untouched: it is genuinely asymmetric (a row is a
coordinate tuple, a column is a measure), and `prune_empty` still returns
early there.

No behaviour change; no test changed. 801 workspace tests pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
2026-09-19 13:32:14 -07:00