753 lines
32 KiB
Markdown
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.
|