Files
claude@clouddev1 1eb2e0d01f handoff
2026-05-12 09:02:54 +00:00

753 lines
32 KiB
Markdown

# 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<Token>` 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.<command>`
(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 `<chosen> ` 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<String>`
(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).
<Tab> -- input becomes "add "
hint: "column"
(next required kw)
# Tab again chains through unique completions.
<Tab> -- input becomes "add column "
hint: "to table"
(multi: `to`, `table`,
in source order)
# Multi-candidate Tab: inserts WITHOUT trailing space.
<Tab> -- input "add column to"
(memo alive,
`to` highlighted)
# Tab cycles within the memo.
<Tab> -- input "add column table"
(memo still alive,
`table` highlighted)
# Pressing space commits the choice. Memo clears.
<space> -- input "add column table "
# Continue. Tab on the table-name slot offers schema entries
# in cyan-teal (identifier colour).
<Tab> -- 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)
<Tab> -- input completes to
"...Email (decimal "
with trailing space
# Type close paren and submit.
)<Enter> -- 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.