# 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: