# Session handoff — 2026-05-12 (7) Seventh handover. The previous session (handoff-6) shipped ADR-0019 (friendly error layer + i18n catalog). This session designed and **fully implemented ADR-0020 (tokenization layer) and ADR-0021 (per-command usage templates / H1a)**, then designed and **shipped all eight stages of ADR-0022 (ambient typing assistance)** — colour highlighting, error overlay, hint panel, non-modal Tab completion with cycling, schema cache, identifier completion, invalid-identifier detection. Four rounds of real-app testing produced follow-up fixes. The next agent picks up a clean baseline with no design deferrals or known bugs — only a partial test pass from the user that may turn up further findings. ## State at handoff **Branch:** `main`. Working tree clean. **Up to date with `origin/main` at `c247f55`** — the user pushed along the way, so there is no in-flight commit queue to publish. Verified via `git status` at handoff time (the per-message "X commits ahead" lines earlier in the session were stale snapshots — do re-check `git status` rather than trust running totals). Commits since handoff-6 (in chronological order, 26 total): ``` 857ee75 ADR-0020 + ADR-0021: tokenization layer and parse-error pedagogy (H1a) fdaf7e3 ADR-0020 implementation: lexer + parser refactor over &[Token] 11071ae ADR-0021 implementation: per-command usage templates in parse errors f0632af ADR-0022: ambient typing assistance (unifies I3 + I4) 00c9dea ADR-0022 stage 1/8: theme token-class colour fields cafc455 ADR-0022 stage 2/8: input panel — token-class highlighting 39da399 ADR-0022 stage 3/8: simple-mode echo lines highlighted 313d4f8 ADR-0022 stage 4/8: render-time parse + error overlay 9c4857e ADR-0022 stage 5/8: hint panel ambient typing assistance 6845df1 ADR-0022 stage 6/8: IdentSlot taxonomy + parser audit aea3224 ADR-0022 stage 7/8: schema query plumbing 06e8d1e ADR-0022 stage 8a: non-modal keyword completion + Esc/Backspace undo faebeed ADR-0022 stage 8b: hint panel candidate list with scroll markers 51a8d9a ADR-0022 stage 8c: IdentSlot propagation + SchemaCache API 7a32c13 ADR-0022 stage 8d: schema cache refresh wiring 8214e41 ADR-0022 stage 8e: invalid-identifier detection + hint variant bd1cce6 ADR-0022 stage 8 follow-up: fixes from real-app testing f94a999 ADR-0022 stage 8 follow-up r2: completion UX fixes from real testing 22119d6 ADR-0022 follow-up r3: identifier colour, NewName hint, "Next:" wording, "type" label c247f55 ADR-0022 follow-up r4: column-type completion ``` **Tests:** **760 passing, 0 failing, 1 ignored** (up from 610 at handoff-6's baseline; +150 over this session). The ignored test is the same `\`\`\`ignore` doc-test in `src/friendly/mod.rs` that was already ignored at handoff-6 — not new debt. Per-area test deltas: - ADR-0020 (lexer + parser refactor): +40 (29 lexer + 11 keyword/punct) - ADR-0021 (per-command usage): +18 (8 usage + 1 token-vocab completeness + 9 parse-error-pedagogy integration tests) - ADR-0022 stages 1-8e: +37 (4 theme + 9 input_render core + 5 input_render ambient + 5 db schema-cache + ~14 completion + others) - ADR-0022 round-1 follow-up: +3 - ADR-0022 round-2 follow-up: +3 - ADR-0022 round-3 follow-up: +5 - ADR-0022 round-4 follow-up: +5 **Clippy:** clean with `nursery` lints enabled. **Release build:** not measured this session; the existing ~7.8 MB binary will grow modestly from the new modules (completion, input_render, keyword, lexer, usage, ident_slot) but they're all hand-rolled, no new dependencies. ## What's implemented (delta vs. handoff-6) ### ADR-0020 — tokenization layer for the DSL parser Amends ADR-0001. Adds a lexer between the source string and the chumsky grammar; chumsky now parses `&[Token]` instead of `&str`. What it provides: - **`src/dsl/lexer.rs`** — tokenizer producing `Vec` with byte-offset spans. Always succeeds: lex-shape errors (unterminated string, unrecognised character, malformed flag) embed as `TokenKind::Error(_)` tokens in the stream rather than as a `Result` variant. Rationale: I4 (syntax highlighting) needs to render partial / invalid input uniformly; the parser sees `Error` tokens and raises a structural error at that point. - **`src/dsl/keyword.rs`** — `Keyword` and `Punct` enums declared via `define_keywords!` / `define_punct!` `macro_rules!` invocations. Single source of truth: the enum, the lex-side string→variant mapping, the variant→literal rendering, and the `parse.token.keyword.*` / `parse.token.punct.*` catalog-key derivation all come from one declaration block. Adding a keyword is one line of Rust + one line of YAML. - **Parser refactor.** All combinators in `src/dsl/parser.rs` rewritten to operate on `&[Token]`. Keyword aggregation across `choice` now works natively ("expected `data` or `table`" instead of just "expected `table`"). Custom `try_map` content errors (unknown type, mutually-exclusive flags, "with pk needs at least one column", "specified twice") survive unchanged. - **`replay` bare-path UX preserved** via a one-place source-slice special case (ADR-0020 §6): after matching `Keyword(Replay)`, the parser reads the remainder of the source string directly rather than reassembling tokens. - **I3 / I4 hooks committed at the parser level**: lexer always produces a stream; parser's expected-token-set is queryable. - **Honest history note** in the ADR: the no-lexer shape in `dsl/parser.rs` arose incrementally without ADR-level deliberation against the known I3/I4/H1a requirements. ADR-0020 corrects that. ### ADR-0021 — parser-as-source-of-truth for H1a Builds on ADR-0020. Pulls forward H1a from `requirements.md`. What it provides: - **`src/dsl/usage.rs`** — per-command `UsageEntry` registry keyed off entry-keyword. Multi-entry families (`add`, `drop`, `show`) return multiple keys; unique-entry keywords return one. `matched_entry(tokens, failure_position)` resolves the entry keyword from the consumed prefix. - **Catalog sections** under `parse.usage.` (templates) and `parse.token.{keyword,punct, identifier,...}` (single-token vocabulary). - **Renderer (in `app.rs::dispatch_dsl`)** composes three blocks: caret + structural/custom error + usage template (or "available commands:" fallback when no entry keyword was consumed, e.g. `frobulate Customers`). - **`tests/parse_error_pedagogy.rs`** — 9 integration tests covering create/add/drop/show/frobulate/update/ insert pedagogy cases. - **Anchor-phrase compliance preserved** (ADR-0019 §10): existing tests assert on substrings like "no such table", "already exists"; unchanged. ### ADR-0022 — ambient typing assistance (the big one) Unified design that subsumed the originally-planned separate ADRs for syntax highlighting (I4) and tab completion (I3). The framing: colour, hint panel, and Tab are three answers to the same question — what does the user need to know mid-typing? — and planning them separately produces three loose pieces that drift apart. What it provides (eight implementation stages + four testing-round follow-ups): - **Token-class colouring** in the input pane and simple-mode echo history (stages 1-3). Theme gains seven `tok_*` fields. Identifier colour was tuned twice from user feedback; final values are `#56B6C2` (dark) / `#0F6B76` (light) — vivid teal to make identifiers stand out. - **Render-time parse + error overlay** on the failing token (stage 4). `ParseError::Invalid` gained an `at_eof: bool` flag; custom errors map to `at_eof = true` conservatively to avoid over-highlighting. - **Hint panel ambient mode** (stage 5) — sub-states Valid/IncompleteAtEof/DefiniteError/InvalidIdent/ TypingName. Three-block composition (caret + error + usage) when relevant. - **Identifier-slot taxonomy** (stage 6): `IdentSlot` enum (`NewName`, `TableName`, `Column`, `RelationshipName`) with `expected_label()` / `from_expected_label()` round-trip. `ident_ctx(slot)` wrapper labels each call site; every `ident()` in the parser was audited and tagged. - **Schema query plumbing** (stage 7) — `Database::list_names_for(slot)` worker request. - **Schema cache refresh wiring** (stage 8d) — `AppEvent::SchemaCacheRefreshed`; runtime posts at every TablesRefreshed site (project load, post-DDL, rebuild, replay). - **Non-modal Tab completion** (stage 8a): - **Single candidate** → insert with trailing space, no memo. Chains naturally: `a` Tab → `add `, Tab → `add column `. - **Multi candidate** → insert WITHOUT trailing space, create memo. Tab cycles forward (Shift-Tab backward, starting from last); space (or any non-Tab key) clears the memo and inserts itself naturally, producing the final ` ` form. - **Esc/Backspace while memo alive** → restore original text + cursor in one keystroke. The user's preference for symmetric insert-and-undo is baked in. - **Multi-candidate hint panel** (stage 8b) with `<` / `>` scroll markers when items overflow; selected item bold. Kind-coloured (keyword purple, identifier teal). - **Sticky hint during cycling** — while the memo is alive, the hint shows the memo's candidate list with the new selection, NOT what comes next at the post-Tab cursor. - **Grammar-order ordering** — keywords come first in chumsky's source-order traversal (so `to` before `table` for `add column [to] [table] …`), then schema identifiers alphabetised. Achieved by dropping a sort in `describe_expected` rather than alphabetising blindly. - **Invalid-identifier detection** (stage 8e) — when the user types text that doesn't prefix-match any schema entry at a known-set slot, render red overlay + "no such {kind}: `{found}`" hint. - **NewName slot probe** (round 3) — at user-invents-the- name positions, the hint reads "Type a name, then `(`" (or whatever follows) instead of the technical "next: …" that would surface once the partial identifier got consumed. The "what follows" is computed by re-parsing with a single-letter placeholder identifier substituted at the cursor. - **Type-name completion** (round 4) — `Type::all()` surfaces as Tab candidates at `(` slots, filtered by partial prefix, in declaration order (text/int/real/decimal/bool/date/datetime/blob/serial/ shortid). Type names live outside the Keyword enum (ADR-0020 §2 keeps them as identifiers validated by `Type::from_str`) so they needed their own completion path via the `TYPE_SLOT_LABEL` constant. - **"Next:" wording** instead of "expected:" — friendlier framing. Hint sentences are now capitalised (Submit/Next/Type/No such). ## Open testing work — picks up next session The user has noted that every testing round has turned up issues, and the next session begins with another testing pass. Issues found and **fixed** so far (rounds 1-4): 1. **r1**: hint ordering (keywords alphabetised vs. grammar order); hint-panel kind colouring; `tok_identifier` equal to `theme.fg` (no contrast). 2. **r2**: stuck-on-unique completions (memo created for single candidate caused Tab to no-op visibly); grammar order regression (alphabetical sort was burying chumsky's source order). 3. **r3**: identifier colour still too subtle; "expected: …" reading as a leaked diagnostic; "Next: something else" after `(` (unlabelled `type_keyword`); typing into a NewName slot showed the post-consume "expected: `(`" prose. 4. **r4**: column-type completion entirely missing — type names aren't in the Keyword enum so the completion engine never saw them. The fixes from each round are described in the matching commit messages. **Nothing is currently outstanding from the user's reports** — but the user has explicitly noted that they will run another testing pass and expect to find more, so the next session should: 1. Do nothing structural until the user has tested. Wait. 2. Expect findings to be small surface-level UX issues like the previous rounds (wording, ordering, colour adjustments, edge cases in completion / hint panel / render). 3. Approach each finding by tracing the symptom to the minimum-viable layer (completion engine, `ambient_hint`, render-time classifier, parser labelling, catalog wording), then applying the fix there. ### Known surface-level wart (not user-reported but visible) The `create table Customers ` case still shows the lowercase custom-error wording "tables need at least one column. Add `with pk`..." in the hint panel — because that message is hand-written in `parser.rs` source code rather than coming through the catalog. Capitalising it needs the user's sign-off (rewriting a parser error string). Flagged in commit `22119d6`'s message. ## Recommended next move (after testing) ### Push to test the still-quiet areas What hasn't been tested yet in user-facing terms: - **Shift-Tab cycling** (only forward-Tab has been exercised in the user's reports). - **Esc / Backspace undo** of a multi-candidate Tab (memo path). - **Cursor-in-the-middle-of-input completion** (most tests have been cursor-at-end). The completion engine handles mid-input but it hasn't been driven through the TUI by the user. - **Long candidate lists** that trigger the `<` `>` scroll markers — schema with many tables. - **Multi-byte UTF-8** in string literals + identifiers (the lexer handles this but it hasn't been driven from the keyboard). - **Advanced mode** behaviour — should be plain-text render with no hint panel content, no Tab completion. Confirmed by code but not user-tested in this session. - **Replay** with bare paths and quoted paths, particularly the path special-case (ADR-0020 §6) under the new lexer. - **Interaction with modals** — should completion be suppressed when a modal is open? Code currently short-circuits via `if self.modal.is_some() return handle_modal_key(key)` BEFORE the completion match arms, so completion is correctly suppressed. ### After testing — three sizeable pieces in priority order 1. **A1 (CI workflow)**. Has been the "easy quick win" for several sessions now and hasn't shipped. Standard GitHub Actions YAML at `.github/workflows/ci.yml`; cross-platform Linux / macOS / Windows; `cargo test` + `cargo clippy --all-targets -- -D warnings`. Locks in the 760-test green baseline. 1-2 hours. 2. **Query DSL ADR + implementation**. Biggest remaining design piece. Discussed in handoff-6: extend `show data` into a SELECT-style command with WHERE / projection / order; expose generated SQL as a pedagogical hook; bundle C5a's complex WHERE into one coherent feature. Then QA1 (EXPLAIN QUERY PLAN) becomes meaningful. Probably 600-1000 lines of ADR + 800-1500 of implementation. 3. **Constraint management surface (C3)**. UNIQUE / CHECK / NOT NULL DDL operations. The friendly-error layer (ADR-0019) has CHECK wording ready; the missing piece is the DDL surface itself. ### V-series UX projects Still pending from handoff-6: - V4 — session log + Markdown export. - V1/V2 — relationship rendering (the "two structures + arrow" view). - V3 — ER diagram export. ### Smaller items still on the table - I1 — multi-line input (Enter inserts newline, Ctrl-Enter submits). - I1b — readline shortcuts (Ctrl-A/E, Ctrl-W/K/U). - C4 — m:n convenience (auto-junction-table). ### Tracked but bounded to other ADRs - Q1 (SQL handling in advanced mode) — waits on Q4 (SQL subset ADR), which itself waits on the lexer model for advanced-mode tokenization. Now that ADR-0020 has established the DSL-side lexer, the Q4 ADR can decide whether to share / wrap `sqlparser-rs`'s tokens or add a parallel SQL lexer. - U-series undo/snapshot. - Settings persistence — feeds the deferred `messages` persistence and (when it arrives) any user-configurable theme picker. ## Sharp edges and subtleties (delta vs. handoff-6) Carried-over edges still apply. New ones this session: - **The parser combinators are now `Parser<'a, &'a [Token], …>` instead of `Parser<'a, &'a str, …>`.** Helper combinators in `dsl/parser.rs`: `kw(Keyword)`, `punct(Punct)`, `ident_inner()` / `ident_ctx(IdentSlot)`, `number_literal()`, `string_literal()`, `string_payload()`, `flag(name)`. All defined inline at the top of the parser combinator section. - **`ident_inner` is the only call that produces a bare identifier match.** Every command parser combinator must use `ident_ctx(slot)`. There is no compile-time enforcement (`ident_inner` is `fn` not a sealed type), only convention + code review. - **`ParseError::Invalid` gained two fields**: `at_eof: bool` (stage 4) and `expected: Vec` (stage 5). Pattern matches with `..` are unaffected; the two constructions in `dsl/parser.rs` populate both correctly. `at_eof` for custom errors is conservatively `true` — known limitation, noted on the field's docstring. - **Type names are NOT keywords.** They lex as `Identifier` tokens; the parser's `type_keyword()` helper uses `Type::from_str` to classify, which emits the existing "unknown type 'X' (expected one of: …)" custom error on miss. The completion engine recognises the `"type"` label (from `ident_inner().labelled("type")` inside `type_keyword`) via the `TYPE_SLOT_LABEL` constant in `completion.rs`. Adding a new type means: add the variant + keyword to `Type::all()` (ADR-0005), and the completion / error paths pick it up automatically. - **The lexer eats whitespace.** `padded()` combinators are gone from the parser entirely. Whitespace positioning between tokens is recoverable from token spans if anyone needs it. - **`replay` parses via a special-case** in `try_parse_replay_with_bare_path` (before the chumsky parser runs). Quoted paths go through chumsky; everything else is source-sliced from the byte after `replay`. Documented in ADR-0020 §6. - **The hint panel render now branches** on the `AmbientHint` enum: `Prose(String)` or `Candidates { items, selected }`. The renderer in `ui.rs::render_candidate_line` builds spans with per-candidate kind colouring and `<` / `>` scroll markers when overflowing. - **The schema cache refresh fires from the runtime**, not from the App. App receives `AppEvent::SchemaCacheRefreshed(cache)` and stores it. Refresh sites: project load, after DDL, after rebuild, after replay. Best-effort: a failed `list_names_for` for one slot kind leaves that field empty in the cache rather than dropping the whole refresh. - **Tab/Shift-Tab key handling is at the TOP of `handle_key`** — before the modal short-circuit's match arm and before the regular key matcher. The ordering is: `if modal { handle_modal_key }; match Tab/BackTab/Esc-with-memo/Backspace-with-memo { … }; match (the existing key matcher) { … }`. Esc / Backspace match arms have `if self.last_completion.is_some()` guards so they fall through to normal behaviour when no memo is alive. - **`completion::candidates_at_cursor` is sync.** It consults the in-memory `SchemaCache` rather than the worker thread. The cache may be slightly stale between a DDL command and its `SchemaCacheRefreshed` event; acceptable per ADR-0022 §9. - **`NewName` slots return `&[]` from `SchemaCache::for_slot`** — the user invents these names. The `typing_name_at_cursor` function handles them with a friendlier hint ("Type a name, then …") via a placeholder-substitution re-parse. - **The `TYPE_SLOT_LABEL = "type"` const** in `completion.rs` must equal what `dsl::parser::type_keyword` labels its `select_ref!` with. The strings are physically separate; no compile-time link. If a future maintainer changes one, the other must change too. - **The macro-generated `Keyword::ALL` and `Punct::ALL` arrays** are the canonical iteration source for catalog-validity tests. A new keyword or punct that forgets a YAML entry under `parse.token.*` fails the `keys_validate_against_catalog` test loudly. ## ADR index (read these before touching the related areas) ``` 0000 Record architecture decisions (process) 0001 Language and TUI framework (Rust + Ratatui) — amended by ADR-0020 (adds tokenization layer) 0002 Database engine (User-facing posture) 0003 Input modes and command dispatch 0004 Project file format (amended by 0015) 0005 Column type vocabulary 0006 Undo snapshots and replay log 0007 Sharing and export (amended by 0015 amendment 1) 0008 Testing approach 0009 DSL command syntax conventions 0010 Database access via worker thread 0011 FK column type compatibility 0012 Internal metadata for user-facing column types 0013 Relationships, naming, and rebuild-table strategy 0014 Data operations, value literals, and auto-show 0015 Project storage runtime 0016 Pretty table rendering for data and structure views 0017 Column type-change compatibility 0018 Auto-fill contracts for serial and shortid columns 0019 Friendly error layer (H1) and i18n message catalog 0020 Tokenization layer for the DSL parser — IMPLEMENTED (this session). Amends ADR-0001. 0021 Parser-as-source-of-truth for H1a — IMPLEMENTED (this session). Builds on ADR-0020. 0022 Ambient typing assistance (I3 + I4 unified) — IMPLEMENTED through stage 8e + 4 testing rounds. ``` ## Repository layout (delta vs. handoff-6) ``` src/ completion.rs — NEW. SchemaCache, Candidate, CandidateKind, Completion, LastCompletion structs. candidates_at_cursor, invalid_ident_at_cursor, typing_name_at_cursor. input_render.rs — NEW. StyledRun, AmbientHint, InputState. render_input_runs (token-class colour + parse- error overlay + invalid-ident overlay + cursor injection). ambient_hint (the resolution ladder: memo → candidates → invalid_ident → typing_name → parse-prose fallback). classify_input, lex_to_runs. dsl/ ident_slot.rs — NEW. IdentSlot enum + expected_label / from_expected_label round-trip. keyword.rs — NEW. define_keywords! / define_punct! macros + ALL static tables. lexer.rs — NEW. Token, TokenKind, LexError, Span. lex() always succeeds; embeds Error tokens in the stream. parser.rs — Heavily refactored. Parser now operates on &[Token]. ident_ctx(slot) wrapper. ParseError gained at_eof + expected fields. usage.rs — NEW. UsageEntry registry + matched_entry + entry_keywords_alphabetised. app.rs — Tab/BackTab/Esc-with-memo/ Backspace-with-memo handlers. completion_tab_forward / _backward, start_or_complete_at / _last, commit_unique / commit_multi, replace_inserted, undo_last_completion. AppEvent::SchemaCacheRefreshed handler. last_completion: Option<…>, schema_cache: SchemaCache fields on App. render path in dispatch_dsl composes the three-block parse error per ADR-0021 §2. ui.rs — render_input_panel uses input_render::render_input_runs for simple mode; advanced modes fall back to plain before/under/after. render_output_line peels the dsl::ECHO_PREFIX from simple- mode echo lines and re- tokenises (lex_to_runs). render_hint_panel dispatches on AmbientHint variant; render_candidate_line composes spans with kind colouring + `<` `>` scroll markers. theme.rs — Seven new tok_* Color fields on Theme. Theme::token_color helper. WCAG-AA contrast values for dark + light. event.rs — AppEvent::SchemaCacheRefreshed variant. runtime.rs — refresh_schema_cache helper + call sites at every TablesRefreshed point. friendly/ keys.rs — Massive expansion. hint.*, parse.usage.*, parse.token.*, ambient_*, invalid_ident, typing_name, typing_name_then keys all validated. strings/en-US.yaml — Same expansion on the YAML side. Hint sentences are capitalised (Submit / Next / Type / No such). docs/ adr/ 0020-tokenization-layer-for-the-dsl-parser.md 0021-parser-as-source-of-truth-for-h1a.md 0022-ambient-typing-assistance.md README.md — indexed handoff/ 20260512-handoff-7.md — this file tests/ parse_error_pedagogy.rs — NEW (ADR-0021). 9 integration tests for caret + structural error + usage template composition. ``` ## How to take over 1. Read this file. 2. Read `CLAUDE.md` for the working-style rules. 3. Read `docs/requirements.md` for the granular progress table (note: probably wants an update reflecting the stage-8 completion of I3 and I4). 4. **Expect the user to begin with another testing pass.** Resist the urge to ship anything new until they have. Past rounds: r1-r4 each turned up real findings that needed surgical fixes. 5. When findings arrive, **trace each one to the minimum relevant layer** before patching: - Completion behaviour → `src/completion.rs` (`candidates_at_cursor`, `invalid_ident_at_cursor`, `typing_name_at_cursor`). - Hint panel content → `src/input_render.rs::ambient_hint`. - Hint panel render → `src/ui.rs::render_candidate_line` / `render_hint_panel`. - Input pane colours → `src/theme.rs` (tok_* fields). - Parser shape / labels → `src/dsl/parser.rs`. - Catalog wording → `src/friendly/strings/en-US.yaml` (and `keys.rs` for the placeholder declarations). 6. After testing settles, the next bigger move is **A1 (CI workflow)** — quick win that locks in the now- substantial 760-test baseline before more design work. 7. Then **Query DSL ADR + implementation** is the recommended bigger piece. 8. Run `cargo test` to confirm the 760-test green baseline. 9. Run `cargo clippy --all-targets` to confirm clippy-clean. ### End-to-end smoke test (current state, post-r4) Demonstrates ambient typing assistance. Replaces handoff-6's smoke test, which is now stale (it predates ADR-0022). ``` $ rm -rf /tmp/handoff7-smoke $ rdbms-playground --data-dir /tmp/handoff7-smoke # Inside the app — empty input. Hint panel shows the existing # panel.hint_empty content (no ambient interference until you # type). # Type a single character. Hint immediately offers candidates. a -- hint: "add" (single Keyword candidate, purple) # Tab inserts with trailing space (single candidate, no memo). -- input becomes "add " hint: "column" (next required kw) # Tab again chains through unique completions. -- input becomes "add column " hint: "to table" (multi: `to`, `table`, in source order) # Multi-candidate Tab: inserts WITHOUT trailing space. -- input "add column to" (memo alive, `to` highlighted) # Tab cycles within the memo. -- input "add column table" (memo still alive, `table` highlighted) # Pressing space commits the choice. Memo clears. -- input "add column table " # Continue. Tab on the table-name slot offers schema entries # in cyan-teal (identifier colour). -- if schema has Customers, Orders, etc., they cycle. # Type a fresh column name yourself. Customers: Email -- builds "add column to table Customers: Email" # Now hint reads "Type a name, then `(`" — wait, that's at the # NewName slot. After typing Email the parser has consumed it. # Hint reads "Next: `(`". # Type the type slot. (de -- hint: "decimal" (single match, keyword colour) -- input completes to "...Email (decimal " with trailing space # Type close paren and submit. ) -- command runs. # Invalid identifier feedback: show data Custp -- if no table starts with Custp, `Custp` renders red and hint says "No such table: `Custp`" # Unknown command fallback: frobulate widgets -- on submit, hint shows usage block; live hint shows "available commands" fallback. quit ``` ### Manual spot-checks worth running - Single-Tab chaining: `a` Tab Tab Tab Tab — should build `add column to ` (the unique-chain runs out at position 4 where `to` and `table` are both possible). - Multi-Tab cycling: `show ` Tab Tab Tab — should cycle data → table → data (wrap). - Esc undo: `show ` Tab Esc — should restore to `show `. - Backspace undo (symmetric): `show ` Tab Backspace — should also restore to `show `. - Sticky hint: `show ` Tab Tab — hint stays as the two-item candidate list with the selection moving. - Invalid identifier: type `show data X` where no table starts with X — `X` should render red + "No such table" hint. - Type completion: `add column to T: Name (` Tab Tab Tab — cycles through types. - Long candidate list: with many tables (say 12+), position at `show data ` to see the `<` / `>` scroll markers in the hint panel. - Advanced mode: `:show data` (one-shot) or `mode advanced` — input renders plain, hint shows existing `panel.hint_empty` content, Tab is a no-op. - Replay with bare path: `replay history.log` — should work as before via the source-slice special case. - Replay with quoted path: `replay 'my project/data.log'` — chumsky path. - Multi-byte UTF-8 inside a string literal: `insert into T values ('café')` — lexer + render must not panic.