10 KiB
Session handoff — 2026-05-15 (14)
Fourteenth handover. This session cleared the remaining handoff-12 backlog: every §2.1 carry-forward and the §2.2 deferred items. Eight focused commits, each a deliberate decision the user signed off on.
Headline: the handoff-12 §2 catalogue is now empty of actionable items. What remains are two flagged observations (§3) the user should rule on, plus the standing ADR roadmap.
State at handoff
Branch: main. Working tree clean. origin/main is at
42cf851 (handoff-13); local HEAD is 8 commits ahead — this
session's work, unpushed (the user pushes asynchronously).
Commits since handoff-13:
50b7825 Remove dead parse.token.* catalog entries
bcc5ad2 Matrix: pin natural candidate ordering
f1ff597 Hint: pedagogical Form-A pointer at Form B's first value slot
911a537 Walker: node-attached HintMode via Node::Hinted
9bbb96e Walker: memoize DynamicSubgrammar resolution to bound the Box::leak
90e3f5d Insert grammar: Form C type-awareness via lookahead
f46606b Runtime: schema-aware replay parsing
03dd900 Help: consume CommandNode.help_id — REGISTRY-driven in-app help
Tests: 1006 passing, 0 failing, 1 ignored (up from 989).
The ignored test is the long-standing ```ignore doc-test
in src/friendly/mod.rs.
Clippy: clean with nursery lints + -D warnings.
§1. What shipped — handoff-12 backlog cleared
Dead parse.token.* catalog entries removed (50b7825)
The 5 structural-class + 3 lex-error entries handoff-12 §2.1
listed as unreachable are gone (catalog YAML + keys.rs).
Ranker / natural candidate ordering (bcc5ad2)
The user's actual ranker need — to before table so
add column to table T reads in order; keywords before schema
identifiers — already worked via declaration-order
preservation + keywords-first sectioning in
candidates_at_cursor. Nothing pinned it; 8 matrix tests in
tests/typing_surface/candidate_ordering.rs now do. See §3 for
the Ranker type itself.
serial/shortid pedagogical Form-A hint (f1ff597)
handoff-12 §2.2: at the first value slot of insert into T values (…) for a table with auto-generated columns, the hint
now appends "(id auto-generated — skipped here; list columns
explicitly … to set it)". hint_resolution_at_input derives
the skipped columns from the post-walk WalkContext (Form B =
no user_listed_columns + table has serial/shortid columns);
the note fires only at the first slot. New
HintResolution::form_b_autogen_skipped, catalog key
hint.value_slot_autogen_skipped.
Node-attached HintMode (911a537)
handoff-12 §2.1: the hint resolver's signature-matching (does
the expected set contain all five literal forms? an
Ident{NewName}?) is replaced by a grammar-declared
annotation. New Node::Hinted { mode, inner } wrapper; the
walker records the mode in WalkContext::pending_hint_mode on
entry and clears it on any successful match (the cursor
moved past the slot — this also undoes the leak where a failed
Hinted branch of a Choice would strand a stale mode). The
resolver reads pending_hint_mode directly.
Mechanism note: handoff-12 sketched threading HintMode through
the Expectation enum. ADR-0024 §HintMode only says "nodes
carry HintMode, the walker propagates it" — mechanism-
agnostic. The WalkContext::pending_hint_mode route (mirroring
the existing pending_value_type) was chosen as lower-risk; the
user was told and did not object.
DynamicSubgrammar memoization (9bbb96e)
handoff-12 §2.1's Box::leak-per-walk. The handoff's arena
sketch was unworkable (it needs a lifetime-generic Node — a
major refactor). Instead resolve_dynamic memoizes factory
output on the schema state the factory reads (keyed by factory
fn-pointer + ctx fields). Each distinct value-list shape leaks
once — total leak bounded by distinct (schema × form)
combinations, not keystroke count. TableColumn gained Hash.
Form C type-awareness (90e3f5d)
handoff-12 §2.2. Form C (insert into T (vals)) shared the (
opener with Form A, so its values weren't typed. The
explicit-Choice-branch split is impossible (committed-choice
semantics commit after ( matches), so a new
Node::Lookahead(fn(&WalkContext, &str, usize) -> Node)
variant peeks the source: a value-literal first token routes
the paren through the typed column_value_list (Form B
dispatch contract); an identifier or empty paren routes to a
Form A column-name list. Form C values are now type- and
count-checked at parse time. insert into T ( cleanly shows
Form A column candidates instead of mixed Form-A/C suggestions.
Schema-aware replay (f46606b)
handoff-12 §2.1: run_replay parsed schemalessly. It now
re-snapshots the schema per line (extracted build_schema_cache,
shared with the interactive path) and parses with
parse_command_with_schema — typed-slot rejections fire at
replay parse time, matching interactive. New integration test
replay_rejects_typed_slot_violation_at_parse_time.
help_id consumption (03dd900)
handoff-12 §2.1: every CommandNode declared an unused
help_id. note_help now iterates the command REGISTRY and
translates each help_id — a new command appears in help
automatically. 20 per-command catalog entries + 3 framing
entries; help.in_app_body removed. CommandNode.help_id lost
its #[allow(dead_code)].
§2. Bug found this session
libyml 0.0.5 scanner panic on long space runs in
double-quoted YAML scalars. While authoring the help entries,
a space-aligned double-quoted catalog string
("quit — exit") panicked the YAML scanner with
"String join would overflow memory bounds". Block scalars
(|-) are unaffected — that's why the old block-scalar help
worked. Bisected and worked around: all per-command help
entries use |-. If you author new catalog entries, avoid
long internal space runs in double-quoted ("…") values — use
a block scalar or keep runs short. A catalog comment in
en-US.yaml records this.
§3. CRITICAL: two flagged items needing a user decision
3.1 The Ranker type is vestigial. completion::Ranker /
candidates_at_cursor_with have no production caller passing a
non-identity ranker. The user's stated ranker need (candidate
ordering) is met by declaration-order preservation, not the
ranker layer. So Ranker, identity_ranker, and the
candidates_at_cursor_with variant are unused scaffolding. Per
CLAUDE.md "don't remove without confirmation" they were left
in. Decide: remove them, or keep for a future frequency-
ranking feature? (handoff-12 §2.1 listed the ranker as
"scaffolding-only … future work" — this is the same item, now
confirmed genuinely unused.)
3.2 CommandNode.hint_mode is now genuinely dead. The
per-command hint_mode: Option<HintMode> field predates the
node-attached HintMode work; HintMode is now per-node
(Node::Hinted), never per-command. The field is still
#[allow(dead_code)] and read by nothing. Removing it is a
safe mechanical edit across the 20 CommandNode declarations.
Decide: remove it, or keep? Not done this session (20-site
edit, separate from the HintMode mechanism change).
§4. Open items — standing roadmap (unchanged)
handoff-12 §2's actionable backlog is cleared. What remains is
the ADR roadmap in CLAUDE.md "Things deliberately deferred"
(complex WHERE expressions, SQL advanced mode, indexes, m:n
convenience, snapshot/replay/undo, tutorial system, etc.) and
handoff-13's two accepted items:
- Partial entry words classify as
DefiniteErrorAt(handoff-13 §3) — the user accepted this; the matrix testapp_commands::partial_entry_word_classifies_as_definite_error_but_completesdocuments it. - Matrix scope (handoff-13 §4) — cursor coverage is "meaningful transitions" not every byte offset; assertion (5) is parse-layer not a live dispatch differential. User accepted both.
§5. Architectural delta (vs. handoff-13)
New Node variants
Node::Hinted { mode: HintMode, inner: &'static Node }— node-attached hint-mode annotation.Node::Lookahead(fn(&WalkContext, &str, usize) -> Node)— source-aware dynamic subgrammar (Form A/C discrimination).
New WalkContext field
pending_hint_mode: Option<HintMode>— set onHintedentry, cleared on any match.
Walker driver
walk_nodesplit into a wrapper (clearspending_hint_modeon match) +walk_node_inner(the dispatch).resolve_dynamic+DYNAMIC_CACHE— memoizedDynamicSubgrammarresolution.Node::Lookaheadarm — not memoized (source-dependent), returns a small node.
New API surface
input_render::classify_input_with_schema(added handoff-13, noted here for completeness).HintResolution::form_b_autogen_skipped: Vec<String>.runtime::build_schema_cache(extracted fromrefresh_schema_cache).
Catalog
- Removed:
parse.token.*(×8),help.in_app_body. - Added:
hint.value_slot_autogen_skipped,parse.custom.insert_form_a_missing_values(handoff-13),help.intro/help.dsl_section/help.types_reference,help.{app,ddl,data}.*(×20).
§6. How to take over
- Read this file, then handoff-13, then 12 for the chain.
- Read
CLAUDE.md— the working-style rules. This session escalated every ambiguous fork (HintMode mechanism, theBox::leakarena's true cost, the Form C restructure twice) rather than deciding unilaterally. - Run
cargo test— 1006 passing, 0 failing, 1 ignored. - Run
cargo clippy --all-targets -- -D warnings— clean. - Resolve §3 — the two flagged dead-code items — with the user.
- Then the standing ADR roadmap (§4) is the next structural work; pick per the user's priorities.
Note on the typing-surface matrix
tests/typing_surface/ (now 144 cells) is the regression net
for everything walker/hint/completion. After any grammar or
walker change: a failing matrix cell with correct new
behaviour → update its snapshot
(INSTA_UPDATE=always cargo test --test typing_surface_matrix <family>); a failing cell with wrong behaviour → the cell
earned its keep. The Form C type-awareness work this session
was guarded entirely by it.