# 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, and ADR-0024 is confirmed fully implemented.** The two items §3 originally flagged are both resolved. The next session's work is the product roadmap in `docs/requirements.md` (reconciled this session) — see §4. ## 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. Flagged items — both now resolved Both items this section originally flagged were ruled on by the user after the main work: **3.1 The `Ranker` type — KEEP.** `completion::Ranker` / `candidates_at_cursor_with` have no production caller passing a non-identity ranker (the candidate-ordering need is met by declaration-order preservation). The user ruled: **keep it** — it is intentional scaffolding for future frequency / content- aware ranking (ADR-0024 §"out of scope" explicitly anticipates this). No longer an open question. **3.2 `CommandNode.hint_mode` — REMOVED.** The per-command `hint_mode` field predated the node-attached HintMode work and was read by nothing. Removed (field + 20 `None` initialisers) in commit `6d2b929`. ## §4. What's next — the standing roadmap **handoff-12 §2's backlog is cleared, and ADR-0024 is confirmed fully implemented** (audited this session — Phases A–F done; Phase F shipped "minimal" with `parser.rs` retained as the router, now recorded in an ADR-0024 implementation note). There is no migration or carry-forward debt left. **The next session's work is the product roadmap, tracked in `docs/requirements.md`** — reconciled this session against what handoffs 10–14 actually built (test baseline refreshed to 1006; `U4` replay marked satisfied; `A1` / `H1a` / `H3` progress notes corrected). `requirements.md` is now the trustworthy "what's open" tracker — read it, not the (coarser) `CLAUDE.md` "Things deliberately deferred" list. Notable open clusters in `requirements.md` (prioritisation is a **user product decision** — do not pick unilaterally): - **Indexes** (`C3` partial) — `add index` / `drop index`, then `EXPLAIN QUERY PLAN` rendering (`QA1`). Self-contained. - **Complex WHERE expressions** (`C5a`, `[~]`) — AND/OR / comparison / LIKE in UPDATE/DELETE/show-data filters. Needs an ADR. The bridge from DSL toward real SQL. - **SQL in advanced mode** (`Q1`/`Q4`, `[~]`) — `sqlparser-rs` + a defined subset. Needs an ADR. - **Snapshot / undo** (`U1`/`U2`) — designed in ADR-0006, not built. (`replay`, `U4`, is now done.) - **m:n convenience** (`C4`), **modify relationship** (`C3a`, `[~]`), **table rename** (`C1`). - **Friendly error layer** (`H1`) — partial; full SQL→English translation pending. **Syntax-help in parse errors** (`H1a`) — piecemeal so far. - **Session log + Markdown export** (`V4`, `[~]`), **multi-line input** (`I1`), **readline shortcuts** (`I1b`), **seeding** (`SD1`), **CI** (`TT5`), **tutorial system** (`TU1`, `[~]`). Two handoff-13 items the user already **accepted** (not work, just recorded): - **Partial entry words classify as `DefiniteErrorAt`** (handoff-13 §3) — documented by the matrix test `app_commands::partial_entry_word_classifies_as_definite_error_but_completes`. - **Matrix scope** (handoff-13 §4) — cursor coverage is "meaningful transitions" not every byte offset; assertion (5) is parse-layer, not a live dispatch differential. ## §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` — set on `Hinted` entry, cleared on any match. ### Walker driver - `walk_node` split into a wrapper (clears `pending_hint_mode` on match) + `walk_node_inner` (the dispatch). - `resolve_dynamic` + `DYNAMIC_CACHE` — memoized `DynamicSubgrammar` resolution. - `Node::Lookahead` arm — 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`. - `runtime::build_schema_cache` (extracted from `refresh_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). ### Post-handoff cleanup (this section's work) After the eight items above, three follow-ups landed: - `CommandNode.hint_mode` field removed (commit `6d2b929`) — see §3.2. - ADR-0024 amended with a "Phase F minimal" implementation note (`parser.rs` retained as the router); `docs/adr/README.md` index line updated to match. - `docs/requirements.md` reconciled — see §4. ## §6. How to take over 1. **Read this file, then handoff-13, then 12** for the chain. 2. **Read `CLAUDE.md`** — the working-style rules. This session escalated every ambiguous fork (HintMode mechanism, the `Box::leak` arena's true cost, the Form C restructure twice) rather than deciding unilaterally. 3. **Read `docs/requirements.md`** — reconciled this session; it is the authoritative "what's open" tracker (§4). 4. **Run `cargo test`** — 1006 passing, 0 failing, 1 ignored. 5. **Run `cargo clippy --all-targets -- -D warnings`** — clean. 6. **Pick the next work from §4 / `requirements.md`** — but prioritisation is a user product decision; ask, don't assume. handoff-12's backlog and ADR-0024 are both fully done — there is no carry-forward debt. ### 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 `); a failing cell with *wrong* behaviour → the cell earned its keep. The Form C type-awareness work this session was guarded entirely by it.