Files
improvise/context
Edward Langley 81fe546594 Settle the mode by observing the grid, not at each composition site
ChatGPT Codex's P1 on #98: switch_view_at composes
[SwitchView(target), ChangeMode(Normal)], and changed_mode settled that
against the grid the client STILL HOLDS -- the outgoing view's. Records ->
pivot emitted RecordsNormal and pivot -> records emitted Normal, which is
the cross-view drift this PR exists to close, by a second door.

The hand-back predicted this as follow-up 3 and called it unreachable
because R is a stranding decline in the browser. That is right about the
TOGGLE route and wrong about this one: records-ness is auto-detected from
the axes (gotcha 1), so a view switch changes it with nothing in the batch
saying "records". One door was enumerated and there were two.

So the remedy is general rather than a patch to switch_view_at.
changed_mode is DELETED; every site emits the plain AppMode::Normal; and
ClientState::fold settles after every fold against the grid it HOLDS.
Route-independent, so no enumeration of composition sites is load-bearing
any more -- the same shape the repo already prefers in cursor_epoch over
matching on CycleAxis, document_generation over matching on LoadModel, and
the dirty flag over a table of command variants.

improvise_protocol::settled_mode is the one rule and all three ends call
it: ClientState::fold, ClientSession::fold (the server's mirror, so the two
cannot diverge in a field nobody currently reads today) and
App::settle_normal_mode. App::normal_mode is gone -- it was that
function's host-side duplicate, and my change orphaned it.

Bidirectional coverage drives a real Host: a session connected to a
records-mode host, the pair folded locally and sent as a
ClientFrame::Commands, every reply delivered in arrival order. The
composed ChangeMode carries the STALE answer deliberately, because
composing the friendly value would let the records -> pivot direction pass
under the fault. Both directions fail with settle_mode no-opped, each
proved separately since the loop stops at the first.

Evidence, the injections and why the first fix's unreachability argument
was too narrow: context/test-evidence/clients.md, context/code/protocol.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
2026-09-21 10:44:17 -07:00
..