From 447112b17f6a08dae932c98fac5fcf3fb65daaa7 Mon Sep 17 00:00:00 2001 From: "claude@clouddev1" Date: Mon, 15 Jun 2026 16:34:10 +0000 Subject: [PATCH] =?UTF-8?q?feat(hint):=20H2=20Phase=20D=20=E2=80=94=20cove?= =?UTF-8?q?rage=20gate,=20F1=20strip,=20status=20flips=20(ADR-0053)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completes H2: - comprehensiveness coverage tests: every REGISTRY command form has a hint_id resolving to a hint.cmd.* block, and every runtime error class resolves to a hint.err.* block (enforces ADR-0053 D6) - ADR-0051 keybinding strip advertises F1 in the editing (leads) and default states; +shortcut.hint label; 12 full-panel snapshots re-accepted (status-bar line only) - flip ADR-0053 -> implemented, requirements H2 + A1 -> [x], README 2498 pass / 1 ignored, clippy clean. --- ...-contextual-hint-command-and-keybinding.md | 10 +++- docs/adr/README.md | 2 +- docs/requirements.md | 21 +++++--- src/dsl/grammar/mod.rs | 50 +++++++++++++++++++ src/friendly/keys.rs | 1 + src/friendly/strings/en-US.yaml | 1 + ...und__ui__tests__default_advanced_dark.snap | 4 +- ...round__ui__tests__default_simple_dark.snap | 4 +- ...ound__ui__tests__default_simple_light.snap | 4 +- ..._demo_badge_and_caption_stacked_90x26.snap | 4 +- ...__tests__demo_badge_enter_light_90x26.snap | 4 +- ..._ui__tests__demo_badge_tab_dark_90x26.snap | 4 +- ...d__ui__tests__demo_caption_dark_90x26.snap | 4 +- ...ui__tests__demo_caption_wrapped_90x26.snap | 4 +- ...hlighted_input_all_token_classes_dark.snap | 4 +- ...nd__ui__tests__one_shot_advanced_dark.snap | 4 +- ..._ui__tests__populated_with_table_dark.snap | 4 +- ...ui__tests__rebuild_confirm_modal_dark.snap | 4 +- ...__ui__tests__relationships_panel_dark.snap | 4 +- ...ground__ui__tests__two_row_input_dark.snap | 2 +- src/ui.rs | 15 +++--- 21 files changed, 111 insertions(+), 43 deletions(-) diff --git a/docs/adr/0053-contextual-hint-command-and-keybinding.md b/docs/adr/0053-contextual-hint-command-and-keybinding.md index dfe3081..9bf6ddf 100644 --- a/docs/adr/0053-contextual-hint-command-and-keybinding.md +++ b/docs/adr/0053-contextual-hint-command-and-keybinding.md @@ -2,7 +2,15 @@ ## Status -Accepted — implementation in progress. Revised after a `/runda` review +Accepted — **implemented 2026-06-15** (plan: +`docs/plans/20260614-adr-0053-contextual-hint-H2.md`; the F1 keybinding + +`hint` command, the `hint_ids` per-form keying + `hint_key_for_input_in_mode`, +`last_error_hint_key` + `friendly::error_hint_class`, the `note_hint*` +renderers, and the `hint.cmd.*`/`hint.err.*` corpus for every command form ++ the 9 runtime error classes, with the comprehensiveness coverage test +and the ADR-0051 strip advertising F1). Closes **A1** + requirements +**H2**. Deferred: the pre-submit-diagnostic route + `diagnostic.*` blocks +(#38), clause-concept hints (#37). Revised after a `/runda` review (2026-06-14): corrected the verbosity-default fact; re-keyed tier-3 content off `help_id`; split the pre-submit-diagnostic and runtime-error paths; added a comprehensiveness coverage test. Revised again during diff --git a/docs/adr/README.md b/docs/adr/README.md index 6a8a3e9..1954476 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -58,4 +58,4 @@ This directory contains the project's ADRs, recorded per - [ADR-0050 — Incidental-DDL confirmations omit relationship info (structure-only)](0050-incidental-ddl-confirmations-omit-relationships.md) — **Accepted + implemented 2026-06-12 (issue #28)**, closes Gitea **#28**. **Supersedes** the incidental-DDL clause of **ADR-0044 §1** and the relationship-block half of **ADR-0016 §5**. Incidental-DDL confirmation echoes (`create table`, `add`/`drop`/`rename`/`change column`, `add`/`drop index`) now render **structure only** — header + column box + `Indexes:` + constraints — with **no `References:` / `Referenced by:` block** (neither prose nor diagram), even when the table carries relationships the user did not touch. Rationale (owner): a confirmation echo reports the change just made, not untouched relationships; ADR-0044's terse prose was the lesser of "prose vs diagram", but the right answer for these surfaces is **neither**. **Relationship-subject surfaces are unchanged** — `show table`, `add`/`drop relationship`, `show relationship` still render ADR-0044 diagrams; relationships appear only when the user asks for (`show table`) or acts on (`add`/`drop relationship`) one, and are one `show table ` away — **no information lost**. Forks both user-chosen: **scope = all incidental DDL** (not just `add column` — the rationale is uniform, the mental model clean, and it's the simpler edit) and **delete the prose renderer** (not retain it dormant — no dead code). **Mechanism:** the `handle_dsl_success` `matches!` routing is unchanged (relationship-subject → diagrams; else → `render_structure`); the change is one line inside `render_structure` (`output_render.rs` — drop the relationship-block call) since all its callers are incidental DDL, plus deletion of the orphaned `relationship_prose_lines` + `cols_disp` helpers. The prose format survives in ADR-0016 §5 + git history for a future OOS-7 always-prose setting. **Tests:** the prose-presence unit test + its snapshot removed; a new unit test asserts `render_structure` on a description carrying **both** inbound and outbound relationships emits the box but no prose; the misnamed `add_relationship_flow_shows_inbound_section_on_parent` integration test (which sent an `AddColumn`) inverted + renamed to assert the add-column echo omits the prose; the diagram tests (`show table`, `add relationship`) unaffected. **2458 pass / 0 fail / 0 skip (1 ignored), clippy clean**. `requirements.md` unaffected (ADR-tracked refinement of a decided area, like ADR-0044 itself) - [ADR-0051 — Bottom keybinding strip: context- and state-aware](0051-context-state-aware-keybinding-strip.md) — **Accepted + implemented 2026-06-13 (issue #27)**, closes Gitea **#27**. Repurposes the bottom status line into a **keystrokes-only, state-selected** strip (builds on ADR-0046 nav focus, ADR-0003 modes, ADR-0049 the #29 readline keys it now advertises, ADR-0022 the completion memo). A pure `status_bar_bindings(app) -> Vec<(key,label)>` chooses the strip by **priority, first match wins**: (1) **sidebar focus** → `Ctrl-O next pane · ↑↓/PgUp/PgDn scroll · Esc input`; (2) **completion memo live** (`last_completion`) → `Tab/Shift-Tab cycle · Esc cancel · Enter run`; (3) **history navigation** (new `App::is_browsing_history()` exposing the private `history_cursor`) → `↑↓ browse · Esc clear · Enter run`; (4) **editing** (input non-empty) → `Esc clear · Ctrl-A/E home/end · Ctrl-W del word · Enter run` (surfaces the #29 keys, closing ADR-0049's deferred advertisement); (5) **default** (empty) → `Ctrl-O sidebar · Tab complete · ↑ history · Enter run`. Priority is correct because Up clears the completion memo and Tab cancels history nav, so states 2/3 never co-occur, and the five are exhaustive for Input focus. **Typed-command words leave the strip** (`mode advanced`/`mode simple` switch, `:` one-shot) and **mode discovery moves to the empty-input hint** (`resolve_hint_lines`), **simple mode only**: `\`mode advanced\` for SQL` (the verb "type" omitted — the prompt implies it; advanced mode shows **no** pointer per a post-trial user decision — a switcher knows how they got there and `help` covers the way back). The one-shot's old `Backspace cancel one-shot` label is subsumed by the editing state (behaviour intact). Forks all user-chosen: **editing state shows the #29 keys** (vs unadvertised); **`Ctrl-C quit` omitted** from the strip (vs always shown); **no width-drop machinery** — the longest strip (~65 cols) fits all supported widths, so a **width-budget unit test** keeps it lean by construction instead (the user's own observation). Catalog: 12 new `shortcut.*` labels + the `panel.hint_mode_advanced` string added to `en-US.yaml`+`keys.rs` (validator-checked 1:1), 5 now-dead strip strings removed. **Modal-aware strip is OOS** (pre-existing: a modal owns the keyboard and carries its own hints; the strip under it is unchanged-in-kind, not worsened). Tests: 9 Tier-1 unit (per-state key sets — completion/history driven through real key events; width budget; mode-pointer presence/absence), 1 Tier-3 rewritten (`status_bar_is_keystroke_only_and_state_aware`), 15 full-panel snapshots re-accepted (reviewed — strip/hint only). **2467 pass / 0 fail / 0 skip (1 ignored), clippy clean.** OOS: modal-aware strip; a full-key cheatsheet overlay; Ctrl-K/U advertisement (editing strip shows the highest-value subset within the width budget) - [ADR-0052 — Mode-tagged history for cross-mode recall](0052-mode-tagged-history-cross-mode-recall.md) — **Accepted + implemented 2026-06-13 (issue #30)**, closes Gitea **#30** — the feature (advanced history reusable in simple mode) **and** the bug in its comment (the `:` one-shot prefix lost across sessions). **Amends ADR-0034** (status field gains a `:adv` tag; **journaling moves from the worker to the dispatch layer**), **ADR-0015 §5/§6** (history.log leaves the worker transaction — `commit-db-last` now scopes yaml/csv/db only), and **ADR-0040** (a success-path journal-write failure is best-effort, not fatal); references ADR-0003. **Root cause:** history carried no mode, and the in-memory ring stored the raw `:select 1` while the worker journalled the *stripped* `select 1`, so the `:` was lost on disk. **Fix:** record the submission mode per entry as a **`:adv` suffix on the status token** (`ok`/`ok:adv`/`err`/`err:adv`) — `source` stays last + canonical so replay is unaffected; the in-memory ring (still `Vec`) stores advanced entries in their `: `-prefixed simple-mode runnable form (a leading `:` unambiguously marks advanced since simple DSL never starts with `:`); recall **strips the `:` in advanced mode** (runs as bare SQL) and keeps it in simple mode (runs via the one-shot escape); hydration reconstructs the `: `-prefix from the tag, so cross-session = in-session. **The architectural turn (user's call):** the first draft kept journaling in the worker + threaded the mode down (~30-site plumbing); on review the user asked why the journal is written deep in the worker when the *failure* path already journals at the top of the chain — it shouldn't (history.log is a journal, not state). So **success journaling moved up** to `spawn_dsl_dispatch` / `run_replay` / the app-command sites (next to the failure path), the worker's `finalize_persistence` now writes only yaml/csv, and the journal write became **best-effort** (the command is already committed — consistent with the failure path; a rare disk-full leaves a committed command unjournalled, state intact). **App commands** journal simple (dispatched outside the spawn) and `submit` excludes them from the ring's advanced flag, so `undo`/`mode advanced` recall bare. Forks user-chosen: status-tag format (vs 4th field / `:`-in-source); unified scope; **dispatch-layer best-effort journaling** (vs worker-coupled-fatal). Two `/runda` passes (the second drove the relocation + app-command exclusion). Tests: the 15 worker-level journaling tests retired (worker no longer journals — yaml/csv/operation checks kept), re-covered at the new layer (history.rs status-tag + `:`-reconstruct; app.rs recall matrix; the #30 cross-session regression in `iteration6`; replay tests cover `run_replay` journaling). **2471 pass / 0 fail / 0 skip (1 ignored), clippy clean.** replay re-journaling mode-fidelity (a replayed advanced line re-journals simple — not a regression). **Follow-up done 2026-06-14:** the vestigial worker `source` plumbing was fully unwound (compiler-guided, no behaviour change) — `_source` removed from `finalize_persistence`/`do_rebuild_from_text`, the three `*_request` wrappers inlined+deleted, the dead `source` param dropped from the ~30 forwarding worker handlers, and the `source` field removed from the `DescribeTable`/`QueryData`/`RunSelect` requests + their `DatabaseHandle` methods (~164 mostly-test call sites); the only worker `source` left is the snapshot/undo label (see ADR-0052 *Consequences*) -- [ADR-0053 — Contextual `hint` command and keybinding](0053-contextual-hint-command-and-keybinding.md) — **Accepted, implementation in progress (Phases A–C done 2026-06-15: mechanism + per-form keying + the command-form & runtime-error content; Phase D polish next)**. Settles the `hint` slot ADR-0003 left "ADR pending"; closes the last open piece of **A1** and tracks requirements **H2**. **Two surfaces:** an **F1 keybinding** that renders a deep hint for the *live* partial input without submitting (the primary path — a submitted `hint` command can't see the buffer it would help with, since Enter empties it), and a submitted **`hint` command** that expands on the *most recent error*. **No topic argument** (contextual only — `help ` already owns explicit reference). Introduces a **tier-3 teaching layer**, deeper than the existing tier-1 (colour / error headline) and tier-2 (ambient one-liner; and the error `hint:`, which is shown **by default** since `Verbosity::Verbose` is the default — `messages short` is the opt-*out*); without it `hint` would just duplicate what's already on screen. Tier-3 content lives in the catalogue under `hint.cmd.` (per command form) and `hint.err.` (per error/diagnostic class), each a structured `what`/`example`/`concept` block rendered via a new `note_hint*` family with `OutputStyleClass::Hint`. **Keyed per-form via a new `hint_ids: &[&str]` field on `CommandNode` mirroring `usage_ids`** (revised in Phase B): a per-*node* key proved too coarse — `add`/`drop`/`show`/`create` are each one node spanning many forms, and a live-input hint for `add 1:n relationship` must be specific to relationships; `hint_key_for_input_in_mode` reuses `usage_key_for_input_in_mode`'s form-word disambiguation, and covers the advanced-SQL forms whose `usage_ids` are empty. Not keyed off `help_id` (it is `None` on the advanced-SQL nodes purely to dedup the `help` list; that parallel gap is issue **#36**). **Clause-concept hints** (`on delete` actions, constraint slots, `with pk`, cardinality) are a recorded **deferred extension** (`hint.concept.`, issue **#37**) — per-form is the right tier-3 granularity, with position-awareness owned by tier-2 + the live `Next:` line. Runtime `translate_error` classes resolve via stored `last_error_hint_key` (`hint` command / empty-F1). (The second route — pre-submit `diagnostic.*` read live from the walker on the F1 path — is **deferred**, issue **#38**: `Diagnostic` carries no class key.) Adds `AppCommand::Hint`, a `HINT` grammar node + REGISTRY entry, the `hint_ids` field, and `last_error_hint_key`; F1 is a read-only overlay (buffer + completion memo untouched). **Content is the bulk of the work** (the mechanism is ~a day): v1 scope = ~37 command forms + 9 runtime error classes (comprehensive for those, ~57 blocks), authored **exemplars-first** (voice approved in this ADR's `/runda` review, then mass-authored in batches), enforced by a **comprehensiveness coverage test**, with graceful fall-back to tier-2 if a key is ever missing. The **pre-submit-diagnostic route + ~33 `diagnostic.*` blocks were deferred** (issue **#38**) — `Diagnostic` carries no class key, so the route needs a broad change for marginal value (tier-2 already surfaces diagnostics; many duplicate runtime classes). Forks user-chosen: two-surface model; **F1** (vs `?` / a chord); no-arg; comprehensive-for-commands-and-errors scope; exemplars-first; diagnostics deferred. OOS: per-topic `hint ` (rejected — overlaps `help`); always-on tier-3 (rejected — keeps ambient terse); non-`en-US` locales + success-command teaching (deferred); clause-concept hints (issue #37); the diagnostic route (issue #38); the `help`-side advanced-SQL gap (issue #36) +- [ADR-0053 — Contextual `hint` command and keybinding](0053-contextual-hint-command-and-keybinding.md) — **Accepted, implemented 2026-06-15** (Phases A–D; closes **A1** + requirements **H2**). Settles the `hint` slot ADR-0003 left "ADR pending"; closes the last open piece of **A1** and tracks requirements **H2**. **Two surfaces:** an **F1 keybinding** that renders a deep hint for the *live* partial input without submitting (the primary path — a submitted `hint` command can't see the buffer it would help with, since Enter empties it), and a submitted **`hint` command** that expands on the *most recent error*. **No topic argument** (contextual only — `help ` already owns explicit reference). Introduces a **tier-3 teaching layer**, deeper than the existing tier-1 (colour / error headline) and tier-2 (ambient one-liner; and the error `hint:`, which is shown **by default** since `Verbosity::Verbose` is the default — `messages short` is the opt-*out*); without it `hint` would just duplicate what's already on screen. Tier-3 content lives in the catalogue under `hint.cmd.` (per command form) and `hint.err.` (per error/diagnostic class), each a structured `what`/`example`/`concept` block rendered via a new `note_hint*` family with `OutputStyleClass::Hint`. **Keyed per-form via a new `hint_ids: &[&str]` field on `CommandNode` mirroring `usage_ids`** (revised in Phase B): a per-*node* key proved too coarse — `add`/`drop`/`show`/`create` are each one node spanning many forms, and a live-input hint for `add 1:n relationship` must be specific to relationships; `hint_key_for_input_in_mode` reuses `usage_key_for_input_in_mode`'s form-word disambiguation, and covers the advanced-SQL forms whose `usage_ids` are empty. Not keyed off `help_id` (it is `None` on the advanced-SQL nodes purely to dedup the `help` list; that parallel gap is issue **#36**). **Clause-concept hints** (`on delete` actions, constraint slots, `with pk`, cardinality) are a recorded **deferred extension** (`hint.concept.`, issue **#37**) — per-form is the right tier-3 granularity, with position-awareness owned by tier-2 + the live `Next:` line. Runtime `translate_error` classes resolve via stored `last_error_hint_key` (`hint` command / empty-F1). (The second route — pre-submit `diagnostic.*` read live from the walker on the F1 path — is **deferred**, issue **#38**: `Diagnostic` carries no class key.) Adds `AppCommand::Hint`, a `HINT` grammar node + REGISTRY entry, the `hint_ids` field, and `last_error_hint_key`; F1 is a read-only overlay (buffer + completion memo untouched). **Content is the bulk of the work** (the mechanism is ~a day): v1 scope = ~37 command forms + 9 runtime error classes (comprehensive for those, ~57 blocks), authored **exemplars-first** (voice approved in this ADR's `/runda` review, then mass-authored in batches), enforced by a **comprehensiveness coverage test**, with graceful fall-back to tier-2 if a key is ever missing. The **pre-submit-diagnostic route + ~33 `diagnostic.*` blocks were deferred** (issue **#38**) — `Diagnostic` carries no class key, so the route needs a broad change for marginal value (tier-2 already surfaces diagnostics; many duplicate runtime classes). Forks user-chosen: two-surface model; **F1** (vs `?` / a chord); no-arg; comprehensive-for-commands-and-errors scope; exemplars-first; diagnostics deferred. OOS: per-topic `hint ` (rejected — overlaps `help`); always-on tier-3 (rejected — keeps ambient terse); non-`en-US` locales + success-command teaching (deferred); clause-concept hints (issue #37); the diagnostic route (issue #38); the `help`-side advanced-SQL gap (issue #36) diff --git a/docs/requirements.md b/docs/requirements.md index d3fc046..4aaf750 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -250,16 +250,13 @@ since ADR-0027.) ## App-level commands (per ADR-0003) -- [/] **A1** All canonical app-level commands implemented and +- [x] **A1** All canonical app-level commands implemented and available in both modes: `save`, `save as`, `load`, `new`, `rebuild`, `export`, `import`, `seed`, `replay`, `undo`, `redo`, `mode`, `help`, `hint`, `quit`. - *(Partial: **14 of 15** implemented and available in both modes — - `quit`/`q`, `mode simple|advanced`, `help`, `save`, `save as`, - `load`, `new`, `rebuild`, `export`, `import`, `replay`, `undo`, - `redo`, and now **`seed`** (ADR-0048 / SD1, done 2026-06-11). - **Only `hint`** (tracked as H2) remains unregistered. A1 closes - when H2 lands.)* + *(Done 2026-06-15: the last command, **`hint`**, landed with H2 + (ADR-0053). All 15 canonical app commands are now registered and + available in both modes.)* ## DSL data commands @@ -793,8 +790,16 @@ since ADR-0027.) `returning `) still shows the raw expression first-set — typing-time completion already offers the right candidates there, so the payoff is small. -- [ ] **H2** `hint` provides contextual help for the current +- [x] **H2** `hint` provides contextual help for the current input or the most recent error. + *(Done 2026-06-15, ADR-0053: an **F1** keybinding gives a tier-3 + teaching hint for the live partial input (read-only overlay), and a + submitted **`hint`** command expands on the most recent runtime error. + A new `hint.cmd.
` / `hint.err.` catalogue tier + (`what`/`example`/`concept`) covers every command form + the 9 runtime + error classes, enforced by a comprehensiveness coverage test. Deferred: + the pre-submit-diagnostic route + `diagnostic.*` blocks (#38), + clause-concept hints (#37).)* - [x] **H3** `help` provides general reference and per-command help. *(Done 2026-06-07: the **general reference** is `help` (no arg) — diff --git a/src/dsl/grammar/mod.rs b/src/dsl/grammar/mod.rs index 476e060..3842073 100644 --- a/src/dsl/grammar/mod.rs +++ b/src/dsl/grammar/mod.rs @@ -962,6 +962,56 @@ mod hint_key_tests { // Unknown entry word → None (tier-2 fallback). assert_eq!(hint_key_for_input_in_mode("zzz", Mode::Simple), None); } + + /// Comprehensiveness gate (ADR-0053 D6): every command form in the + /// REGISTRY carries at least one `hint_id`, and each resolves to a + /// tier-3 `hint.cmd.` block. `keys.rs` checks referenced keys + /// resolve; this checks every command *has* one. + #[test] + fn every_command_form_has_a_tier3_block() { + let cat = crate::friendly::catalog(); + for (node, _category) in super::REGISTRY { + assert!( + !node.hint_ids.is_empty(), + "command `{}` has no hint_ids (ADR-0053 D6)", + node.entry.primary + ); + for id in node.hint_ids { + let key = format!("hint.cmd.{id}.what"); + assert!( + cat.get(&key).is_some(), + "missing tier-3 block `{key}` for command `{}`", + node.entry.primary + ); + } + } + } + + /// Comprehensiveness gate (ADR-0053 D6): every runtime error class + /// `friendly::error_hint_class` can return resolves to a tier-3 + /// `hint.err.` block. Keep this list in sync with + /// `error_hint_class` (its own unit tests pin the outputs). + /// Diagnostic classes are deferred (issue #38), so not checked here. + #[test] + fn every_runtime_error_class_has_a_tier3_block() { + let cat = crate::friendly::catalog(); + let classes = [ + "unique", + "foreign_key.child_side", + "foreign_key.parent_side", + "not_null", + "check", + "type_mismatch", + "not_found", + "already_exists", + "generic", + "invalid_value", + ]; + for c in classes { + let key = format!("hint.err.{c}.what"); + assert!(cat.get(&key).is_some(), "missing tier-3 error block `{key}`"); + } + } } #[cfg(test)] diff --git a/src/friendly/keys.rs b/src/friendly/keys.rs index bb945ff..177526d 100644 --- a/src/friendly/keys.rs +++ b/src/friendly/keys.rs @@ -647,6 +647,7 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[ ("shortcut.confirm", &[]), ("shortcut.cycle", &[]), ("shortcut.del_word", &[]), + ("shortcut.hint", &[]), ("shortcut.history", &[]), ("shortcut.home_end", &[]), ("shortcut.load", &[]), diff --git a/src/friendly/strings/en-US.yaml b/src/friendly/strings/en-US.yaml index 8fd4cbb..f6df963 100644 --- a/src/friendly/strings/en-US.yaml +++ b/src/friendly/strings/en-US.yaml @@ -1165,6 +1165,7 @@ shortcut: browse: "browse" clear: "clear" complete: "complete" + hint: "hint" history: "history" home_end: "home/end" del_word: "del word" diff --git a/src/snapshots/rdbms_playground__ui__tests__default_advanced_dark.snap b/src/snapshots/rdbms_playground__ui__tests__default_advanced_dark.snap index 0505791..fc6eb98 100644 --- a/src/snapshots/rdbms_playground__ui__tests__default_advanced_dark.snap +++ b/src/snapshots/rdbms_playground__ui__tests__default_advanced_dark.snap @@ -1,6 +1,6 @@ --- source: src/ui.rs -assertion_line: 2836 +assertion_line: 2839 expression: snapshot --- ╭ Output ──────────────────────────────────────────────────────────────────────╮ @@ -26,4 +26,4 @@ expression: snapshot │ │ ╰──────────────────────────────────────────────────────────────────────────────╯ Project: Term Planner -Ctrl-O sidebar · Tab complete · ↑ history · Enter run +Ctrl-O sidebar · Tab complete · ↑ history · F1 hint · Enter run diff --git a/src/snapshots/rdbms_playground__ui__tests__default_simple_dark.snap b/src/snapshots/rdbms_playground__ui__tests__default_simple_dark.snap index e192380..8841ef3 100644 --- a/src/snapshots/rdbms_playground__ui__tests__default_simple_dark.snap +++ b/src/snapshots/rdbms_playground__ui__tests__default_simple_dark.snap @@ -1,6 +1,6 @@ --- source: src/ui.rs -assertion_line: 2819 +assertion_line: 2822 expression: snapshot --- ╭ Output ──────────────────────────────────────────────────────────────────────╮ @@ -26,4 +26,4 @@ expression: snapshot │for SQL │ ╰──────────────────────────────────────────────────────────────────────────────╯ Project: Term Planner -Ctrl-O sidebar · Tab complete · ↑ history · Enter run +Ctrl-O sidebar · Tab complete · ↑ history · F1 hint · Enter run diff --git a/src/snapshots/rdbms_playground__ui__tests__default_simple_light.snap b/src/snapshots/rdbms_playground__ui__tests__default_simple_light.snap index a86c7b3..c79416c 100644 --- a/src/snapshots/rdbms_playground__ui__tests__default_simple_light.snap +++ b/src/snapshots/rdbms_playground__ui__tests__default_simple_light.snap @@ -1,6 +1,6 @@ --- source: src/ui.rs -assertion_line: 2827 +assertion_line: 2830 expression: snapshot --- ╭ Output ──────────────────────────────────────────────────────────────────────╮ @@ -26,4 +26,4 @@ expression: snapshot │for SQL │ ╰──────────────────────────────────────────────────────────────────────────────╯ Project: Term Planner -Ctrl-O sidebar · Tab complete · ↑ history · Enter run +Ctrl-O sidebar · Tab complete · ↑ history · F1 hint · Enter run diff --git a/src/snapshots/rdbms_playground__ui__tests__demo_badge_and_caption_stacked_90x26.snap b/src/snapshots/rdbms_playground__ui__tests__demo_badge_and_caption_stacked_90x26.snap index f429374..3482f13 100644 --- a/src/snapshots/rdbms_playground__ui__tests__demo_badge_and_caption_stacked_90x26.snap +++ b/src/snapshots/rdbms_playground__ui__tests__demo_badge_and_caption_stacked_90x26.snap @@ -1,6 +1,6 @@ --- source: src/ui.rs -assertion_line: 3442 +assertion_line: 3445 expression: snapshot --- ╭ Output ────────────────────────────────────────────────────────────────────────────────╮ @@ -28,4 +28,4 @@ expression: snapshot │ │ ╰────────────────────────────────────────────────────────────────────────────────────────╯ Project: Term Planner -Ctrl-O sidebar · Tab complete · ↑ history · Enter run +Ctrl-O sidebar · Tab complete · ↑ history · F1 hint · Enter run diff --git a/src/snapshots/rdbms_playground__ui__tests__demo_badge_enter_light_90x26.snap b/src/snapshots/rdbms_playground__ui__tests__demo_badge_enter_light_90x26.snap index 86f0ce5..ecc05ad 100644 --- a/src/snapshots/rdbms_playground__ui__tests__demo_badge_enter_light_90x26.snap +++ b/src/snapshots/rdbms_playground__ui__tests__demo_badge_enter_light_90x26.snap @@ -1,6 +1,6 @@ --- source: src/ui.rs -assertion_line: 3388 +assertion_line: 3391 expression: snapshot --- ╭ Output ────────────────────────────────────────────────────────────────────────────────╮ @@ -28,4 +28,4 @@ expression: snapshot │ │ ╰────────────────────────────────────────────────────────────────────────────────────────╯ Project: Term Planner -Ctrl-O sidebar · Tab complete · ↑ history · Enter run +Ctrl-O sidebar · Tab complete · ↑ history · F1 hint · Enter run diff --git a/src/snapshots/rdbms_playground__ui__tests__demo_badge_tab_dark_90x26.snap b/src/snapshots/rdbms_playground__ui__tests__demo_badge_tab_dark_90x26.snap index e9b9e4a..723e28e 100644 --- a/src/snapshots/rdbms_playground__ui__tests__demo_badge_tab_dark_90x26.snap +++ b/src/snapshots/rdbms_playground__ui__tests__demo_badge_tab_dark_90x26.snap @@ -1,6 +1,6 @@ --- source: src/ui.rs -assertion_line: 3378 +assertion_line: 3381 expression: snapshot --- ╭ Output ────────────────────────────────────────────────────────────────────────────────╮ @@ -28,4 +28,4 @@ expression: snapshot │ │ ╰────────────────────────────────────────────────────────────────────────────────────────╯ Project: Term Planner -Ctrl-O sidebar · Tab complete · ↑ history · Enter run +Ctrl-O sidebar · Tab complete · ↑ history · F1 hint · Enter run diff --git a/src/snapshots/rdbms_playground__ui__tests__demo_caption_dark_90x26.snap b/src/snapshots/rdbms_playground__ui__tests__demo_caption_dark_90x26.snap index 1d2e68a..a14d17e 100644 --- a/src/snapshots/rdbms_playground__ui__tests__demo_caption_dark_90x26.snap +++ b/src/snapshots/rdbms_playground__ui__tests__demo_caption_dark_90x26.snap @@ -1,6 +1,6 @@ --- source: src/ui.rs -assertion_line: 3431 +assertion_line: 3434 expression: snapshot --- ╭ Output ────────────────────────────────────────────────────────────────────────────────╮ @@ -28,4 +28,4 @@ expression: snapshot │ │ ╰────────────────────────────────────────────────────────────────────────────────────────╯ Project: Term Planner -Ctrl-O sidebar · Tab complete · ↑ history · Enter run +Ctrl-O sidebar · Tab complete · ↑ history · F1 hint · Enter run diff --git a/src/snapshots/rdbms_playground__ui__tests__demo_caption_wrapped_90x26.snap b/src/snapshots/rdbms_playground__ui__tests__demo_caption_wrapped_90x26.snap index b3e064d..24a8f37 100644 --- a/src/snapshots/rdbms_playground__ui__tests__demo_caption_wrapped_90x26.snap +++ b/src/snapshots/rdbms_playground__ui__tests__demo_caption_wrapped_90x26.snap @@ -1,6 +1,6 @@ --- source: src/ui.rs -assertion_line: 3457 +assertion_line: 3460 expression: snapshot --- ╭ Output ────────────────────────────────────────────────────────────────────────────────╮ @@ -28,4 +28,4 @@ expression: snapshot │ │ ╰────────────────────────────────────────────────────────────────────────────────────────╯ Project: Term Planner -Ctrl-O sidebar · Tab complete · ↑ history · Enter run +Ctrl-O sidebar · Tab complete · ↑ history · F1 hint · Enter run diff --git a/src/snapshots/rdbms_playground__ui__tests__highlighted_input_all_token_classes_dark.snap b/src/snapshots/rdbms_playground__ui__tests__highlighted_input_all_token_classes_dark.snap index 0c1353e..ce720b2 100644 --- a/src/snapshots/rdbms_playground__ui__tests__highlighted_input_all_token_classes_dark.snap +++ b/src/snapshots/rdbms_playground__ui__tests__highlighted_input_all_token_classes_dark.snap @@ -1,6 +1,6 @@ --- source: src/ui.rs -assertion_line: 2880 +assertion_line: 2882 expression: snapshot --- ╭ Output ──────────────────────────────────────────────────────────────────────╮ @@ -26,4 +26,4 @@ expression: snapshot │insert into [([, ...])] [values] ([, ...]) │ ╰──────────────────────────────────────────────────────────────────────────────╯ Project: Term Planner -Esc clear · Ctrl-A/E home/end · Ctrl-W del word · Enter run +F1 hint · Esc clear · Ctrl-A/E home/end · Ctrl-W del word · Enter run diff --git a/src/snapshots/rdbms_playground__ui__tests__one_shot_advanced_dark.snap b/src/snapshots/rdbms_playground__ui__tests__one_shot_advanced_dark.snap index 99c972e..1d6a202 100644 --- a/src/snapshots/rdbms_playground__ui__tests__one_shot_advanced_dark.snap +++ b/src/snapshots/rdbms_playground__ui__tests__one_shot_advanced_dark.snap @@ -1,6 +1,6 @@ --- source: src/ui.rs -assertion_line: 2896 +assertion_line: 2898 expression: snapshot --- ╭ Output ──────────────────────────────────────────────────────────────────────╮ @@ -26,4 +26,4 @@ expression: snapshot │ │ ╰──────────────────────────────────────────────────────────────────────────────╯ Project: Term Planner -Esc clear · Ctrl-A/E home/end · Ctrl-W del word · Enter run +F1 hint · Esc clear · Ctrl-A/E home/end · Ctrl-W del word · Enter run diff --git a/src/snapshots/rdbms_playground__ui__tests__populated_with_table_dark.snap b/src/snapshots/rdbms_playground__ui__tests__populated_with_table_dark.snap index bebe44f..3956d34 100644 --- a/src/snapshots/rdbms_playground__ui__tests__populated_with_table_dark.snap +++ b/src/snapshots/rdbms_playground__ui__tests__populated_with_table_dark.snap @@ -1,6 +1,6 @@ --- source: src/ui.rs -assertion_line: 3099 +assertion_line: 3102 expression: snapshot --- ╭ Tables ──────────────────╮╭ Output ────────────────────────────────────────────────────────────────────────╮ @@ -26,4 +26,4 @@ expression: snapshot │ ││for SQL │ ╰──────────────────────────╯╰────────────────────────────────────────────────────────────────────────────────╯ Project: Term Planner -Ctrl-O sidebar · Tab complete · ↑ history · Enter run +Ctrl-O sidebar · Tab complete · ↑ history · F1 hint · Enter run diff --git a/src/snapshots/rdbms_playground__ui__tests__rebuild_confirm_modal_dark.snap b/src/snapshots/rdbms_playground__ui__tests__rebuild_confirm_modal_dark.snap index f396dff..dc29061 100644 --- a/src/snapshots/rdbms_playground__ui__tests__rebuild_confirm_modal_dark.snap +++ b/src/snapshots/rdbms_playground__ui__tests__rebuild_confirm_modal_dark.snap @@ -1,6 +1,6 @@ --- source: src/ui.rs -assertion_line: 2909 +assertion_line: 2912 expression: snapshot --- ╭ Output ──────────────────────────────────────────────────────────────────────╮ @@ -26,4 +26,4 @@ expression: snapshot │for SQL │ ╰──────────────────────────────────────────────────────────────────────────────╯ Project: Term Planner -Ctrl-O sidebar · Tab complete · ↑ history · Enter run +Ctrl-O sidebar · Tab complete · ↑ history · F1 hint · Enter run diff --git a/src/snapshots/rdbms_playground__ui__tests__relationships_panel_dark.snap b/src/snapshots/rdbms_playground__ui__tests__relationships_panel_dark.snap index 87afd3b..91ca731 100644 --- a/src/snapshots/rdbms_playground__ui__tests__relationships_panel_dark.snap +++ b/src/snapshots/rdbms_playground__ui__tests__relationships_panel_dark.snap @@ -1,6 +1,6 @@ --- source: src/ui.rs -assertion_line: 3209 +assertion_line: 3212 expression: snapshot --- ╭ Tables ──────────────────╮╭ Output ────────────────────────────────────────────────────────────────────────╮ @@ -26,4 +26,4 @@ expression: snapshot │ Orders.customer_id ││for SQL │ ╰──────────────────────────╯╰────────────────────────────────────────────────────────────────────────────────╯ Project: Term Planner -Ctrl-O sidebar · Tab complete · ↑ history · Enter run +Ctrl-O sidebar · Tab complete · ↑ history · F1 hint · Enter run diff --git a/src/snapshots/rdbms_playground__ui__tests__two_row_input_dark.snap b/src/snapshots/rdbms_playground__ui__tests__two_row_input_dark.snap index 88166eb..a490bcc 100644 --- a/src/snapshots/rdbms_playground__ui__tests__two_row_input_dark.snap +++ b/src/snapshots/rdbms_playground__ui__tests__two_row_input_dark.snap @@ -46,4 +46,4 @@ expression: snapshot │with `mode advanced`, or prefix the line with `:` to run… │ ╰──────────────────────────────────────────────────────────╯ Project: Term Planner -Esc clear · Ctrl-A/E home/end · Ctrl-W del word · Ente +F1 hint · Esc clear · Ctrl-A/E home/end · Ctrl-W del w diff --git a/src/ui.rs b/src/ui.rs index c50e95d..d2f6f25 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1894,22 +1894,25 @@ fn status_bar_bindings(app: &App) -> Vec<(&'static str, String)> { ("Enter", crate::t!("shortcut.run")), ]; } - // 4. Editing — the input has text: surface the readline edit keys - // (ADR-0049). The highest-value subset stays within the width - // budget; Ctrl-K/U remain unadvertised muscle memory. + // 4. Editing — the input has text: F1 (the contextual hint for what + // you're typing, ADR-0053) leads, then the readline edit keys + // (ADR-0049). Ctrl-K/U remain unadvertised muscle memory. if !app.input.is_empty() { return vec![ + ("F1", crate::t!("shortcut.hint")), ("Esc", crate::t!("shortcut.clear")), ("Ctrl-A/E", crate::t!("shortcut.home_end")), ("Ctrl-W", crate::t!("shortcut.del_word")), ("Enter", crate::t!("shortcut.run")), ]; } - // 5. Default — empty input, Input focus. + // 5. Default — empty input, Input focus. F1 here expands on the most + // recent error, or points the user at getting started (ADR-0053). vec![ ("Ctrl-O", crate::t!("shortcut.nav")), ("Tab", crate::t!("shortcut.complete")), ("↑", crate::t!("shortcut.history")), + ("F1", crate::t!("shortcut.hint")), ("Enter", crate::t!("shortcut.run")), ] } @@ -2664,7 +2667,7 @@ mod tests { #[test] fn strip_default_state_is_nav_complete_history_run() { let app = App::new(); - assert_eq!(strip_keys(&app), vec!["Ctrl-O", "Tab", "↑", "Enter"]); + assert_eq!(strip_keys(&app), vec!["Ctrl-O", "Tab", "↑", "F1", "Enter"]); } #[test] @@ -2675,7 +2678,7 @@ mod tests { app.input.push_str("create ta"); assert_eq!( strip_keys(&app), - vec!["Esc", "Ctrl-A/E", "Ctrl-W", "Enter"], + vec!["F1", "Esc", "Ctrl-A/E", "Ctrl-W", "Enter"], ); }