docs(worker): the Key arm's emptiness is now the answer, not the gap

`a_key_resolved_on_the_host_moves_state_no_client_can_hear` still holds
and is still the reason — nothing in the projection stream carries the
host's own `ViewState`. What changed is the conclusion drawn from it:
improvise-bpq2 did not close the divergence by deriving `Command`s from a
`ViewState` delta, it moved resolution to the client, so a key the client
can answer never arrives as a `Key` frame at all. Two doc comments and an
assertion message that read as "not built yet" now say what is settled
and what genuinely remains.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
This commit is contained in:
Edward Langley
2026-09-19 19:34:22 -07:00
parent b1f8b1563c
commit 43422d4d21
+21 -3
View File
@@ -49,8 +49,18 @@
//! would not even scroll to follow. `a_key_resolved_on_the_host_moves_state_no_
//! client_can_hear` demonstrates exactly that against a real `App` and a real
//! `Sessions`, so the gap is recorded as a failing property rather than as a
//! comment. Closing it means deriving `Command`s from a `ViewState` delta,
//! which is improvise-bpq2 and is a ticket, not plumbing.
//! comment.
//!
//! improvise-bpq2 answered it, and **not** by deriving `Command`s from a
//! `ViewState` delta. The user's ruling is that a keymap is UI and the server
//! must not know it, so the resolution moves to the *client*: a key the client
//! can answer arrives here already resolved, as a `ClientFrame::Commands`
//! batch it has folded itself, and this arm's emptiness stops being a gap and
//! becomes the statement. What remains upstream is the set of bindings a
//! client still cannot compose — prefix keys, anything naming a row's category
//! or a group, anything touching `Storage` — and those are closed by enriching
//! the projection, one at a time, not by wiring this arm up. See
//! `improvise-wasm-client::keys`, which carries the list.
//!
//! # Persistence is the worker's, because saving is not on the wire
//!
@@ -510,6 +520,11 @@ mod tests {
/// The `Key` arm answers nothing. Asserted at the worker so that wiring it
/// halfway fails here rather than in a browser.
///
/// Since improvise-bpq2 this is the settled answer rather than a gap: a
/// key the client can resolve never arrives as a `Key` frame at all, and
/// one that does is a binding nothing on this side could answer without
/// diverging — see the next test, which still holds.
#[test]
fn a_key_frame_is_answered_with_nothing_and_moves_nothing() {
let mut worker = worker();
@@ -519,7 +534,10 @@ mod tests {
KeyCode::Char('j'),
KeyModifiers::NONE,
)));
assert!(out.is_empty(), "improvise-bpq2 is not built: {out:?}");
assert!(
out.is_empty(),
"a forwarded key must be answered with nothing, not resolved here: {out:?}"
);
assert_eq!(worker.app().cursor().selected, before);
}