32 KiB
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 \``ignoredoc-test insrc/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 producingVec<Token>with byte-offset spans. Always succeeds: lex-shape errors (unterminated string, unrecognised character, malformed flag) embed asTokenKind::Error(_)tokens in the stream rather than as aResultvariant. Rationale: I4 (syntax highlighting) needs to render partial / invalid input uniformly; the parser seesErrortokens and raises a structural error at that point.src/dsl/keyword.rs—KeywordandPunctenums declared viadefine_keywords!/define_punct!macro_rules!invocations. Single source of truth: the enum, the lex-side string→variant mapping, the variant→literal rendering, and theparse.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.rsrewritten to operate on&[Token]. Keyword aggregation acrosschoicenow works natively ("expecteddataortable" instead of just "expectedtable"). Customtry_mapcontent errors (unknown type, mutually-exclusive flags, "with pk needs at least one column", "specified twice") survive unchanged. replaybare-path UX preserved via a one-place source-slice special case (ADR-0020 §6): after matchingKeyword(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.rsarose 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-commandUsageEntryregistry 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) andparse.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::Invalidgained anat_eof: boolflag; custom errors map toat_eof = trueconservatively 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):
IdentSlotenum (NewName,TableName,Column,RelationshipName) withexpected_label()/from_expected_label()round-trip.ident_ctx(slot)wrapper labels each call site; everyident()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:
aTab →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.
- Single candidate → insert with trailing space, no
memo. Chains naturally:
- 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
tobeforetableforadd column [to] [table] …), then schema identifiers alphabetised. Achieved by dropping a sort indescribe_expectedrather 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 byType::from_str) so they needed their own completion path via theTYPE_SLOT_LABELconstant. - "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):
- r1: hint ordering (keywords alphabetised vs. grammar
order); hint-panel kind colouring;
tok_identifierequal totheme.fg(no contrast). - 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).
- r3: identifier colour still too subtle; "expected: …"
reading as a leaked diagnostic; "Next: something else"
after
((unlabelledtype_keyword); typing into a NewName slot showed the post-consume "expected:(" prose. - 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:
- Do nothing structural until the user has tested. Wait.
- Expect findings to be small surface-level UX issues like the previous rounds (wording, ordering, colour adjustments, edge cases in completion / hint panel / render).
- 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
-
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 testcargo clippy --all-targets -- -D warnings. Locks in the 760-test green baseline. 1-2 hours.
-
Query DSL ADR + implementation. Biggest remaining design piece. Discussed in handoff-6: extend
show datainto 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. -
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
messagespersistence 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 ofParser<'a, &'a str, …>. Helper combinators indsl/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_inneris the only call that produces a bare identifier match. Every command parser combinator must useident_ctx(slot). There is no compile-time enforcement (ident_innerisfnnot a sealed type), only convention + code review. -
ParseError::Invalidgained two fields:at_eof: bool(stage 4) andexpected: Vec<String>(stage 5). Pattern matches with..are unaffected; the two constructions indsl/parser.rspopulate both correctly.at_eoffor custom errors is conservativelytrue— known limitation, noted on the field's docstring. -
Type names are NOT keywords. They lex as
Identifiertokens; the parser'stype_keyword()helper usesType::from_strto classify, which emits the existing "unknown type 'X' (expected one of: …)" custom error on miss. The completion engine recognises the"type"label (fromident_inner().labelled("type")insidetype_keyword) via theTYPE_SLOT_LABELconstant incompletion.rs. Adding a new type means: add the variant + keyword toType::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. -
replayparses via a special-case intry_parse_replay_with_bare_path(before the chumsky parser runs). Quoted paths go through chumsky; everything else is source-sliced from the byte afterreplay. Documented in ADR-0020 §6. -
The hint panel render now branches on the
AmbientHintenum:Prose(String)orCandidates { items, selected }. The renderer inui.rs::render_candidate_linebuilds 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 failedlist_names_forfor 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 haveif self.last_completion.is_some()guards so they fall through to normal behaviour when no memo is alive. -
completion::candidates_at_cursoris sync. It consults the in-memorySchemaCacherather than the worker thread. The cache may be slightly stale between a DDL command and itsSchemaCacheRefreshedevent; acceptable per ADR-0022 §9. -
NewNameslots return&[]fromSchemaCache::for_slot— the user invents these names. Thetyping_name_at_cursorfunction handles them with a friendlier hint ("Type a name, then …") via a placeholder-substitution re-parse. -
The
TYPE_SLOT_LABEL = "type"const incompletion.rsmust equal whatdsl::parser::type_keywordlabels itsselect_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::ALLandPunct::ALLarrays are the canonical iteration source for catalog-validity tests. A new keyword or punct that forgets a YAML entry underparse.token.*fails thekeys_validate_against_catalogtest 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
- Read this file.
- Read
CLAUDE.mdfor the working-style rules. - Read
docs/requirements.mdfor the granular progress table (note: probably wants an update reflecting the stage-8 completion of I3 and I4). - 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.
- 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(andkeys.rsfor the placeholder declarations).
- Completion behaviour →
- 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.
- Then Query DSL ADR + implementation is the recommended bigger piece.
- Run
cargo testto confirm the 760-test green baseline. - Run
cargo clippy --all-targetsto 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:
aTab Tab Tab Tab — should buildadd column to(the unique-chain runs out at position 4 wheretoandtableare both possible). - Multi-Tab cycling:
showTab Tab Tab — should cycle data → table → data (wrap). - Esc undo:
showTab Esc — should restore toshow. - Backspace undo (symmetric):
showTab Backspace — should also restore toshow. - Sticky hint:
showTab Tab — hint stays as the two-item candidate list with the selection moving. - Invalid identifier: type
show data Xwhere no table starts with X —Xshould 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 datato see the</>scroll markers in the hint panel. - Advanced mode:
:show data(one-shot) ormode advanced— input renders plain, hint shows existingpanel.hint_emptycontent, 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.