docs: add ADR-0027 — input-field validity indicator
A debounced `[ERR]` / `[WRN]` marker at the right edge of the input row, summarising — before submit — whether the current command would run. Backed by a small diagnostics-severity model: the walker emits severity- tagged diagnostics (parse outcome, schema-existence of table / column names) that the indicator summarises and the existing highlighting / hint layers detail. Advisory only — submission is never blocked. - docs/adr/0027-input-validity-indicator.md — the ADR. - docs/adr/README.md — index entry. - docs/requirements.md — new S6 (TUI shell).
This commit is contained in:
@@ -0,0 +1,241 @@
|
|||||||
|
# ADR-0027: Input-field validity indicator
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Accepted
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
While the user types, the app already flags problems in
|
||||||
|
two immediate ways (ADR-0022): per-token syntax
|
||||||
|
highlighting, and an ambient hint panel describing the slot
|
||||||
|
under the cursor. Both are *local* — they speak about the
|
||||||
|
spot being edited.
|
||||||
|
|
||||||
|
What is missing is a *global* signal: a single, glanceable
|
||||||
|
answer to "if I press Enter now, will this command run?" A
|
||||||
|
learner can type something whose error is highlighted ten
|
||||||
|
columns back, move the cursor to the end, and submit it
|
||||||
|
without noticing.
|
||||||
|
|
||||||
|
This ADR adds a **validity indicator** at the right edge of
|
||||||
|
the input row, and — because a meaningful indicator needs a
|
||||||
|
consistent notion of "what is wrong" — a small
|
||||||
|
**diagnostics-severity model** behind it.
|
||||||
|
|
||||||
|
The indicator is **advisory**. It never blocks submission:
|
||||||
|
that posture is settled (ADR-0022 — the app indicates, it
|
||||||
|
does not refuse; ADR-0026 §7 — even a flagged WHERE still
|
||||||
|
runs if submitted).
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
### 1. Two severities
|
||||||
|
|
||||||
|
- **ERROR** — the input is *known* to fail. Either it does
|
||||||
|
not parse (incomplete, or a mismatched / invalid token),
|
||||||
|
or it parses but names something that does not exist (an
|
||||||
|
unknown table or column).
|
||||||
|
- **WARNING** — the input is valid and *will* run, but is
|
||||||
|
very likely not what a knowledgeable user wants: a
|
||||||
|
type-mismatched comparison, or `= NULL` (both from
|
||||||
|
ADR-0026 §7).
|
||||||
|
|
||||||
|
The split is *certainty of failure* versus *likely
|
||||||
|
misleading*. The indicator shows the highest severity
|
||||||
|
present; clean input shows nothing at all.
|
||||||
|
|
||||||
|
### 2. The diagnostics model
|
||||||
|
|
||||||
|
The walk produces a set of **diagnostics** alongside its
|
||||||
|
`WalkOutcome`. A diagnostic is, roughly,
|
||||||
|
`{ severity, span, message_key }` — the severity drives the
|
||||||
|
indicator, the span drives highlighting, the message key
|
||||||
|
drives the hint text (ADR-0019 catalog).
|
||||||
|
|
||||||
|
Diagnostics come from three sources, all reachable within
|
||||||
|
the single schema-aware walk:
|
||||||
|
|
||||||
|
- **The parse outcome.** `Incomplete`, `Mismatch`, and
|
||||||
|
`ValidationFailed` each yield an ERROR diagnostic; a
|
||||||
|
plain `Match` yields none.
|
||||||
|
- **Schema existence.** A matched `IdentSource::Tables` or
|
||||||
|
`IdentSource::Columns` token whose name is absent from
|
||||||
|
the schema cache yields an ERROR diagnostic. *This is new
|
||||||
|
behaviour.* Today `walk_ident` returns `Matched` for any
|
||||||
|
identifier-shaped token and consults the schema only to
|
||||||
|
populate context (`current_table`, `current_column`); an
|
||||||
|
unknown table parses cleanly and fails only at execution.
|
||||||
|
The check is cheap — the schema cache is already in
|
||||||
|
`WalkContext` — but it is genuinely new code, and it must
|
||||||
|
run for every Tables / Columns ident, not only the
|
||||||
|
`writes_*` ones.
|
||||||
|
- **Expression flagging (ADR-0026).** A type-mismatched
|
||||||
|
comparison and `= NULL` yield WARNING diagnostics.
|
||||||
|
|
||||||
|
The walker is the one schema-aware pass, so diagnostics are
|
||||||
|
emitted there, not in a separate re-resolution pass.
|
||||||
|
`WalkResult` gains a `diagnostics` field. The indicator
|
||||||
|
reads the highest severity across the outcome and the
|
||||||
|
diagnostics; highlighting and the hint panel read the
|
||||||
|
individual diagnostics for *where* and *why*. The indicator
|
||||||
|
is the summary; the existing layers remain the detail.
|
||||||
|
|
||||||
|
### 3. "Would Enter succeed now?" — and the debounce
|
||||||
|
|
||||||
|
The indicator answers exactly one question: *if you pressed
|
||||||
|
Enter right now, what would happen?* — runs clean (nothing
|
||||||
|
shown), runs but flagged (`[WRN]`), or will not run
|
||||||
|
(`[ERR]`).
|
||||||
|
|
||||||
|
An incomplete, still-being-typed command is in the `[ERR]`
|
||||||
|
bucket: pressing Enter on it fails. The indicator does not
|
||||||
|
special-case "still typing".
|
||||||
|
|
||||||
|
Flicker is prevented not by suppressing states but by a
|
||||||
|
**debounce**:
|
||||||
|
|
||||||
|
- On every keystroke the indicator is hidden immediately.
|
||||||
|
- After roughly one second with no typing, it reappears,
|
||||||
|
showing the current verdict.
|
||||||
|
|
||||||
|
So: typing → blank; a pause → the verdict. The user gets a
|
||||||
|
settled, glance-before-submit signal without a marker that
|
||||||
|
thrashes on every key. Highlighting and hints stay
|
||||||
|
immediate and unchanged — only the *indicator's display* is
|
||||||
|
debounced; diagnostics themselves are still computed every
|
||||||
|
keystroke to feed those immediate layers.
|
||||||
|
|
||||||
|
The indicator's display is therefore time-gated. Per the
|
||||||
|
`update()`-is-pure invariant, the debounce timer lives in
|
||||||
|
the runtime / event loop; `App` holds the indicator's
|
||||||
|
visible state, which the runtime sets when the quiet
|
||||||
|
interval elapses or a keystroke arrives. The interval is a
|
||||||
|
single tunable constant (~1 s).
|
||||||
|
|
||||||
|
### 4. Rendering
|
||||||
|
|
||||||
|
The indicator is a fixed-width five-column label —
|
||||||
|
`[ERR]` or `[WRN]` — or nothing when the input is clean. A
|
||||||
|
positive "all good" state is deliberately omitted: absence
|
||||||
|
*is* the all-clear.
|
||||||
|
|
||||||
|
It sits in the input row, at the right end. The rightmost
|
||||||
|
strip — five columns for the label plus one column of gap,
|
||||||
|
six in all — is **reserved unconditionally**: the text area
|
||||||
|
is always `width − 6`, whether or not the indicator is
|
||||||
|
currently visible. The label appears and disappears within
|
||||||
|
its already-reserved strip, so the text / horizontal-scroll
|
||||||
|
boundary never moves and the label never collides with the
|
||||||
|
typed command.
|
||||||
|
|
||||||
|
A *conditionally* reserved strip was rejected: it would
|
||||||
|
make a long, horizontally-scrolled command jump up to six
|
||||||
|
columns sideways on every typing-pause transition.
|
||||||
|
Unconditional reservation costs ~6 columns of typing width
|
||||||
|
— negligible — for a stable layout.
|
||||||
|
|
||||||
|
Border-mounting the indicator (as chrome on the field's
|
||||||
|
frame, like the mode label) was also considered and not
|
||||||
|
chosen: a right-edge border decoration reads differently
|
||||||
|
from a top-border label, and the in-row position is the
|
||||||
|
intended look.
|
||||||
|
|
||||||
|
`[ERR]` uses the existing `theme.error`. `[WRN]` needs a
|
||||||
|
new **`warning`** theme colour — an orange / amber —
|
||||||
|
defined for both the light and dark themes (NFR-5: colour
|
||||||
|
conveys information; NFR-7: legible on either background).
|
||||||
|
|
||||||
|
### 5. The indicator never blocks submission
|
||||||
|
|
||||||
|
Enter always submits, whatever the indicator shows. A user
|
||||||
|
who wants to run a flagged command — or even a doomed one,
|
||||||
|
to see what the engine does — may; the error lands in the
|
||||||
|
output as usual. The indicator's job is to make "this will
|
||||||
|
not do what you think" visible *before* submission, not to
|
||||||
|
prevent it. This is the same advisory posture as ADR-0022
|
||||||
|
and ADR-0026 §7.
|
||||||
|
|
||||||
|
### 6. The existing-cases sweep, and discoverability
|
||||||
|
|
||||||
|
Implementation includes a **sweep** of the current command
|
||||||
|
surface for failures that are detectable before submission
|
||||||
|
but not yet surfaced as diagnostics — chiefly unknown table
|
||||||
|
and column references, across every command that takes an
|
||||||
|
identifier. Each becomes an ERROR diagnostic via §2. (The
|
||||||
|
WARNING set is thin until ADR-0026 lands; type mismatch and
|
||||||
|
`= NULL` are its first members.)
|
||||||
|
|
||||||
|
The diagnostics model is documented as *the* route for any
|
||||||
|
future "we can tell, before it runs, that this is wrong or
|
||||||
|
dubious" case. This ADR is cross-referenced from the
|
||||||
|
diagnostic-producing code and from related ADRs, so new
|
||||||
|
cases plug into the model rather than becoming one-off
|
||||||
|
checks.
|
||||||
|
|
||||||
|
### 7. Out of scope
|
||||||
|
|
||||||
|
- **Blocking submission** — never; the indicator is
|
||||||
|
advisory (§5).
|
||||||
|
- **A positive `[OK]` state** — clean input shows nothing.
|
||||||
|
- **Raw advanced-mode SQL** — there is no SQL parser yet
|
||||||
|
(`Q1`). The indicator covers simple-mode DSL and the
|
||||||
|
app-level commands the walker parses; when SQL parsing
|
||||||
|
lands, SQL diagnostics route through this same model.
|
||||||
|
- **Per-diagnostic display in the indicator itself** — the
|
||||||
|
indicator is a one-glyph summary; *where* and *why* stay
|
||||||
|
with highlighting and the hint panel.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
- `WalkResult` gains a `diagnostics` field; the walker
|
||||||
|
emits diagnostics (parse-outcome and schema-existence) as
|
||||||
|
it walks.
|
||||||
|
- A new schema-existence check on `IdentSource::Tables` /
|
||||||
|
`Columns` matches. Small, but genuinely new — today an
|
||||||
|
unknown identifier parses cleanly and fails only at
|
||||||
|
execution.
|
||||||
|
- The event loop gains a debounce timer for the indicator's
|
||||||
|
display. It lives in the runtime, so `update()` stays
|
||||||
|
pure.
|
||||||
|
- A new `warning` theme colour; the input field reserves a
|
||||||
|
fixed six-column right strip.
|
||||||
|
- The indicator is advisory; submission is never gated.
|
||||||
|
- A reusable diagnostics-severity model that future
|
||||||
|
pre-submit checks — and, eventually, advanced-mode SQL —
|
||||||
|
extend.
|
||||||
|
- The WARNING severity has no triggers until ADR-0026 is
|
||||||
|
implemented. The indicator may ship ERROR-only first and
|
||||||
|
gain WARNING with C5a, or ship after C5a; the model
|
||||||
|
carries both severities regardless.
|
||||||
|
|
||||||
|
## Implementation notes
|
||||||
|
|
||||||
|
A sensible order, each step test-guarded:
|
||||||
|
|
||||||
|
1. The `Diagnostic` type and the `diagnostics` field on
|
||||||
|
`WalkResult`; map the parse outcome to diagnostics.
|
||||||
|
2. The schema-existence check in `walk_ident` for
|
||||||
|
`Tables` / `Columns` idents.
|
||||||
|
3. The `warning` theme colour; the fixed six-column input
|
||||||
|
strip; the `[ERR]` / `[WRN]` rendering.
|
||||||
|
4. The debounce in the runtime, and the indicator's
|
||||||
|
visible state on `App`.
|
||||||
|
5. The sweep — confirm every identifier-taking command
|
||||||
|
surfaces unknown-name diagnostics; cross-reference this
|
||||||
|
ADR from the diagnostic sites.
|
||||||
|
|
||||||
|
ADR-0026's expression diagnostics (type mismatch, `= NULL`)
|
||||||
|
land with that ADR's implementation and feed the WARNING
|
||||||
|
severity.
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- ADR-0003 — input modes; the input field and its mode
|
||||||
|
label.
|
||||||
|
- ADR-0019 — the friendly-message catalog the diagnostic
|
||||||
|
message keys resolve through.
|
||||||
|
- ADR-0022 — ambient typing assistance: the immediate
|
||||||
|
highlighting and hint layers this indicator summarises.
|
||||||
|
- ADR-0026 — complex WHERE expressions; the type-mismatch
|
||||||
|
and `= NULL` WARNING diagnostics.
|
||||||
@@ -32,3 +32,4 @@ This directory contains the project's ADRs, recorded per
|
|||||||
- [ADR-0024 — Unified grammar tree: execution plan](0024-unified-grammar-tree-execution-plan.md) — **Accepted**, the executable spec — implemented (Phases A–F; Phase F shipped "minimal", `parser.rs` retained as the router — see the ADR's Phase F implementation note)
|
- [ADR-0024 — Unified grammar tree: execution plan](0024-unified-grammar-tree-execution-plan.md) — **Accepted**, the executable spec — implemented (Phases A–F; Phase F shipped "minimal", `parser.rs` retained as the router — see the ADR's Phase F implementation note)
|
||||||
- [ADR-0025 — Indexes](0025-indexes.md) — **Accepted**, `add index` / `drop index`, persistence, rebuild-table preservation, and items-list display (`C3` index portion + `S2`)
|
- [ADR-0025 — Indexes](0025-indexes.md) — **Accepted**, `add index` / `drop index`, persistence, rebuild-table preservation, and items-list display (`C3` index portion + `S2`)
|
||||||
- [ADR-0026 — Complex WHERE expressions](0026-complex-where-expressions.md) — **Accepted**, stratified recursive expression grammar (`AND`/`OR`/`NOT`, comparisons, `LIKE`, `IS NULL`, `IN`, `BETWEEN`) for `update` / `delete` / `show data` filters; `show data` gains `where` + `limit`; adds the `Subgrammar` node and a recursive `Expr` AST (`C5a`)
|
- [ADR-0026 — Complex WHERE expressions](0026-complex-where-expressions.md) — **Accepted**, stratified recursive expression grammar (`AND`/`OR`/`NOT`, comparisons, `LIKE`, `IS NULL`, `IN`, `BETWEEN`) for `update` / `delete` / `show data` filters; `show data` gains `where` + `limit`; adds the `Subgrammar` node and a recursive `Expr` AST (`C5a`)
|
||||||
|
- [ADR-0027 — Input-field validity indicator](0027-input-validity-indicator.md) — **Accepted**, a debounced `[ERR]` / `[WRN]` marker at the input row's right edge, backed by a walker diagnostics-severity model (parse-outcome + schema-existence); advisory, never blocks submission (`S6`)
|
||||||
|
|||||||
@@ -59,6 +59,12 @@ group enabled. (Earlier reference points: 1006 after ADR-0024
|
|||||||
for inspecting hints about the current input or last error.
|
for inspecting hints about the current input or last error.
|
||||||
- [ ] **S5** Mode label and distinct border style on the input
|
- [ ] **S5** Mode label and distinct border style on the input
|
||||||
field communicate the current input mode at all times.
|
field communicate the current input mode at all times.
|
||||||
|
- [ ] **S6** Input-field validity indicator: a debounced
|
||||||
|
`[ERR]` / `[WRN]` marker at the right edge of the input row,
|
||||||
|
summarising — before submit — whether the current command
|
||||||
|
would run. Backed by a walker diagnostics-severity model
|
||||||
|
(ERROR / WARNING). Advisory only — never blocks submission.
|
||||||
|
Designed in ADR-0027; implementation pending.
|
||||||
|
|
||||||
## Input field
|
## Input field
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user