Commit Graph

1534 Commits

Author SHA1 Message Date
Ed Langley 4d71c019cb Merge pull request #168 from fiddlerwoaroof/fix/web3-s8h7
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
s8h7: i on an aggregate names drill, in help and in the browser
2026-09-22 21:04:45 -07:00
Ed Langley f909cadf90 Merge pull request #167 from fiddlerwoaroof/docs/commit-frequently
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
docs(agents): commit frequently, so the work is safe in git
2026-09-22 21:00:01 -07:00
Ed Langley 34b56bca07 Merge pull request #166 from fiddlerwoaroof/fix/usus-kb-shrink-guard
kb: refuse a write that leaves kb.pl short of HEAD; publish by rename (improvise-usus)
2026-09-22 20:59:49 -07:00
Edward Langley 334e218c7b s8h7: i on an aggregate names drill, in help and in the browser
In the TUI, EditOrDrill hands an aggregated cell to DrillIntoCell, so `i`
on an aggregate is drill by another door. The help page called it only
"Edit current cell", and the browser refused it in words unrelated to `>`'s,
so a user could not tell the two were one missing feature.

- help.rs: the edit-or-drill row says it drills into an aggregate.
- help.rs: Normal-mode Enter was captioned "Edit current cell (same as i)"
  but is bound to enter-advance alone, which only moves down; the row moves
  to Cursor movement and says so.
- resolve.rs: the Aggregate arm now reads "an aggregate is edited by
  drilling in — " + not_yet("drill"), the same sentence `>` uses.
- context/code/clients.md and the evidence entry reverse the recorded
  "different sentences on purpose" rationale, which was false about the TUI.
- harness/tests/drill.spec.js follows the new wording.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-22 20:53:18 -07:00
Edward Langley ef16723b90 docs(agents): commit frequently, so the work is safe in git
User ruling. A worktree is not durable storage: an agent finished a whole
ticket with every gate green and lost all of it, uncommitted, when its
worktree was deleted during a disk cleanup. The brief template now requires a
WIP commit after the first green test and after every meaningful step, and
separates "do not merge to main" from pushing the agent's own branch, which is
encouraged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-22 20:47:01 -07:00
Edward Langley a55b3c7147 kb: refuse a write that leaves the kb short of HEAD; publish by rename
scripts/kb add wrote onto a working-tree kb.pl that had lost 276 of its 801
clauses and reported success, because kb_consistent checks consistency, not
completeness (improvise-usus). Every write now runs kb-merge.pl `complete`
on the candidate before kb_consistent. It refuses unless every clause HEAD
has is still present by key, or its removal was recorded by `kb rm` in a
per-worktree ledger in the git dir, which prunes itself on every write.

The candidate is now built beside kb.pl, so publishing it is a rename(2)
rather than a possible cross-device copy that a kill could truncate.

Exit codes: 0 done, 1 refused, 2 usage, 3 HEAD's kb.pl unreadable. kb-test.sh
now builds its fixtures from HEAD and adds cases 18-22. Argument in
context/code/kb-tool.md, evidence and rivals in context/test-evidence/tooling.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-22 20:46:03 -07:00
Ed Langley 40b8913397 Merge pull request #165 from fiddlerwoaroof/docs/kb-fmt-and-collapse
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
docs: the fmt gate every brief prescribed, and the collapse ruling
2026-09-22 20:29:16 -07:00
Ed Langley 1d9855f881 Merge pull request #161 from fiddlerwoaroof/fix/hs0u-lut7-browser-prefix
Resolve prefix chords in the browser (improvise-hs0u, improvise-lut7)
2026-09-22 20:29:05 -07:00
Edward Langley 594734ad8d SetYanked is composed now, so it leaves the uncomposed list
A SEMANTIC merge conflict that auto-merged cleanly: PR #160 added
`vocabulary_is_composed.rs`, whose `NOT_COMPOSED` list names `SetYanked`
with the verdict "improvise-lut7: nothing resolves yank, so paste reads a
permanently empty slot". This branch is the fix for improvise-lut7, so
that entry is now false.

