From f0632af8af3579dc3dcb767a080064d37bc099a6 Mon Sep 17 00:00:00 2001 From: "claude@clouddev1" Date: Sun, 10 May 2026 15:51:22 +0000 Subject: [PATCH] ADR-0022: ambient typing assistance (unifies I3 + I4) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the originally-planned separate ADRs for syntax highlighting (I4) and tab completion (I3) with a single unified design. 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. Three mid-typing states (valid-so-far / definite-error / incomplete-but-plausible) drive four layered channels: token-class colour and parse-error overlay (silent, always on), hint panel ambient and Tab-triggered completion mode (verbose, in the existing hint panel — no floating popups). Schema-aware from day one via an IdentSlot taxonomy in the parser (NewName / TableName / ColumnIn(TableRef::Earlier(N)) / RelationshipName); every existing ident() call gets audited and tagged. Completion candidates come from chumsky's expected-token-set for keyword slots and from a new worker request (ListNamesFor) for identifier slots. Implementation lands in 8 green-after-each commits: theme colours; input panel highlighting; echo line highlighting; render-time parse + error overlay; hint panel ambient; identifier-slot taxonomy + parser audit; schema query plumbing; completion mode + key bindings. Estimated 1500-2500 lines across the eight stages. Out of scope (deliberately): inline ghost text (could return as a "most-likely" affordance later — fish-shell style), fuzzy matching, punctuation completion, user-customisable keybindings, SQL highlighting in advanced mode (waits on Q4). --- docs/adr/0022-ambient-typing-assistance.md | 565 +++++++++++++++++++++ docs/adr/README.md | 1 + 2 files changed, 566 insertions(+) create mode 100644 docs/adr/0022-ambient-typing-assistance.md diff --git a/docs/adr/0022-ambient-typing-assistance.md b/docs/adr/0022-ambient-typing-assistance.md new file mode 100644 index 0000000..03fba23 --- /dev/null +++ b/docs/adr/0022-ambient-typing-assistance.md @@ -0,0 +1,565 @@ +# ADR-0022: Ambient typing assistance — colour, hint panel, completion (I3 + I4) + +## Status + +Accepted. + +Subsumes the originally-planned I4 (syntax highlighting) and +I3 (tab completion) into a single coherent feature: ambient +typing assistance. Builds on ADR-0020 (the lexer + parser-over-tokens +that this ADR consumes) and ADR-0021 (per-command usage +templates, which this ADR promotes from on-submit-only to live +ambient feedback). Cross-references ADR-0001 (theme conventions), +ADR-0010 (worker-thread access for schema queries), ADR-0019 +(catalog conventions for any new strings), and ADR-0009 (the +closed grammar surface that defines what each token class means). + +## Context + +ADR-0019, 0020, and 0021 closed the on-submit feedback gap: a +parse error now renders with a caret, a structural / custom +message, and a per-command usage template. Once the user +hits Enter, they get a thorough explanation. + +What's missing is *during* typing. Today the input field +renders as plain foreground colour with one inverted character +at the cursor. There is no signal for any of: + +- "is `crete` a keyword or a typo?"; +- "what comes after `create table Customers`?"; +- "is `Customers` actually a table that exists?"; +- "did I open this string and forget to close it?"; +- "does Tab do anything here?". + +Three separate mechanisms are commonly built to fix this: +**syntax highlighting** (colour), **tab completion** (Tab +key), and a **hint / status surface** (in our case the +already-present hint panel, which has been empty most of the +time). Each is correct on its own but they answer the same +underlying question — *what does the user need to know mid-typing?* +— and planning them separately produces three loose pieces +that drift apart in voice, mechanism, and what they cover. + +The framing this ADR adopts: + +- Colour is the **silent always-on** layer. +- The hint panel is the **verbose always-visible** layer. +- Tab is the **accept-this-suggestion action**. + +These layer cleanly, with each one taking the load the other +two cannot bear: + +- Colour catches lexer-level garbage and signals "this + token is in error" instantly, but cannot explain why. +- The hint panel always says what comes next in prose, but + cannot tell the user "the third character you typed is + wrong" — colour does that. +- Tab does nothing on its own; the hint panel tells the + user when Tab would help. + +The hint panel is the central design move: it converts the +"Tab opens a popup over your text" anti-pattern into "the +already-visible panel becomes the chooser when Tab activates +it", which keeps screen real estate stable and removes any +modal floating UI. + +## Decision + +### 1. Three mid-typing states + +Every keystroke produces one of three classifications, derived +from `lex(input)` followed by `parse_tokens(...)`: + +1. **Valid-so-far.** Lex produces zero `Error` tokens AND + parse succeeds OR parse fails at end-of-input (more input + would make it valid). +2. **Definite error.** Lex produces an `Error` token, OR + parse fails at a position before end-of-input (a token + appears where no production accepts it). +3. **Incomplete-but-plausible.** A subset of valid-so-far: + parse fails at end-of-input. The user's input is on + track but not done. + +The three states drive what colour, hint, and tab all do. + +### 2. Layered feedback channels + +Four channels operate independently, layered: + +| Channel | Layer | Always on | Driver | +|----------------------|--------------|-----------|---------------------| +| Token-class colour | Silent | Yes | Lexer | +| Error overlay | Silent | When applies | Parse position | +| Hint panel ambient | Verbose | Yes | Parse expected-set + state | +| Hint panel completion| Verbose | Tab-triggered | User action | + +Each channel knows its layer's job and stays in it. + +### 3. Token-class colour (the original I4 scope) + +`Theme` gains seven token-class colour fields: + +```rust +pub struct Theme { + // ... existing fields ... + pub tok_keyword: Color, + pub tok_identifier: Color, + pub tok_number: Color, + pub tok_string: Color, + pub tok_punct: Color, + pub tok_flag: Color, + pub tok_error: Color, +} +``` + +Both `Theme::dark()` and `Theme::light()` populate all seven +with WCAG-AA-contrasting values. Where two classes can +plausibly share `theme.fg`, the field still exists so a +future palette refresh can distinguish them without code +churn. + +`ui::render_input_panel` runs `lex(&app.input)` per render. +Each token contributes a styled `Span` using its class +colour; whitespace gaps are preserved as `theme.fg` spans. +The cursor reverse-styling is injected by splitting the span +that contains the cursor's byte position into +(before, under, after) sub-spans, marking `under` REVERSED +while preserving its colour. + +Per-render lex is microsecond-cheap. No caching. + +### 4. Error overlay (parse-error highlighting on the failing token) + +Render-time parse, in addition to lex. Then: + +- If parse succeeds, no overlay. +- If parse fails at end-of-input (incomplete-but-plausible), + no overlay. +- If parse fails at a token position before end-of-input, + the token at that position renders with `theme.tok_error` + overlay (foreground only, no underline / reverse — see + reasoning in §6 of the previous draft). +- Lex `Error` tokens always render with `theme.tok_error` + regardless of parse outcome. + +Subsequent tokens after the error position render in their +normal lex-class colours. Rationale: the user fixes one +thing at a time; cascading red across the rest of the input +is visually overwhelming and rarely informative. + +### 5. Echo lines: same colour treatment for simple-mode echoes + +`render_output_line` for `OutputKind::Echo + Mode::Simple` +peels the catalog-bound `running: ` prefix, lexes the rest, +and renders the tokens identically to the input panel. +Advanced-mode echoes stay plain. Lift the +`dsl::ECHO_PREFIX = "running: "` constant + a unit test +asserting `t!("dsl.running", input = "")` matches it (so a +translator changing the prefix breaks the test, not the +render). + +### 6. Hint panel ambient mode (always visible) + +The hint panel currently shows `panel.hint_empty` when +`app.hint` is `None`. This ADR repurposes it as the verbose +typing-assistance surface. Three sub-states, one per +mid-typing state: + +- **Valid-so-far + complete** (parse succeeds, all tokens + consumed): "submit with Enter" — short and unobtrusive. +- **Valid-so-far + incomplete-but-plausible** (parse fails at + EOF): "expected: ". Pulls + from chumsky's expected-set at the failure point. For + multi-entry families ("`add` family expects `column` or + `1`") this is one line of natural prose. +- **Definite error**: " — usage: +