git reported no textual conflict — the two sides touched different files —
and `the_uncomposed_list_names_nothing_that_is_composed` caught it on the
merged tree, which is exactly the both-directions check #160 added it for.
Worth stating because the two PRs agree on the diagnosis: the list cites
the ticket this branch closes, and the guard is what turned that agreement
into a build failure rather than a stale sentence.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
2026-09-22 20:23:08 -07:00
Edward Langley e9f7327328 docs: the fmt gate every brief prescribed, and the collapse ruling
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
AGENTS.md's brief template now carries the gate list, because the one I had
been handing agents said `cargo fmt --all`, which reaches only one of this
repo's two workspace roots — and `cargo fmt -p opfs-contract` from the root
exits 1 as a usage error indistinguishable from a real verdict
(improvise-b4ks). Both spellings fail in the direction that looks like
success. The gate is `scripts/fmt-workspaces.sh`.

kb: the improvise-6qet ruling (a collapsed group is one aggregate row), and
the CI lane's doctrine on stubs and fixtures.

Also recovers kb.pl from a truncation: the working tree had lost 276 of 801
facts, most likely to a `kb replace` killed mid-write at a session boundary,
and `kb add` had written onto it with kb_consistent passing. Restored from
HEAD; this commit is 6 insertions, 0 deletions. Filed as improvise-usus.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-22 20:21:05 -07:00
Edward Langley 812f555958 Merge remote-tracking branch 'gh/main' into fix/hs0u-lut7-browser-prefix
# Conflicts:
#	context/test-evidence/clients.md
2026-09-22 20:19:38 -07:00
Ed Langley c26e9d3488 Merge pull request #164 from fiddlerwoaroof/edl.review
Edl.review
2026-09-22 20:18:52 -07:00
Edward Langley d0f2bf9789 chore: update cargo deps 2026-09-22 20:18:13 -07:00
Ed Langley 9430ac733c Merge pull request #163 from fiddlerwoaroof/fix/k84e-rebase-rollback
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
Test clause 4 over the Applied::Rebased path (improvise-k84e)
2026-09-22 20:17:55 -07:00
Ed Langley 46ad715e3a Merge pull request #162 from fiddlerwoaroof/fix/xg1z-editing-overlay
An editing overlay with no buffer shows the cell (improvise-xg1z)
2026-09-22 20:17:45 -07:00
Ed Langley 5c651a0815 Merge pull request #160 from fiddlerwoaroof/fix/m0sx-clearbuffer
Delete the invented Command::ClearBuffer, and guard the class (improvise-m0sx)
2026-09-22 20:17:35 -07:00
Ed Langley 5252cc0044 Merge pull request #159 from fiddlerwoaroof/fix/b4ks-fmt-workspaces
Split a fmt usage error from a fmt verdict across both workspace roots (improvise-b4ks)
2026-09-22 20:17:23 -07:00
Edward Langley 98bdad02dd chore: update cargo deps 2026-09-22 20:16:46 -07:00
Ed Langley d72a68e2ba Merge pull request #158 from fiddlerwoaroof/docs/kb-review-batch
docs(kb): what adversarial review found, and four vacuity shapes
2026-09-22 20:14:30 -07:00
Edward Langley 6596de6140 The prefix guard refuses a request rather than counting it answered
ChatGPT Codex on PR #161, and the finding was correct. The structural
guard counted `requested(name).is_some()` as "answered", but `chord`
maps `Step::Request(_)` to `None` — so a future prefix binding for
`save` would have PASSED the guard while the request was discarded at
runtime. That is the exact shape improvise-lut7 was filed about: a green
derivation over a broken thing.

The guard now refuses a request-backed prefix name. `prefixed` is split
so the binding step is `chord(state, event, Option<&Binding>)`, which
lets a test drive a binding the shared table does not contain — the
regression the review asked for. It shows the outcome is the disarm
ALONE, and asserts `requested("save").is_some()` first so the guard's
refusal clause is known to be able to fire.

Two faults injected. Binding `save` behind `g` in `default_keymaps` —
the scenario the review described — fails the guard, naming `save`. And
making `chord` return `KeyOutcome::Request(r)` (the review's other
suggested option) fails the new test, because the disarm is then lost:
that is the evidence the alternative trades one stranding for another.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
2026-09-22 20:14:22 -07:00
Ed Langley d99b816e0f Merge pull request #156 from fiddlerwoaroof/fix/ub4o-toggle-group-involution
test(command): the toggle-group law was a tautology, and the law is false (improvise-ub4o)
2026-09-22 20:14:19 -07:00
Ed Langley d0a4534f97 Merge pull request #152 from fiddlerwoaroof/fix/55v2-nonfinite-wire
fix(core): a non-finite cell value crosses the wire, in the spelling the format already uses (improvise-55v2)
2026-09-22 20:14:09 -07:00
Edward Langley 5fa3fed146 Test clause 4 over the Applied::Rebased path
improvise-k84e, scoped to what PR #109 left: a frame whose LoadModel
SUCCEEDS, rebases every mirror and emits Snapshots, and is THEN rolled back
by a later command in the same frame.

The document half was already covered by
a_rolled_back_load_leaves_no_ghost_view_and_no_false_status. This covers the
Sessions half, which nothing touched: mutating_frame clones the whole
Sessions before the batch and undo_frame puts the clone back, so the reseed
is undone with it and the computed Snapshots are discarded rather than sent.

It reproduced nothing — the mechanism is sound, as the bead predicted. Its
value is the fault it can see. Injected: `self.sessions = mirrors;` replaced
by `let _ = mirrors;`. The test failed on the projection assertion, naming
the asker's mirror and printing the whole correcting batch — the restored
document being re-sent to a client left holding the discarded one, which is
a client hearing about a load that never happened.

Three things keep it non-vacuous: a separate control asserting the fixture's
load really does rebase both clients, the generation check made before
anything is concluded from the rollback, and a bystander seated where a
wrong relay could move it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
2026-09-22 20:11:17 -07:00
Edward Langley 3d1430110c Narrow the fmt gate's exit contract to what it actually maintains
ChatGPT Codex on PR #159: both formatter invocations end `|| rc=1`, so any
failure after the rustfmt probe was reported as "misformatted" although no
verdict was produced. Correct, and the narrower form of the bug this gate
exists for.

Measured: an unparseable source exits 1, identical to a diff; an unknown
rustfmt.toml option is only a warning (exit 0).

Narrowed rather than re-classified, because telling a diff from a parse error
means parsing rustfmt's prose, which is the check-adjacent-to-its-subject this
repo keeps getting wrong. The line the split needs is "did the gate run":
exit 1 names a file and blames the tree; exit 2 names none and blames the
invocation, and every exit-2 cause is checked above the loop.

Two more rivals injected: deleting the `cargo fmt --version` probe, and
turning `|| rc=1` into `|| return 2`. The first exposed a vacuous test -- a
cargo stub that failed for every argument also breaks `cargo metadata`, so the
run exited 2 on the metadata arm and the probe could be deleted with the suite
green. The stub now delegates every subcommand but `fmt`, which is the shape a
missing rustfmt component actually has.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
2026-09-22 20:09:04 -07:00
Edward Langley b680523296 An editing overlay with no buffer shows the cell (improvise-xg1z)
`render_model` built `editing` as
`is_editing().then(|| buffers.get("edit").cloned().unwrap_or_default())`,
so an `AppMode::Editing` that arrived without its `SetBuffer` painted
`Some("")` — a blank editor drawn over a cell that still holds a value.
The page opens an `<input>` seeded from that string, so the next Enter
commits the empty string and the value is gone. An ABSENT buffer and an
EMPTY one are two different facts, and `unwrap_or_default` collapsed them.

Reachable because the pair is two `Command`s rather than one: this
client's own resolver emits `SetBuffer` then `ChangeMode`, and a
`Snapshot` or a host batch can carry the mode without the buffer.

The fallback is `improvise_protocol::edit_seed`, which is the EXISTING
home of "what an editor opened here starts with" — `seed_at` at the
cursor, the function `enter-advance` already seeds from. A second
reconciliation of the scroll offset inside `render.rs` would have been
gotcha 42's own trap, so this costs one `pub` rather than a copy.

Two tests, and the second is the one that matters for the shape of the
fix: an empty buffer is a state the user typed their way into with
backspace, so refilling it would make the last backspace un-doable. The
over-broad repair — "empty or absent, refill" — fails that test alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
2026-09-22 20:06:23 -07:00
Edward Langley 6c46502f52 Resolve prefix chords in the browser (improvise-hs0u, improvise-lut7)
`resolve` answered `Binding::Prefix` with `KeyOutcome::Upstream`
unconditionally, and `Host::handle` answers `ClientFrame::Key` with an
explicit empty vec (gotcha 47) — so `g`, `y` and `Z` were black holes and
everything behind them was dead. `grid_move`'s `GridMove::ToStart(true)`
arm was correct and unreachable, so a browser user could reach the last
row and never get back to the first.

Arming is now a `Command::SetTransientPrefix`, which was already on the
wire and already folded by `reduce_view`; nothing composed it. The second
key is resolved in the sub-keymap through `Keymaps::prefix_keymap`,
mirroring `App::dispatch_key` line for line — including that the prefix
is spent whether or not the second key matched, because leaving it armed
strands every key after it in a sub-keymap the user has forgotten about.

Every outcome of a chord therefore carries the disarm, so the second key
can never be `Upstream`: there would be nowhere to put it. The two names
that reach no composition, `wq` and `toggle-col-group-under-cursor`, are
answered by `declined` instead (improvise-rmvx: silence was worse), and
`every_name_bound_behind_a_prefix_is_answered_by_this_client` makes that
structural — a future prefix-bound name nothing answers fails the build.

`p` could never do anything because nothing could put a value in
`ViewState::yanked` (improvise-lut7) — a *state* reason no name-level
derivation can see, which is why paste appeared on neither the declined
list nor the host-needed one. `yank` now composes, off the `edit_text`
the host already projects, which is the value `YankCell` evaluates.

The three prefixes leave `the_command_names_that_still_need_the_host_are_these`.

Not predicted by either ticket: `tests/lockstep.rs` — the instrument for
"host and client agree on a keypress" — passed unchanged through all of
this, because no chord had ever been driven through it. Every key it
pressed was a single key. `the_g_chord_returns_to_the_first_row_on_both_ends`
closes that gap and compares both ends after *each* press.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
2026-09-22 19:59:58 -07:00
Edward Langley 21184bbf64 Delete the invented Command::ClearBuffer, and guard the class
improvise-m0sx. `Command::ClearBuffer(String)` was declared on the wire and
composed by nothing in production: its only occurrences were the enum, the
fold arm, and two of reduce.rs's own tests. Its fold arm removed the buffer
key, while the only two implementations of its own name — the host's
`clear-buffer` command and resolve.rs::write_buffer — both compose
`SetBuffer { value: "" }`, which inserts. So the variant was invented rather
than derived and disagreed with both ends.

Deleted rather than given an effect. Adding a ClearBuffer effect to justify
the variant reverses decision(improvise-cqi, command_set_derived_not_invented),
and it would buy nothing: both readers of the map end in unwrap_or_default(),
so an absent key and an empty one are indistinguishable to every reader.

The guard is the generalisation. vocabulary_is_composed.rs reads the variant
list off serde's derive — the one enumeration the compiler maintains — and
requires each name to be constructed in non-test source, or named in a short
NOT_COMPOSED list with its verdict, checked in both directions.

It found two more orphans nobody had connected to the vocabulary, both
already filed as resolver gaps rather than inventions: SetYanked
(improvise-lut7) and SetSearchMode (improvise-fo0w item 1).

Proved discriminating twice: removing SetTileCatIdx's three production
composers failed the first test naming it, and listing the composed
SetBuffer in NOT_COMPOSED failed the second.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
2026-09-22 19:59:41 -07:00
Edward Langley cb3afb559e Split a fmt usage error from a fmt verdict across both workspace roots
improvise-b4ks. From the repo root `cargo fmt -p opfs-contract -- --check`
exits 1 with "package `opfs-contract` is not a member of the workspace" and
never runs --check, so a caller reading $? cannot tell "misformatted" from
"nothing was checked". Reproduced at 7cef9f8.

scripts/fmt-workspaces.sh discovers every [workspace] root and checks each
with `-p <member>` scoped by that root's own --manifest-path. Exit 2 is a
usage error or an unrunnable cargo; exit 1 is a formatting verdict.

Both obvious spellings were measured and both fail in the direction that
looks like success: `--all` crosses the boundary, because it formats local
path dependencies and harness/opfs-contract depends on improvise-io by path
(an injected fault in src/draw.rs was reported by the harness root); and the
bare --manifest-path form is blind to workspace members, because the repo
root is a package as well as a workspace (an injected fault in
crates/improvise-core exited 0).

scripts/fmt-workspaces-test.sh is the check_the_checker. Four rivals were
injected and each failed it. Its fixture had to be corrected twice -- a
missing path dependency and a virtual outer manifest -- and both vacuities
were found only by injecting the rival, never by reading.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
2026-09-22 19:59:29 -07:00
Edward Langley 29e41a32e0 docs(kb): what adversarial review found, and four vacuity shapes
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
From PRs #143/#146/#147/#150/#152/#156/#157 and the adversarial review of the
first five.

The finding worth the review's cost: `Repr::Nonfinite(&'a str)` could only be
produced by a deserializer that borrows from its input, so the new wire
spelling round-tripped through `from_str` and `from_slice` and nothing else —
the same JSON document escaped as `inf` was refused, and `to_value` did
not round-trip its own output. Both of the PR's tests used `from_str`, which
is precisely the one path that borrows.

Four vacuity shapes, each confirmed by injection rather than argued:

- A disjunctive assertion can accept the bug it guards — one branch here was
  exactly the error message the defect produces.
- A law asserted over an immutable `CmdContext` is a tautology: a `Cmd` is
  pure over its context by construction, so `execute` twice without applying
  effects asserts `f(x) == f(x)` and no rival can fail it. Applying the
  effects REFUTED this one rather than rescuing it.
- A rival must fail the property, not the setup — `AddFormula` and
  `RemoveItem` are both `CellData`, so the obvious test proved the neighbour.
- Every agent appends to the same evidence file at EOF: four conflicts in ten
  PRs. Loud, but the resolution is the hazard, since `--check-evidence` cannot
  see a dropped entry.

kb_consistent: TRUE, 803 facts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
2026-09-22 19:49:23 -07:00
Ed Langley e7de19d011 Merge pull request #147 from fiddlerwoaroof/fix/nzeu-scaling-deser
fix(gp-tree): a deserialized Scaling goes through its one constructor (improvise-nzeu)
2026-09-22 19:43:28 -07:00
Edward Langley 99fda1d2a9 Merge gh/main
Conflict was context/test-evidence/command.md only — the third evidence-file
conflict in this batch, since every agent appends at EOF. Both sides kept and
verified line-by-line against both parents.

1727 passed / 0 failed over 38 binaries.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
2026-09-22 19:42:53 -07:00
Edward Langley f94fbedb85 Merge gh/main; the wire adaptor round-trips through any deserializer
`Repr::Nonfinite(&'a str)` could only be produced by a deserializer that
borrows from its input, so the new wire spelling round-tripped through
`from_str` and `from_slice` and through nothing else. Measured on the
pre-fix head:

    from_str  {"Number":"inf"}       -> Ok
    from_str  {"Number":"inf"}  -> Err   (the SAME JSON document)
    to_value then from_value         -> Err   (does not round-trip its own output)
    from_reader                      -> Err

The escaped form is the serious half: escaping is not a distinction JSON
makes, so the wire accepted a subset of the encodings of the spelling it
claims to share with the file. `Cow<'a, str>` fixes all four; the serialized
form is unchanged.

The PR's own tests could not see this — both use `from_str` exclusively,
which is precisely the one path that borrows. A check adjacent to the
question it names, inside the change that defines the question.

Also tightened `a_string_that_is_not_one_of_the_three_tokens_is_refused`: its
second disjunct accepted `data did not match any variant`, which is exactly
the message the above bug produces, so it passed in both worlds.

Conflict was `context/test-evidence/core.md` only, against #143. Both entries
kept and verified line-by-line — neither test cites the file, so a dropped
entry would pass `--check-evidence`.

Gates on the merge: 1728 passed / 0 failed / 7 ignored over 38 binaries,
clippy -D warnings clean, fmt clean, and `cargo check --target
wasm32-unknown-unknown -p improvise-core` clean — the one gate the review
asked for and could not run.

Found by adversarial review (confirmed by injection, not reasoned).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
2026-09-22 19:41:32 -07:00
Ed Langley 7cef9f8ba0 Merge pull request #157 from fiddlerwoaroof/fix/en4s-formula-panel-item-delete
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
test(project): guard the formula a target ITEM's deletion takes with it (improvise-en4s)
2026-09-22 19:41:00 -07:00
Ed Langley c95ba4a796 Merge pull request #153 from fiddlerwoaroof/docs/kb-tui-and-remotes
docs(kb): the TUI composed-row rule, and a trap in our own remote topology
2026-09-22 19:40:50 -07:00
Edward Langley f187fa57fe Merge gh/main; fix the whitespace run in the refusal message
Conflict was `context/test-evidence/gp.md` only — this PR and #146
(improvise-ipqu) appended at the same point. Both entries kept, verified
line-by-line against both parents, because --check-evidence cannot see a
dropped entry: it checks that every heading names a running test, and neither
of this PR's tests cites the file, so losing one would pass the gate.

The format string was broken across source lines with no `\` continuation, so
eighteen spaces were baked into what a user reads on --predict against a
corrupt model. Found by adversarial review, which printed the message rather
than reading the source.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
2026-09-22 19:39:07 -07:00
Ed Langley e49c4f97bb Merge pull request #146 from fiddlerwoaroof/fix/ipqu-warning-order
test(gp): pin the FitOutcome::warnings order the doc comment and gp.md both state (improvise-ipqu)
2026-09-22 19:38:10 -07:00
Ed Langley 3443ec1646 Merge pull request #150 from fiddlerwoaroof/fix/yss5-date-mismatch-warning
fix(io): a date that does not match the detected format is reported, not dropped in silence (improvise-yss5)
2026-09-22 19:37:49 -07:00
Ed Langley 870322d8d1 Merge pull request #143 from fiddlerwoaroof/fix/a36c-count-exact
test(core): count_aggregation asserts the count, not that it is at least one (improvise-a36c)
2026-09-22 19:37:38 -07:00
Ed Langley 0f74691b97 Merge pull request #155 from fiddlerwoaroof/fix/htl6-view-forward-key
fix(tui): `,` walks the view history forward (improvise-htl6)
2026-09-22 19:35:30 -07:00
Ed Langley 971b432c3b Merge pull request #154 from fiddlerwoaroof/fix/swzk-view-history
fix(tui): view history records a move that happened (improvise-swzk)
2026-09-22 19:34:46 -07:00
Ed Langley 16a18feec9 Merge pull request #148 from fiddlerwoaroof/fix/lm5e-effect-fields
test(cmd): sixteen effect assertions name a field, not just a type (improvise-lm5e)
2026-09-22 19:34:37 -07:00
Edward Langley 5e127fe9bf test(project): guard the formula a target ITEM's deletion takes with it (improvise-en4s)
`Dirty::of(ModelChange::CellData)` reports `formulas: true`. That was deliberate
over-reporting in the cheap direction, costing one redundant `Projection` and
buying nothing — until improvise-g3no made `Model::remove_item` drop the formulas
targeting the item. `effect::RemoveItem` is `CellData`, so a deletion can now
take a formula out of the document, and tightening that arm would leave every
connected client's formula panel naming a formula the document no longer has,
forever, with nothing in the stream to correct it.

`removing_a_category_empties_the_clients_formula_panel` covers the
`LayoutGeometry` twin only. This is its item-level sibling.

The setup is deliberately not a mirror of the sibling's. Written that way —
connect, `AddFormula`, `RemoveItem` — it does fail under the rival, but on the
SETUP assertion, because `AddFormula` is itself `CellData` and with the arm
tightened the formula never reaches the client at all. That answers whether
`AddFormula`'s projection arrived, which is the neighbouring question. So the
formula is added before `Sessions::connect` and the client takes it from the
bootstrap seed, which no `Dirty` gates; the rival then lands on the assertion
that means something. The sibling does not have this problem against its own
rival and is left alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
2026-09-22 19:34:19 -07:00
Edward Langley b1a989d8cf docs(kb): the command lane's doctrine, and a second vector for the base trap
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
From PRs #144/#148/#151. `AGENTS.md`'s remote note gains the worktree-base
vector: a branch cut from the orchestrator's local `main` inherits whatever
that checkout merged but did not push, and nothing in the branch says so —
which is how #148's diff came to name the three orchestrator-only files and
read as a rule violation rather than a base problem.

Four facts. A fold test must fold the batch the producer really emits — a
`Binding::Sequence` flattens into one vec, and a per-step batching cannot see
intra-batch order at all. A type name is not a field, measured: one 14-site
rival left 14 of 16 assertions passing, while an assertion on a *fieldless*
effect is complete rather than smoke. A registry with two arms demotes the
weaker caller, and neither direction is safe by default. And a clean text
merge can delete an evidence entry that no gate checks.

Two gate traps: `--check-evidence` prints the comment cap FIRST, so reading it
through `tail -1` drops the half that was always there; and a compound command
refused by the worktree shell guard runs NEITHER part.

kb_consistent: TRUE, 798 facts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
2026-09-22 19:34:07 -07:00
Edward Langley ee199a1f96 test(command): the toggle-group law was a tautology, and the law is false (improvise-ub4o)
`law_toggle_group_involution` called `execute` twice against the SAME immutable
`CmdContext`, so it asserted `f(x) == f(x)` for a deterministic `f` — an
assertion no rival of any kind can fail. It was vacuous twice over: its fixture
`two_cat_model()` has no groups, so both sides were the empty string.

Applying the effects between the calls, which is what the bead asked for, does
not rescue the law — it refutes it. A collapsed group contributes no
`AxisEntry::DataItem`, and `row_group_for` is indexed by data row, so with two
groups on the row axis and the cursor at row 0: `z` collapses Q1, `z` again
names Q2 (the neighbour, not the group just collapsed), and a third press
returns nothing because the axis has no rows left.

So this replaces the law with three tests of what the command does, each naming
a category and a group by name. Both rivals were run: an `execute` that always
returns `ToggleGroup { Month, Q1 }` — the stateless one the old test exists to
miss — passes the old law and fails two of the three; an `execute` returning
`vec![]` fails all three.

The behaviour those tests now pin is unchanged and is filed as improvise-6qet:
a collapsed group is drawn but not addressable, so there is no way back to it
from the grid. It is the third bead in that family after improvise-lhi4 and
improvise-0o6x, both of which fixed the header's visibility rather than its
addressability.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
2026-09-22 19:26:31 -07:00
Edward Langley 5de61e48f4 Merge remote-tracking branch 'gh/main' into fix/swzk-view-history 2026-09-22 19:26:03 -07:00
Edward Langley e645ec883d fix(tui): , walks the view history forward (improvise-htl6)
`view-forward` was bound in NO keymap, in either front end, from the day it was
written. The TUI reached it as `:view-forward`; a browser could not reach it at
all, because `declined()` refuses to enter CommandMode at the door. It survived
because pressing `<` twice is the only way to observe the forward stack, and
until PR #115 no client maintained one.

THE KEY IS `,`, AND `>` IS WHY THE OBVIOUS ANSWER IS WRONG. `<` is `view-back`
and `>` is `drill-into-cell`, so the pair that LOOKS symmetric is not one: `>`
moves within the grid, `<` moves between views. Binding the forward half to
anything reading as `>`'s partner would deepen the confusion that hid the gap.
`,` is the unshifted twin of `<`, so both halves of the history sit on one
physical key, and it carries no direction-in-the-grid meaning.

IT IS DELIBERATELY NOT A CTRL- KEY. Ctrl-R, the universal "forward through a
history" spelling, is browser reload. The keymap is SHARED between the two front
ends, so a key chosen for the terminal is one the browser must live with, and
improvise-clso is already open for exactly this against Ctrl-S.

The absence assertion PR #117 placed in
`the_forward_step_mirrors_its_cmd_and_no_key_reaches_it` is INVERTED rather than
deleted: renamed `..._and_one_key_reaches_it`, it now asserts the binding exists,
keeps `view-back` as the control, and pins the key itself so the binding cannot
drift while the presence check stays happy.

Reasoning: context/code/tui.md.
Evidence: context/test-evidence/{command,clients}.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
2026-09-22 19:25:18 -07:00
Edward Langley 19eed82e66 Merge gh/main into fix/lm5e-effect-fields
Conflict was context/test-evidence/command.md only: improvise-gjat (PR #144)
and improvise-lm5e appended different sections at the same point. Both sides
kept — the entries are disjoint, 16 from lm5e and one from gjat — and verified
line-by-line that neither side lost content, because a clean-looking textual
resolution is exactly how evidence entries go missing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
2026-09-22 19:15:45 -07:00
Edward Langley b3490da54f Merge remote-tracking branch 'gh/main'
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
2026-09-22 19:14:28 -07:00
Ed Langley 57be9144d1 Merge pull request #151 from fiddlerwoaroof/fix/6uva-script-cursor
fix(cli): a script's command is built against the cursor it will run against (improvise-6uva)
2026-09-22 19:13:58 -07:00
Ed Langley f7573bff27 Merge pull request #144 from fiddlerwoaroof/fix/gjat-sequence-fold
test(navigation): a sequence folds in order; the old law was a tautology (improvise-gjat)
2026-09-22 19:13:40 -07:00