Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ae0eedd44 | |||
| 5f28de8ac3 | |||
| 888be16090 | |||
| 329adfc935 | |||
| 447112b17f | |||
| 984bc30256 | |||
| 417cbc8df9 | |||
| b6b98ad30f | |||
| 97970f2a2c | |||
| 9c4d520d5c |
@@ -108,6 +108,23 @@ Current decisions at a glance (each backed by an ADR):
|
||||
SQL `select` / `with` / `insert` / `update` / `delete`
|
||||
(ADR-0039). `EXPLAIN QUERY PLAN` never executes, so
|
||||
explaining a destructive command is safe.
|
||||
- **Continuous integration & release** (built on the `ci` branch,
|
||||
2026-06-15; decisions in `docs/ci/adr/` — **ADR-ci-001/002/003**,
|
||||
a namespace kept separate from the main ADR sequence to avoid
|
||||
cross-branch number collisions, like the website's): a self-hosted
|
||||
**Gitea Actions** pipeline built on a **nix flake** (pinned Rust
|
||||
`1.95.0` — one source of toolchain for dev *and* CI) plus a
|
||||
prebuilt CI image. **Gate** (`ci.yaml`): `clippy -D warnings` +
|
||||
`cargo test` on every branch push / PR. **Release** on a `v*` tag
|
||||
(`release.yaml`): the four non-macOS **D1** targets cross-built
|
||||
with `cargo-zigbuild` (Linux musl static + standalone Windows
|
||||
`.exe`); the two macOS targets via the **dispatched**
|
||||
`release-macos.yaml` on a Tart Apple-Silicon runner (de-nix the
|
||||
`libiconv` load path + ad-hoc re-sign). All published to a Gitea
|
||||
release with `.sha256`s. **`fmt` is intentionally not gated yet**
|
||||
(the tree isn't stock-`rustfmt`-clean). `workflow_dispatch` is
|
||||
Gitea-default-branch-only, so `release-macos` is dispatchable once
|
||||
this lands on `main`.
|
||||
|
||||
## Repository layout
|
||||
|
||||
@@ -344,8 +361,14 @@ not yet implemented:
|
||||
Ctrl-Enter submits.
|
||||
- **Tab completion** (I3), **syntax highlighting** (I4).
|
||||
- **ER diagram export** (V3).
|
||||
- **CI** (TT5): test infrastructure exists; CI workflow not
|
||||
yet configured.
|
||||
- **Full TT5** (CI): the pipeline is live (see the CI decision
|
||||
above / `docs/ci/adr/`), but "all tiers on all OSes" isn't
|
||||
complete — **Windows is build-only** (cross-compiled, not
|
||||
executed: no Windows runner) and **Tier 4** (PTY, TT4) isn't
|
||||
wired in CI.
|
||||
- **D3 packaging**: prebuilt binaries + checksums ship to Gitea
|
||||
releases, but the Homebrew / Scoop / winget / `cargo binstall`
|
||||
manifests are not done.
|
||||
|
||||
## Handoff notes
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -10,10 +18,13 @@ Phase B implementation (2026-06-15): the first exemplar showed per-*node*
|
||||
keying is too coarse for multi-form commands (`add`/`drop`/`show`/
|
||||
`create`), so D3 now keys tier-3 content **per form** via a
|
||||
`hint_ids: &[&str]` array mirroring `usage_ids` — and **clause-concept
|
||||
hints** are recorded as a deferred extension (separate tracking issue).
|
||||
The parallel question of whether the in-app `help` command should
|
||||
likewise distinguish advanced-SQL forms is tracked **separately** as
|
||||
Gitea issue #36 (it touches shipped, ADR-backed `help` behaviour).
|
||||
hints** are recorded as a deferred extension (issue #37). During Phase C
|
||||
the **pre-submit-diagnostic route + the ~33 `diagnostic.*` blocks** were
|
||||
**deferred** (issue #38) — `Diagnostic` doesn't carry its class key, so
|
||||
the route needs a broad change for marginal value (D6). v1 therefore
|
||||
ships command-form hints + the 9 runtime error-class hints. The parallel
|
||||
question of whether the in-app `help` command should likewise distinguish
|
||||
advanced-SQL forms is tracked **separately** as Gitea issue #36.
|
||||
|
||||
Decided in conversation 2026-06-14. Closes the last open piece of **A1**
|
||||
(the canonical app-command set, ADR-0003): every app command is
|
||||
@@ -102,7 +113,7 @@ into the output journal. (It must therefore be handled in `handle_key`
|
||||
|
||||
| Trigger | Buffer / state | Result |
|
||||
|---|---|---|
|
||||
| **F1** | non-empty input | tier-3 hint for the command being typed, plus the live "expected next" (from the walker's `tail_expected` / parser `expected`) |
|
||||
| **F1** | non-empty input | tier-3 hint for the command being typed. (No "expected next" line — the always-on tier-2 ambient panel already shows it live; tier-2 owns position-awareness.) |
|
||||
| **F1** | empty input, a recent error exists | tier-3 expansion of that error |
|
||||
| **F1** | empty input, no recent error | a short "getting started" pointer (press F1 while typing a command; `help` for the full list) |
|
||||
| **`hint`** (submitted) | a recent error exists | tier-3 expansion of that error (primary use) |
|
||||
@@ -112,19 +123,13 @@ F1 is inert behind a modal and while a sidebar panel holds navigation
|
||||
focus (consistent with the existing `handle_key` gates, ADR-0046); it is
|
||||
active in the input context in both Simple and Advanced mode.
|
||||
|
||||
**Two error sources, one namespace.** Errors come in two kinds and reach
|
||||
`hint` by different routes:
|
||||
|
||||
- **Pre-submit diagnostics** (the ~33 `diagnostic.*` classes — arity,
|
||||
type, unknown table/column) are computed *while typing* by the walker.
|
||||
The **F1 live-input path** reads the current under-cursor diagnostic
|
||||
directly from the walker (the same source the ambient panel uses) and
|
||||
renders its `hint.err.<class>` block — no stored state needed.
|
||||
- **Runtime errors** (the 9 `translate_error` classes) occur *after*
|
||||
submit. The **`hint` command / empty-input F1** path reads them via the
|
||||
stored `last_error_hint_key` (D5).
|
||||
|
||||
Both render from the same `hint.err.*` namespace. **`:`-prefix handling:**
|
||||
**Error routes.** **Runtime errors** (the 9 `translate_error` classes)
|
||||
occur *after* submit; the **`hint` command / empty-input F1** path reads
|
||||
them via the stored `last_error_hint_key` (D5) and renders their
|
||||
`hint.err.<class>` block. (A second route for **pre-submit diagnostics**
|
||||
on the F1 live-input path was specified but is **deferred** — D6 / issue
|
||||
#38; with a diagnostic present, F1 shows the command block and tier-2
|
||||
shows the diagnostic.) **`:`-prefix handling:**
|
||||
on the simple-mode one-shot escape (`: SELECT …`), command
|
||||
identification for the F1 path strips the leading `:` first, so the
|
||||
advanced form is matched.
|
||||
@@ -200,14 +205,17 @@ mechanics (e.g. `quit`); `what` + `example` are always present.
|
||||
|
||||
### D4 — Rendering
|
||||
|
||||
Both surfaces render through one new renderer, `App::note_hint*` (sibling
|
||||
of `note_help`/`note_help_topic`, `src/app.rs`), emitting a small framed
|
||||
block into the `output` buffer as `OutputKind::System` with
|
||||
`OutputStyleClass::Hint` on the `what`/`concept` prose and `Neutral` on
|
||||
the `example` line. The block is **persistent** (scrolls in the journal),
|
||||
unlike the transient ambient panel — pressing F1 is an explicit request
|
||||
to *keep* the deeper guidance on screen. The bottom keybinding strip
|
||||
(ADR-0051) advertises F1 in the editing/typing state.
|
||||
Both surfaces render through the `App::note_hint*` family (sibling of
|
||||
`note_help`/`note_help_topic`, `src/app.rs`) via `emit_tier3_block`,
|
||||
emitting into the `output` buffer as `OutputKind::System`: a **`Hint`
|
||||
heading** followed by aligned **`What:` / `Example:` / `Concept:`** lines
|
||||
(labels + heading from `hint.block.*`). The `concept` line is muted
|
||||
(`OutputStyleClass::Hint`); the rest are plain. The block is
|
||||
**persistent** (scrolls in the journal), unlike the transient ambient
|
||||
panel — pressing F1 is an explicit request to *keep* the deeper guidance
|
||||
on screen. Its rendered shape is locked by an `insta` snapshot
|
||||
(`hint_block_insert`). The bottom keybinding strip (ADR-0051) advertises
|
||||
F1 in the editing (leading) and default states.
|
||||
|
||||
### D5 — "Most recent (runtime) error" state
|
||||
|
||||
@@ -220,26 +228,37 @@ Option<String>`** — set at the `translate_error` call sites
|
||||
cleared when a later command succeeds. Absent → the "getting started"
|
||||
pointer.
|
||||
|
||||
The **pre-submit-diagnostic route** (the F1 live-input path) needs no
|
||||
stored state: it reads the current diagnostic from the walker at F1 time
|
||||
(D2). This is the cleaner split the `/runda` pass surfaced — typing-time
|
||||
diagnostics and post-submit runtime errors are genuinely different
|
||||
sources and should not be funnelled through one stored key.
|
||||
The **pre-submit-diagnostic route** (the F1 live-input path reading the
|
||||
under-cursor diagnostic) is **deferred** — see the scope note in D6.
|
||||
|
||||
### D6 — Content scope: comprehensive for v1
|
||||
### D6 — Content scope for v1
|
||||
|
||||
v1 ships tier-3 content for the **whole inventory**, not a subset (the
|
||||
graceful tier-2 fallback below is a safety net, not the plan):
|
||||
v1 ships tier-3 content for the **command forms and runtime error
|
||||
classes** — comprehensive for those (the graceful tier-2 fallback below
|
||||
is a safety net, not the plan):
|
||||
|
||||
- **~37 command forms** — every distinct node in `REGISTRY` gets its own
|
||||
`hint.cmd.<hint_id>` block (app + DSL + DDL + advanced-mode SQL forms),
|
||||
each with a **mode-correct example** (the advanced-SQL forms show SQL
|
||||
syntax, their simple siblings show DSL — no sharing).
|
||||
- **9 runtime error classes** — `unique`, `foreign_key` (×4 sides),
|
||||
`not_null`, `check`, `type_mismatch`, `not_found`, `already_exists`,
|
||||
`generic`, `invalid_value` — each gets a `hint.err.*` block.
|
||||
- **~33 `diagnostic.*` pre-submit classes** — arity, type, unknown
|
||||
table/column, etc. — each gets a `hint.err.*` block.
|
||||
- **9 runtime error classes** — `unique`, `foreign_key` (child/parent
|
||||
side), `not_null`, `check`, `type_mismatch`, `not_found`,
|
||||
`already_exists`, `generic`, `invalid_value` — each gets a
|
||||
`hint.err.*` block.
|
||||
|
||||
**Deferred — the ~33 `diagnostic.*` pre-submit classes and the F1
|
||||
diagnostic route** *(Phase C scope decision, 2026-06-15; issue #38)*. The
|
||||
original "comprehensive" scope included them, but implementation revealed
|
||||
`Diagnostic` (`walker/outcome.rs`) carries only its rendered `message`,
|
||||
not its class key — so a live diagnostic can't be mapped to
|
||||
`hint.err.<class>` without adding a `class` field threaded through every
|
||||
diagnostic-creation site (a broad change). Weighed against the value, it
|
||||
isn't worth it for v1: pre-submit diagnostics are already surfaced by
|
||||
tier-2 (ambient message + validity indicator, ADR-0027); F1 still shows
|
||||
the useful command block when a diagnostic is present; and many
|
||||
diagnostic classes duplicate runtime classes already covered
|
||||
(`type_mismatch`, `unknown_table`↔`not_found`, arity↔`invalid_value`).
|
||||
Deferred to issue #38, additively (the keying doesn't lock it out).
|
||||
|
||||
The full enumerated checklist is the implementation plan's tracking
|
||||
artifact (see *Content inventory*, below).
|
||||
@@ -262,32 +281,31 @@ maintainer owns, content is produced in two stages:
|
||||
**reviewable batches** (grouped by area: DDL, DML, app commands,
|
||||
error classes), not one monolithic drop.
|
||||
|
||||
### Exemplars (the style reference to approve)
|
||||
### Exemplars (the style reference; shipped as the rendered format)
|
||||
|
||||
**Command (F1 live-input), `insert`:**
|
||||
**Command (F1 live-input), `insert`** (the rendered shape, locked by the
|
||||
`hint_block_insert` snapshot — a `Hint` heading + aligned labels, no
|
||||
`Next:` line since tier-2 owns position-awareness):
|
||||
|
||||
```
|
||||
Hint — insert
|
||||
Hint
|
||||
What: Add one or more rows to a table.
|
||||
Example: insert into Customers values ('Ann', 'ann@x.io')
|
||||
Example: insert into Customers values ('Ann', 'ann@example.io')
|
||||
Concept: A row is one record; each value lines up with a column, in
|
||||
order. Columns typed serial/shortid fill themselves — leave
|
||||
them out.
|
||||
Next: a value list `(...)`, or `(col, ...) values (...)` to name columns
|
||||
```
|
||||
(The "Next:" line is the live expected-set from the walker, shown only on
|
||||
the non-empty-input F1 path.)
|
||||
|
||||
**Error (`hint` command), foreign-key child-side violation:**
|
||||
|
||||
```
|
||||
Hint — no parent row to point at
|
||||
What: The value you inserted into Orders.customer_id doesn't match
|
||||
any Customers row, so the foreign key has nothing to point at.
|
||||
Example: First insert into Customers values ('Ann', ...)
|
||||
Then insert into Orders values (..., 'Ann')
|
||||
Hint
|
||||
What: The value you gave for the child column doesn't match any
|
||||
parent row, so the foreign key has nothing to point at.
|
||||
Example: First insert the parent (insert into Customers …), then the
|
||||
child that references it.
|
||||
Concept: A foreign key is a promise that every child points at a real
|
||||
parent. The parent must exist first. To allow orphans on
|
||||
parent, so the parent must exist first. To allow orphans on
|
||||
delete instead, set the relationship's `on delete` to
|
||||
`set null` or `cascade`.
|
||||
```
|
||||
@@ -295,7 +313,7 @@ Hint — no parent row to point at
|
||||
**Command (F1 live-input), `add 1:n relationship`:**
|
||||
|
||||
```
|
||||
Hint — add relationship
|
||||
Hint
|
||||
What: Link two tables so a parent row can own many child rows.
|
||||
Example: add 1:n relationship from Customers.id to Orders.customer_id
|
||||
Concept: The "1:n" means one parent, many children. The child column
|
||||
@@ -339,26 +357,26 @@ Hint — add relationship
|
||||
`App` state (`last_error_hint_key`), and one new renderer family
|
||||
(`note_hint*`); the `AppCommand` enum gains `Hint`, the grammar a `HINT`
|
||||
node, the REGISTRY one entry.
|
||||
- **A large, durable content corpus** (~37 command blocks + ~42 error/
|
||||
diagnostic blocks ≈ 80) enters the catalogue under `hint.cmd.*` /
|
||||
- **A durable content corpus** (~37 command blocks + 10 runtime
|
||||
error-class blocks) enters the catalogue under `hint.cmd.*` /
|
||||
`hint.err.*`, validated by `keys.rs`. This is ongoing surface area: new
|
||||
commands/error classes should ship with their tier-3 hint (a checklist
|
||||
item for future feature ADRs).
|
||||
item for future feature ADRs). (Diagnostic-class blocks deferred — #38.)
|
||||
- **Testing:** Tier-1 unit tests for the trigger matrix (F1 with
|
||||
empty/non-empty input; `hint` with/without a recent error;
|
||||
`last_error_hint_key` set on the `translate_error` sites and cleared on
|
||||
success; the pre-submit-diagnostic vs runtime-error routing; the `:`
|
||||
strip), the command-identification logic, and the tier-2 fallback;
|
||||
Tier-2 `insta` snapshots for a representative rendered hint block;
|
||||
Tier-3 integration tests for the end-to-end flows (type a partial
|
||||
command → F1 → block appears, **buffer and completion memo untouched**;
|
||||
run a failing command → `hint` → error expansion). **A
|
||||
comprehensiveness coverage test** (enforces D6): iterate the REGISTRY
|
||||
and assert every node has a `hint_id` resolving to a `hint.cmd.*` block,
|
||||
and every runtime-error/diagnostic class has a `hint.err.*` block —
|
||||
`keys.rs` only checks that *referenced* keys resolve, not that every
|
||||
command/error *has* one, so this test is what makes "comprehensive"
|
||||
enforceable rather than aspirational.
|
||||
success; the mode-aware form resolution; the `:` strip), the
|
||||
command-identification logic, and the tier-2 fallback; Tier-2 `insta`
|
||||
snapshots for a representative rendered hint block; Tier-3 integration
|
||||
tests for the end-to-end flows (type a partial command → F1 → block
|
||||
appears, **buffer and completion memo untouched**; run a failing
|
||||
command → `hint` → error expansion). **A comprehensiveness coverage
|
||||
test** (enforces D6): iterate the REGISTRY and assert every node with a
|
||||
`hint_ids` entry resolves to a `hint.cmd.*` block, and every runtime
|
||||
error class resolves to a `hint.err.*` block — `keys.rs` only checks
|
||||
that *referenced* keys resolve, not that every command/error *has* one,
|
||||
so this test is what makes the scope enforceable rather than
|
||||
aspirational. (Diagnostic classes are out of this scope — D6 / #38.)
|
||||
|
||||
## Out of scope
|
||||
|
||||
@@ -381,6 +399,11 @@ Hint — add relationship
|
||||
recognized clause, deeper than tier-2's candidate list but narrower than
|
||||
the per-form block. Per-form keying (D3) does not lock it out. To be
|
||||
tackled as a deliberate follow-up job, not gated on usage statistics.
|
||||
- **Pre-submit-diagnostic route + `diagnostic.*` tier-3 blocks** — OOS
|
||||
(deferred, issue #38): needs a class field on `Diagnostic` threaded
|
||||
through every creation site (broad change) for marginal value, since
|
||||
tier-2 already surfaces diagnostics and many duplicate runtime classes
|
||||
(D6).
|
||||
|
||||
## Content inventory (implementation tracking)
|
||||
|
||||
@@ -401,4 +424,5 @@ The implementation plan enumerates and checks off every block:
|
||||
- **`hint.err.*`** — one per runtime error class (`unique`,
|
||||
`foreign_key.{child,parent}_side`, `not_null`, `check`,
|
||||
`type_mismatch`, `not_found`, `already_exists`, `generic`,
|
||||
`invalid_value`) and per `diagnostic.*` pre-submit class.
|
||||
`invalid_value`). The `diagnostic.*` pre-submit classes are **deferred**
|
||||
(D6 / issue #38).
|
||||
|
||||
+1
-1
@@ -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 <T>` 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<String>`) 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 (2026-06-14; Phase A done, Phase B underway)**. 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 <topic>` 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.<hint_id>` (per command form) and `hint.err.<class>` (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.<topic>`, issue **#37**) — per-form is the right tier-3 granularity, with position-awareness owned by tier-2 + the live `Next:` line. Two error routes share `hint.err.*`: pre-submit `diagnostic.*` read live from the walker (F1 path), runtime `translate_error` classes via stored `last_error_hint_key` (`hint` command / empty-F1). 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): **comprehensive for v1** — ~37 command forms + 9 runtime error classes + ~33 `diagnostic.*` classes ≈ 80 teaching blocks — authored **exemplars-first** (voice approved in this ADR's `/runda` review, then mass-authored in batches), enforced by a **comprehensiveness coverage test** (every node/error class has a key), with graceful fall-back to tier-2 if a key is ever missing. Forks user-chosen: two-surface model; **F1** (vs `?` / a chord); no-arg; comprehensive scope; exemplars-first. OOS: per-topic `hint <topic>` (rejected — overlaps `help`); always-on tier-3 (rejected — keeps ambient terse); non-`en-US` locales + success-command teaching (deferred); 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 <topic>` 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.<hint_id>` (per command form) and `hint.err.<class>` (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.<topic>`, 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 <topic>` (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)
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
# CI subproject handoff — 2026-06-15 (ci-01)
|
||||
|
||||
First handover for the **CI / release subproject** (the `ci` branch). Kept in
|
||||
`docs/ci/handoff/`, a namespace separate from the project's global
|
||||
`docs/handoff/` session sequence so it can't collide with `main`'s numbering —
|
||||
the same split as `docs/ci/adr/`, and needed for the same reason: `main`
|
||||
independently wrote its own **handoff-70** this same day (just as it took
|
||||
**ADR-0049**), which would have collided.
|
||||
|
||||
A dedicated infrastructure session that built the project's **entire CI/CD
|
||||
pipeline** on the self-hosted Gitea Actions runner — from nothing to a live
|
||||
gate plus a six-target cross-platform release. Net: the **CI** /
|
||||
`requirements.md` **TT5** item and **D1**/**D2** are now done; **D3** and a
|
||||
couple of TT5 tails remain. Decisions are recorded in the sibling ADR namespace
|
||||
**`docs/ci/adr/`** (ADR-ci-001/002/003).
|
||||
|
||||
## §1. State at handoff
|
||||
|
||||
**Branch:** `ci` (worktree). **`main` has been merged into `ci`** (commit
|
||||
`138e766`, clean — `ci` and `main` touched disjoint files) so the gate runs
|
||||
against current `main` before CI lands there. Working tree clean except the
|
||||
in-progress doc updates from this handoff. Pushes/promotion are the user's
|
||||
step.
|
||||
|
||||
**Gate verified locally on the merged code:** `clippy -D warnings` clean;
|
||||
**`cargo test` 2488 passing / 0 failing / 1 ignored** (the long-standing
|
||||
`friendly` doctest). main's features came in with their tests (2424 → 2488).
|
||||
|
||||
**Pipeline (`.gitea/workflows/`):**
|
||||
|
||||
- `build-ci-image.yaml` — builds + pushes the CI image (`node:22-bookworm-slim`
|
||||
+ single-user nix + the flake's devShell pre-warmed) to the Gitea registry.
|
||||
Triggers only on image-input changes (Dockerfile / flake / toolchain).
|
||||
- `ci.yaml` — the gate: `clippy -D warnings` + `cargo test`, branch pushes + PRs
|
||||
(docs-only changes skipped).
|
||||
- `release.yaml` — on a `v*` tag: `test` → `build` matrix over the **four
|
||||
non-macOS** targets via `cargo-zigbuild`, upload to the Gitea release.
|
||||
- `release-macos.yaml` — **workflow_dispatch** (tag input) on the Tart
|
||||
Apple-Silicon runner (`runs-on: macos`): test → build both `*-apple-darwin`
|
||||
→ de-nix `libiconv` + ad-hoc re-sign → upload.
|
||||
|
||||
**Verified live this session:** the 4-target release published **8 assets**
|
||||
(binary + `.sha256` each) for tag `v.0.0.0-citest3`; the macOS build was proven
|
||||
portable (system-only deps) + signed + launches on the runner.
|
||||
|
||||
## §2. What was built (and the non-obvious bits)
|
||||
|
||||
- **Nix flake** (ADR-ci-002, relocated from a would-be `main` ADR-0049): one
|
||||
pinned toolchain (`1.95.0`) for dev *and* CI; `cargo-zigbuild` + `zig` (Linux
|
||||
only) for the cross targets; `apple-sdk` on darwin.
|
||||
- **Runner facts** (ADR-ci-001): jobs run *inside* a container (`ci-public` →
|
||||
`catthehacker/ubuntu`), so host nix is unreachable — hence the baked image.
|
||||
The Mac runner is **host execution**; its label is `macos` (`:host` in the
|
||||
registration is the act_runner backend, not part of the label).
|
||||
- **Cross-compile** (ADR-ci-003): `cargo-zigbuild` for the 4 non-macOS targets.
|
||||
Windows needs an **empty `libsynchronization.a` stub** (`ci/winstub/`, wired
|
||||
via `.cargo/config.toml`) — std links `-lsynchronization`, absent from
|
||||
rust-overlay's toolchain + zig's mingw, but forwarded by `kernel32`.
|
||||
- **macOS** (ADR-ci-003 amendment): built on **real Apple hardware** (Tart), so
|
||||
the SDK is fully licensed — no osxcross grey area. The darwin stdenv bakes a
|
||||
`/nix/store` `libiconv` path into the binary; the build rewrites it to
|
||||
`/usr/lib/libiconv.2.dylib` (`install_name_tool`) and re-signs ad-hoc
|
||||
(`codesign -f -s -`; `install_name_tool` invalidates the signature, arm64
|
||||
refuses unsigned). A guard fails the build on any remaining `/nix/store` dep.
|
||||
- **Cache hygiene (Mac):** the runner wipes the workspace each run, so cargo
|
||||
`target/` never accumulates; the persistent nix store is bounded by
|
||||
**generation** (record the devShell in a persistent profile, keep the 2
|
||||
newest via `nix-env --delete-generations +2`, GC the rest). First sweep
|
||||
reclaimed a ~3.8 GB one-time backlog of build scaffolding (source + build-only
|
||||
deps, *not* re-installed toolchains).
|
||||
|
||||
## §3. Immediate next steps (user)
|
||||
|
||||
1. **Push `ci`** → the gate re-runs in CI (should be green; no image rebuild —
|
||||
the merge didn't touch the flake/Dockerfile).
|
||||
2. **Promote:** `git checkout main && git merge ci` — a **fast-forward** (`ci`
|
||||
already contains `main`) — then push `main`. CI goes live; `release-macos`
|
||||
becomes dispatchable (workflow_dispatch needs the default branch).
|
||||
3. **First real release:** tag `v0.1.0` (auto-builds the 4 Linux/Windows
|
||||
assets), then **dispatch `release-macos` for `v0.1.0`** with the Mac up (adds
|
||||
the 2 macOS assets) → a full 6-binary release.
|
||||
4. **Cleanup:** delete the `v.0.0.0-citest*` test tags + their releases.
|
||||
5. **Runner-side:** add `min-free`/`max-free` to the Mac's `/etc/nix/nix.conf`
|
||||
as a hands-off nix-store backstop.
|
||||
|
||||
## §4. Known gaps / follow-ups
|
||||
|
||||
- **Versioning is not wired into the binary** (flagged by the user). The release
|
||||
**git tag is nowhere in the produced binary** — there is no `--version` flag,
|
||||
no `CARGO_PKG_VERSION` use anywhere in `src/`, and the release workflows use
|
||||
the tag only for the *release name* + *asset filenames*
|
||||
(`rdbms-playground-<tag>-<target>`). `Cargo.toml` is a static `version =
|
||||
"0.1.0"`, decoupled from the tag. So a `v0.5.0` tag yields a `…-v0.5.0-…`
|
||||
asset whose binary knows nothing of "0.5.0". To fix later: add a `--version`
|
||||
flag, and inject the tag at build time (e.g. a `build.rs` reading a
|
||||
CI-provided env, or bumping `Cargo.toml` as part of tagging) so the binary and
|
||||
the release agree.
|
||||
- **D3 packaging** — Homebrew / Scoop / winget / `cargo binstall` manifests
|
||||
(asset naming is already binstall-friendly).
|
||||
- **TT5 tails** — Windows is build-only (no execution runner); Tier-4 PTY (TT4)
|
||||
is unwired in CI.
|
||||
- **`fmt` gate** — deliberately off (tree isn't stock-`rustfmt`-clean); revisit
|
||||
on `main`.
|
||||
- **Website → Cloudflare** deploy — the separate, simpler workflow, still to do.
|
||||
@@ -0,0 +1,21 @@
|
||||
# CI / Build subproject — session handoffs
|
||||
|
||||
Handover notes for the **CI / release pipeline** work (the Gitea Actions
|
||||
workflows under `.gitea/`, the nix flake, the release tooling). Kept in their
|
||||
own namespace, separate from the project-wide session handoffs in
|
||||
[`docs/handoff/`](../../handoff/), so a CI-branch handoff never competes with
|
||||
`main`'s global handoff sequence for numbers — the same split the CI ADRs use
|
||||
([`docs/ci/adr/`](../adr/README.md)). This is not hypothetical: `main`
|
||||
independently wrote a `handoff-70` the same day this subproject's first handoff
|
||||
was drafted.
|
||||
|
||||
**Numbering.** Files are named `<date>-handoff-ci-<NN>.md` and referenced in
|
||||
prose as `handoff-ci-NN`. Assign the next free `NN` from this index.
|
||||
|
||||
## Index
|
||||
|
||||
- [handoff-ci-01 — the CI/release pipeline build-out](20260615-handoff-ci-01.md)
|
||||
— Gitea Actions gate (clippy + test) + a six-target release (four via
|
||||
`cargo-zigbuild` on a `v*` tag, two macOS via dispatch on a Tart runner), all
|
||||
on a nix flake; decisions in `docs/ci/adr/`. Built on the `ci` branch, merged
|
||||
`main` in, gate green (2488 tests), ready to promote to `main`.
|
||||
@@ -0,0 +1,165 @@
|
||||
# Session handoff — 2026-06-15 (70)
|
||||
|
||||
Seventieth handover. Continues from handoff-69 (which closed the last
|
||||
four Gitea issues and left the tracker empty). This session did the
|
||||
**ADR-0052 follow-up** (unwinding vestigial worker `source` plumbing),
|
||||
then **designed and fully implemented H2 — the contextual `hint`
|
||||
command + F1 keybinding (ADR-0053)** end to end (Phases A–D). The CI
|
||||
branch was also merged into `main` mid-session (not my work — see §5).
|
||||
|
||||
Net: **2 feature areas shipped, 1 new ADR (0053) + 1 ADR amendment
|
||||
(0052), 4 new Gitea issues (#35–#38), the `hint` corpus (~57 teaching
|
||||
blocks), and A1 + H2 closed in `requirements.md`.**
|
||||
|
||||
## §1. State at handoff
|
||||
|
||||
**Branch:** `main`. Working tree **clean**; all work committed. Commits
|
||||
are local (push is the user's step).
|
||||
|
||||
**Tests: 2499 passing / 0 failing / 0 skipped / 1 ignored** (the
|
||||
long-standing `friendly` doctest). **Clippy clean** (nursery, all
|
||||
targets). Breakdown: 1799 lib + 500 `it` + 200 typing-surface-matrix.
|
||||
|
||||
**Open Gitea issues (4, all enhancement, all filed this session):**
|
||||
- **#35** — enforce `cargo fmt` across the codebase (single reformat +
|
||||
CI gate). The tree is *not* fmt-clean (~1800 pre-existing diffs); do it
|
||||
once, coordinated with CI, before first publication.
|
||||
- **#36** — `help` collapses advanced-SQL forms onto their simple sibling
|
||||
(a `help`-list dedup artifact); they deserve distinct help content.
|
||||
- **#37** — `hint` clause-concept hints (`on delete` actions, constraint
|
||||
slots, `with pk`, cardinality) — a deferred `hint.concept.<topic>`
|
||||
layer.
|
||||
- **#38** — `hint` pre-submit-diagnostic route + the ~33 `diagnostic.*`
|
||||
tier-3 blocks (deferred; `Diagnostic` carries no class key).
|
||||
|
||||
## §2. ADR-0052 follow-up — vestigial worker `source` unwind (`e8fa859`)
|
||||
|
||||
The first task from handoff-69 §3. ADR-0052 moved success-journaling out
|
||||
of the worker, leaving the `source` that handlers threaded purely for the
|
||||
old `history.log` write dead. **Bigger than the handoff estimated** (it
|
||||
framed it as ~28 call-site edits): the cascade ran through ~30 worker
|
||||
handlers + the `DescribeTable`/`QueryData`/`RunSelect` request fields +
|
||||
their `DatabaseHandle` methods (~164 mostly-test call sites). Fully
|
||||
unwound, compiler-guided, **no behaviour change** (journaling uses a
|
||||
`source_for_journal` clone at the spawn, independent of the worker). The
|
||||
only worker `source` left is the snapshot/undo label. Amended ADR-0052
|
||||
*Consequences* + README. (Two scope forks escalated + user-approved.)
|
||||
|
||||
## §3. H2 — contextual `hint` (ADR-0053), Phases A–D — **shipped**
|
||||
|
||||
The bulk of the session. ADR-0053 settles the `hint` slot ADR-0003 left
|
||||
"ADR pending"; **closes A1** (all 15 app commands now exist) and
|
||||
**requirements H2**. Read ADR-0053 before touching this area — it went
|
||||
through three revisions and several user decisions.
|
||||
|
||||
### The design (all user-chosen)
|
||||
- **Two surfaces:** an **F1 keybinding** → tier-3 hint for the *live*
|
||||
partial input (read-only overlay — never touches buffer/cursor/memo);
|
||||
a submitted **`hint` command** → expands on the *most recent runtime
|
||||
error*. No topic arg (contextual only; `help <topic>` owns reference).
|
||||
- **Tier-3 teaching layer** beneath the existing tier-1 (colour / error
|
||||
headline) and tier-2 (ambient one-liner; the error `hint:` shown **by
|
||||
default** since `Verbosity::Verbose` is the default). Each block is
|
||||
`what` / `example` / `concept`, rendered as a `Hint` heading + aligned
|
||||
labels.
|
||||
- **Per-form keying** (Phase-B revision — the original per-node `hint_id`
|
||||
was too coarse for multi-form commands like `add`/`drop`/`show`): a new
|
||||
**`hint_ids: &[&str]`** field on `CommandNode` mirroring `usage_ids`,
|
||||
resolved by `hint_key_for_input_in_mode` (reuses `usage_key`'s
|
||||
form-word disambiguation + a mode-primary fallback for shared entry
|
||||
words so advanced `insert` → `sql_insert`, simple → `insert`).
|
||||
- **Comprehensive for v1 = command forms + 9 runtime error classes**
|
||||
(the ~33 `diagnostic.*` classes were **deferred**, #38 — see §4).
|
||||
|
||||
### Key files
|
||||
- `src/dsl/command.rs` — `AppCommand::Hint`.
|
||||
- `src/dsl/grammar/app.rs` — `HINT` node + `build_hint`.
|
||||
- `src/dsl/grammar/mod.rs` — the `hint_ids` field, `hint_key_for_input_in_mode`,
|
||||
the factored `pick_form_key`, and the two **comprehensiveness coverage
|
||||
tests** (every node has a resolving `hint.cmd.*`; every runtime error
|
||||
class has a `hint.err.*`).
|
||||
- `src/app.rs` — F1 arm in `handle_key` (read-only overlay, placed before
|
||||
the completion-memo clear); `note_hint_for_input` / `note_hint_for_recent_error`
|
||||
/ `note_getting_started` / `emit_tier3_block`; `last_error_hint_key`
|
||||
state (set in `handle_dsl_failure`, cleared in `submit` for DSL
|
||||
commands).
|
||||
- `src/friendly/translate.rs` — `error_hint_class` (maps a `DbError` +
|
||||
ctx to its `hint.err.<class>`; mirrors `translate`'s dispatch — keep in
|
||||
sync, unit-tested).
|
||||
- `src/friendly/strings/en-US.yaml` + `keys.rs` — the corpus under
|
||||
`hint.cmd.<form>` / `hint.err.<class>` + `hint.block.*` labels +
|
||||
`shortcut.hint`.
|
||||
- `src/ui.rs` — ADR-0051 strip advertises **F1** (editing + default
|
||||
states); 12 full-panel snapshots re-accepted.
|
||||
|
||||
### Phases (one commit each unless noted)
|
||||
- **A** (`050b363`) skeleton + tier-2 fallback; **B** (`4a5fd1b`) per-form
|
||||
keying + 3 exemplars; **C** content in 5 batches (`4bdfce6` app,
|
||||
`6429b56` DDL, `9c4d520` DML, `97970f2` advanced-SQL, `b6b98ad` runtime
|
||||
errors) + `417cbc8` diagnostic deferral; **D** (`447112b`) coverage gate
|
||||
+ F1 strip + status flips; **/runda fix** (`329adfc`) — see §3.1.
|
||||
|
||||
### 3.1 — what the final `/runda` caught (don't skip)
|
||||
Per-batch substring tests masked a **presentation gap**: `emit_tier3_block`
|
||||
was emitting three *bare, unlabelled* lines, deviating from the approved
|
||||
exemplar format. Fixed to render a `Hint` heading + aligned `What:` /
|
||||
`Example:` / `Concept:` lines, **locked by an `insta` snapshot**
|
||||
(`hint_block_insert`). Also confirmed the `Next:` line (ADR D2 exemplar)
|
||||
is correctly **omitted** — tier-2 ambient already owns live
|
||||
position-awareness. Lesson for the next content/UI work: **add a rendered
|
||||
snapshot early**; substring asserts don't see layout.
|
||||
|
||||
## §4. Deferrals (all tracked, all user-confirmed)
|
||||
|
||||
- **#38 diagnostic route + `diagnostic.*` blocks** — `Diagnostic`
|
||||
(`walker/outcome.rs`) carries only its rendered `message`, not a class
|
||||
key, so the F1 diagnostic route would need a `class` field threaded
|
||||
through every diagnostic site (broad) for marginal value (tier-2
|
||||
already surfaces diagnostics; many duplicate runtime classes). F1 still
|
||||
shows the useful command block when a diagnostic is present.
|
||||
- **#37 clause-concept hints** — per-form is the right tier-3 granularity;
|
||||
clause-level concepts are a separate `hint.concept.<topic>` layer for
|
||||
later.
|
||||
- **#36 `help` advanced-SQL** — out of H2's scope (touches shipped `help`).
|
||||
|
||||
## §5. CI branch merged into `main` (not my work)
|
||||
|
||||
Mid-session the **`ci` branch was merged** (commits `47a0816`, `138e766`
|
||||
+ the `ci:`/`build:`/`docs(ci):` commits). `main` now carries a CI
|
||||
pipeline, a nix flake, and **D1 cross-platform release builds** (matrix +
|
||||
macOS), documented under a **new `docs/ci/adr/` namespace** (ci-001..003).
|
||||
Implications for the roadmap: **D1 (cross-platform binaries) is now
|
||||
substantially underway** — re-assess D1/D2/D3 status against what landed
|
||||
before treating them as open. My H2 work is layered cleanly on top (all
|
||||
green post-merge).
|
||||
|
||||
## §6. Next session — start here
|
||||
|
||||
1. **Push** (user step) — 30-odd local commits incl. the CI merge + all
|
||||
of H2.
|
||||
2. **Re-baseline the roadmap** against the merged CI work: D1/D2/D3 and
|
||||
**TT5 CI** are partly/largely done now — read `docs/ci/adr/` and the
|
||||
workflows before assuming they're open (handoff-69 §5 predates this).
|
||||
3. **#35 (cargo fmt gate)** is the natural pairing with the now-merged CI
|
||||
— the user wanted it done once, before first publication.
|
||||
4. Other `requirements.md` open items (verify against CI merge first):
|
||||
**TT4** PTY tier-4 (still unwired), **I1** multi-line input, **I5/B3**
|
||||
in-flight cancellation, **V4** session journal (own ADR), **TU1**
|
||||
tutorial system (own ADR). H2/A1 are now **done**.
|
||||
5. The H2 deferrals (#36/#37/#38) are available if the user wants to
|
||||
round out the hint/help surface.
|
||||
|
||||
## §7. How to take over
|
||||
|
||||
1. Read handoffs 68 → 69 → 70, `CLAUDE.md`, `docs/requirements.md`.
|
||||
2. Confirm green: `cargo test` (expect **2499 pass / 1 ignored**) +
|
||||
`cargo clippy --all-targets` (clean).
|
||||
3. Read `docs/ci/adr/` (the merged CI work) before touching CI/release/D*.
|
||||
4. For anything in the `hint` area, read **ADR-0053** first (3 revisions
|
||||
+ deferrals #37/#38). For journaling, ADR-0052 (+ its 2026-06-14
|
||||
follow-up note).
|
||||
5. Project workflow unchanged: phased, test-first, `/runda` + DA before
|
||||
commits, ADR amendment + README index-upkeep for decided-area changes,
|
||||
confirm commit messages with the user.
|
||||
6. Consider a `cargo sweep` at this milestone (`target/` grows; see
|
||||
CLAUDE.md "Build hygiene").
|
||||
+47
-11
@@ -61,11 +61,32 @@ since ADR-0027.)
|
||||
|
||||
## Distribution and install
|
||||
|
||||
- [ ] **D1** Cross-platform binaries: Linux, macOS, Windows on
|
||||
- [x] **D1** Cross-platform binaries: Linux, macOS, Windows on
|
||||
x86_64 and aarch64.
|
||||
- [ ] **D2** Single static binary, no runtime dependencies.
|
||||
*(Done 2026-06-15 — CI produces all six. The four non-macOS
|
||||
targets (Linux musl + Windows gnu/gnullvm × x86_64/aarch64) are
|
||||
cross-built from the Linux runner with `cargo-zigbuild` on a `v*`
|
||||
tag (`release.yaml`); the two `*-apple-darwin` targets build
|
||||
natively on a Tart Apple-Silicon runner via the dispatched
|
||||
`release-macos.yaml`. All uploaded to the Gitea release with a
|
||||
`.sha256` each. Decisions in `docs/ci/adr/` (ADR-ci-001/002/003).
|
||||
Runtime-verified by the user: Linux x86_64 + Windows aarch64; the
|
||||
others are link-clean / valid format.)*
|
||||
- [x] **D2** Single static binary, no runtime dependencies.
|
||||
*(Done 2026-06-15, per platform: **Linux** is fully static (musl +
|
||||
`crt-static`); **Windows** is a standalone `.exe` (Zig statically
|
||||
links libc — no mingw runtime DLLs); **macOS** links only system
|
||||
libraries (`libSystem` + the AppKit/Foundation frameworks —
|
||||
inherent on every Mac, never user-installed; the build rewrites the
|
||||
one nix-store `libiconv` path to `/usr/lib` and re-signs ad-hoc).
|
||||
No target requires anything the user must install. ADR-ci-003.)*
|
||||
- [ ] **D3** Released via prebuilt binaries plus Homebrew, Scoop,
|
||||
`winget`, and `cargo binstall`.
|
||||
*(Prebuilt binaries + checksums now published to Gitea releases
|
||||
(D1); the package-manager manifests (Homebrew / Scoop / winget /
|
||||
`cargo binstall`) remain to do. The asset naming
|
||||
`rdbms-playground-<tag>-<target>` is already binstall-friendly.
|
||||
Tracked under ADR-ci-003 "Deferred".)*
|
||||
|
||||
## TUI shell
|
||||
|
||||
@@ -250,16 +271,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 +811,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.<form>` / `hint.err.<class>` 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) —
|
||||
@@ -878,8 +904,18 @@ since ADR-0027.)
|
||||
PTY. Correcting a stale `CLAUDE.md` line that read "Tier 4 is
|
||||
wired only for the listed critical flows" — it was not wired at
|
||||
all. Genuinely deferred.)*
|
||||
- [ ] **TT5** CI runs all tiers on Linux, macOS, and Windows on
|
||||
- [/] **TT5** CI runs all tiers on Linux, macOS, and Windows on
|
||||
stable Rust.
|
||||
*(Partial, 2026-06-15. **CI is live** on the self-hosted Gitea
|
||||
Actions (`docs/ci/adr/`): the gate runs `clippy -D warnings` +
|
||||
`cargo test` (Tiers 1–3) on the **Linux** runner for every branch
|
||||
push / PR, and `release-macos` runs the suite natively on the
|
||||
**macOS** runner. **Windows is build-only** — cross-compiled, not
|
||||
executed (no Windows runner). **Tier 4** (PTY, TT4) is still
|
||||
unwired, so "all tiers" is not yet fully met. "Stable Rust" is
|
||||
satisfied by the flake's pinned `1.95.0` (a stable release, not
|
||||
nightly). Remaining for full TT5: a Windows execution runner and
|
||||
Tier-4 PTY in CI.)*
|
||||
|
||||
## Cross-cutting
|
||||
|
||||
|
||||
+88
-6
@@ -3205,17 +3205,32 @@ impl App {
|
||||
/// polish (the framed block) lands with the corpus.
|
||||
fn emit_tier3_block(&mut self, stem: &str) -> bool {
|
||||
let cat = crate::friendly::catalog();
|
||||
if cat.get(&format!("{stem}.what")).is_none() {
|
||||
let what_key = format!("{stem}.what");
|
||||
if cat.get(&what_key).is_none() {
|
||||
return false;
|
||||
}
|
||||
self.note_system(crate::friendly::translate(&format!("{stem}.what"), &[]));
|
||||
// Labelled block (ADR-0053 D4): a `Hint` heading, then aligned
|
||||
// `What:` / `Example:` / `Concept:` lines. `concept` renders
|
||||
// muted (`OutputStyleClass::Hint`); the rest are plain system.
|
||||
let labelled = |label: &str, value: &str| {
|
||||
// Pad `<Label>:` to a common width so the values align.
|
||||
format!(" {:<9}{value}", format!("{label}:"))
|
||||
};
|
||||
self.note_system(crate::t!("hint.block.heading"));
|
||||
self.note_system(labelled(
|
||||
&crate::t!("hint.block.what"),
|
||||
&crate::friendly::translate(&what_key, &[]),
|
||||
));
|
||||
if cat.get(&format!("{stem}.example")).is_some() {
|
||||
self.note_system(crate::friendly::translate(&format!("{stem}.example"), &[]));
|
||||
self.note_system(labelled(
|
||||
&crate::t!("hint.block.example"),
|
||||
&crate::friendly::translate(&format!("{stem}.example"), &[]),
|
||||
));
|
||||
}
|
||||
if cat.get(&format!("{stem}.concept")).is_some() {
|
||||
self.push_category_three_prose(crate::friendly::translate(
|
||||
&format!("{stem}.concept"),
|
||||
&[],
|
||||
self.push_category_three_prose(labelled(
|
||||
&crate::t!("hint.block.concept"),
|
||||
&crate::friendly::translate(&format!("{stem}.concept"), &[]),
|
||||
));
|
||||
}
|
||||
true
|
||||
@@ -5813,6 +5828,22 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// Locks the rendered tier-3 block format (ADR-0053 D4): a `Hint`
|
||||
/// heading + aligned `What:` / `Example:` / `Concept:` lines.
|
||||
#[test]
|
||||
fn insert_hint_block_renders_in_the_labelled_format() {
|
||||
let mut app = App::new();
|
||||
type_str(&mut app, "insert into Customers ");
|
||||
f1(&mut app);
|
||||
let block = app
|
||||
.output
|
||||
.iter()
|
||||
.map(|l| l.text.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
insta::assert_snapshot!("hint_block_insert", block);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f1_on_add_relationship_renders_the_relationship_block() {
|
||||
let mut app = App::new();
|
||||
@@ -5882,6 +5913,57 @@ mod tests {
|
||||
assert!(!output_contains(&app, "Remove a table"));
|
||||
}
|
||||
|
||||
// ── Phase C batch 3: DML hints render (incl. multi-form SHOW) ──
|
||||
|
||||
#[test]
|
||||
fn f1_on_update_renders_its_hint_block() {
|
||||
let mut app = App::new();
|
||||
type_str(&mut app, "update Customers set email = 'x' ");
|
||||
f1(&mut app);
|
||||
assert!(output_contains(&app, "Change values in the rows"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f1_disambiguates_show_forms() {
|
||||
let mut app = App::new();
|
||||
type_str(&mut app, "show relationships");
|
||||
f1(&mut app);
|
||||
assert!(output_contains(&app, "List all the relationships"));
|
||||
assert!(!output_contains(&app, "rows stored in a table"));
|
||||
}
|
||||
|
||||
// ── Phase C batch 4: advanced-SQL hints (mode-aware) ────────
|
||||
|
||||
#[test]
|
||||
fn f1_in_advanced_mode_renders_the_sql_insert_hint() {
|
||||
let mut app = App::new();
|
||||
app.mode = Mode::Advanced;
|
||||
type_str(&mut app, "insert into Customers (name) values ('x')");
|
||||
f1(&mut app);
|
||||
assert!(output_contains(&app, "Insert rows with SQL"));
|
||||
assert!(!output_contains(&app, "Add one or more rows"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f1_on_select_renders_the_select_hint() {
|
||||
let mut app = App::new();
|
||||
app.mode = Mode::Advanced;
|
||||
type_str(&mut app, "select name from Customers");
|
||||
f1(&mut app);
|
||||
assert!(output_contains(&app, "heart of SQL"));
|
||||
}
|
||||
|
||||
// ── Phase C batch 5: runtime error-class hints render ───────
|
||||
|
||||
#[test]
|
||||
fn hint_renders_a_runtime_error_block() {
|
||||
let mut app = App::new();
|
||||
app.last_error_hint_key = Some("unique".to_string());
|
||||
type_str(&mut app, "hint");
|
||||
submit(&mut app);
|
||||
assert!(output_contains(&app, "must be unique"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn messages_command_toggles_verbosity_and_reports() {
|
||||
let mut app = App::new();
|
||||
|
||||
+18
-12
@@ -1790,7 +1790,13 @@ pub static SHOW: CommandNode = CommandNode {
|
||||
shape: SHOW_SHAPE,
|
||||
ast_builder: build_show,
|
||||
help_id: Some("data.show"),
|
||||
hint_ids: &[],
|
||||
hint_ids: &[
|
||||
"show_data",
|
||||
"show_table",
|
||||
"show_tables",
|
||||
"show_relationships",
|
||||
"show_indexes",
|
||||
],
|
||||
usage_ids: &[
|
||||
"parse.usage.show_data",
|
||||
"parse.usage.show_table",
|
||||
@@ -1806,7 +1812,7 @@ pub static SEED: CommandNode = CommandNode {
|
||||
shape: SEED_SHAPE,
|
||||
ast_builder: build_seed,
|
||||
help_id: Some("data.seed"),
|
||||
hint_ids: &[],
|
||||
hint_ids: &["seed"],
|
||||
usage_ids: &["parse.usage.seed"],
|
||||
};
|
||||
|
||||
@@ -1824,7 +1830,7 @@ pub static UPDATE: CommandNode = CommandNode {
|
||||
shape: UPDATE_SHAPE,
|
||||
ast_builder: build_update,
|
||||
help_id: Some("data.update"),
|
||||
hint_ids: &[],
|
||||
hint_ids: &["update"],
|
||||
usage_ids: &["parse.usage.update"],};
|
||||
|
||||
pub static DELETE: CommandNode = CommandNode {
|
||||
@@ -1832,7 +1838,7 @@ pub static DELETE: CommandNode = CommandNode {
|
||||
shape: DELETE_SHAPE,
|
||||
ast_builder: build_delete,
|
||||
help_id: Some("data.delete"),
|
||||
hint_ids: &[],
|
||||
hint_ids: &["delete"],
|
||||
usage_ids: &["parse.usage.delete"],};
|
||||
|
||||
pub static REPLAY: CommandNode = CommandNode {
|
||||
@@ -1840,7 +1846,7 @@ pub static REPLAY: CommandNode = CommandNode {
|
||||
shape: REPLAY_PATH,
|
||||
ast_builder: build_replay,
|
||||
help_id: Some("data.replay"),
|
||||
hint_ids: &[],
|
||||
hint_ids: &["replay"],
|
||||
usage_ids: &["parse.usage.replay"],};
|
||||
|
||||
pub static EXPLAIN: CommandNode = CommandNode {
|
||||
@@ -1848,7 +1854,7 @@ pub static EXPLAIN: CommandNode = CommandNode {
|
||||
shape: EXPLAIN_SHAPE,
|
||||
ast_builder: build_explain,
|
||||
help_id: Some("data.explain"),
|
||||
hint_ids: &[],
|
||||
hint_ids: &["explain"],
|
||||
usage_ids: &["parse.usage.explain"],};
|
||||
|
||||
/// `explain` over advanced-mode SQL (ADR-0039).
|
||||
@@ -1868,7 +1874,7 @@ pub static EXPLAIN_SQL: CommandNode = CommandNode {
|
||||
// too). Mirrors the `SQL_INSERT`/`SQL_UPDATE`/`SQL_DELETE`
|
||||
// precedent; otherwise `note_help` would print `explain` twice.
|
||||
help_id: None,
|
||||
hint_ids: &[],
|
||||
hint_ids: &["explain_sql"],
|
||||
usage_ids: &[],};
|
||||
|
||||
/// SQL `SELECT` (ADR-0030 §6, ADR-0031, ADR-0032).
|
||||
@@ -1884,7 +1890,7 @@ pub static SELECT: CommandNode = CommandNode {
|
||||
shape: Node::Subgrammar(&sql_select::SQL_SELECT_TAIL),
|
||||
ast_builder: build_select,
|
||||
help_id: None,
|
||||
hint_ids: &[],
|
||||
hint_ids: &["select"],
|
||||
usage_ids: &["parse.usage.select"],};
|
||||
|
||||
/// `WITH …` top-level statement (ADR-0032 §4 / sub-phase 2c).
|
||||
@@ -1899,7 +1905,7 @@ pub static WITH: CommandNode = CommandNode {
|
||||
shape: Node::Subgrammar(&sql_select::SQL_WITH_TAIL),
|
||||
ast_builder: build_select,
|
||||
help_id: None,
|
||||
hint_ids: &[],
|
||||
hint_ids: &["with"],
|
||||
usage_ids: &["parse.usage.with"],};
|
||||
|
||||
/// SQL `INSERT` — the `Advanced`-category node of the shared
|
||||
@@ -1917,7 +1923,7 @@ pub static SQL_INSERT: CommandNode = CommandNode {
|
||||
shape: Node::Subgrammar(&sql_insert::SQL_INSERT_SHAPE),
|
||||
ast_builder: build_sql_insert,
|
||||
help_id: None,
|
||||
hint_ids: &[],
|
||||
hint_ids: &["sql_insert"],
|
||||
usage_ids: &[],
|
||||
};
|
||||
|
||||
@@ -1931,7 +1937,7 @@ pub static SQL_UPDATE: CommandNode = CommandNode {
|
||||
shape: Node::Subgrammar(&sql_update::SQL_UPDATE_SHAPE),
|
||||
ast_builder: build_sql_update,
|
||||
help_id: None,
|
||||
hint_ids: &[],
|
||||
hint_ids: &["sql_update"],
|
||||
usage_ids: &[],
|
||||
};
|
||||
|
||||
@@ -1947,7 +1953,7 @@ pub static SQL_DELETE: CommandNode = CommandNode {
|
||||
shape: Node::Subgrammar(&sql_delete::SQL_DELETE_SHAPE),
|
||||
ast_builder: build_sql_delete,
|
||||
help_id: None,
|
||||
hint_ids: &[],
|
||||
hint_ids: &["sql_delete"],
|
||||
usage_ids: &[],
|
||||
};
|
||||
|
||||
|
||||
@@ -1879,7 +1879,7 @@ pub static SQL_CREATE_TABLE: CommandNode = CommandNode {
|
||||
shape: Node::Subgrammar(&super::sql_create_table::SQL_CREATE_TABLE_SHAPE),
|
||||
ast_builder: build_sql_create_table,
|
||||
help_id: Some("ddl.sql_create_table"),
|
||||
hint_ids: &[],
|
||||
hint_ids: &["sql_create_table"],
|
||||
usage_ids: &["parse.usage.sql_create_table"],
|
||||
};
|
||||
|
||||
@@ -1899,7 +1899,7 @@ pub static SQL_DROP_TABLE: CommandNode = CommandNode {
|
||||
shape: SQL_DROP_TABLE_SHAPE,
|
||||
ast_builder: build_sql_drop_table,
|
||||
help_id: Some("ddl.sql_drop_table"),
|
||||
hint_ids: &[],
|
||||
hint_ids: &["sql_drop_table"],
|
||||
usage_ids: &["parse.usage.sql_drop_table"],
|
||||
};
|
||||
|
||||
@@ -1919,7 +1919,7 @@ pub static SQL_DROP_INDEX: CommandNode = CommandNode {
|
||||
shape: SQL_DROP_INDEX_SHAPE,
|
||||
ast_builder: build_sql_drop_index,
|
||||
help_id: Some("ddl.sql_drop_index"),
|
||||
hint_ids: &[],
|
||||
hint_ids: &["sql_drop_index"],
|
||||
usage_ids: &["parse.usage.sql_drop_index"],
|
||||
};
|
||||
|
||||
@@ -2001,7 +2001,7 @@ pub static SQL_CREATE_INDEX: CommandNode = CommandNode {
|
||||
shape: SQL_CREATE_INDEX_SHAPE,
|
||||
ast_builder: build_sql_create_index,
|
||||
help_id: Some("ddl.sql_create_index"),
|
||||
hint_ids: &[],
|
||||
hint_ids: &["sql_create_index"],
|
||||
usage_ids: &["parse.usage.sql_create_index"],
|
||||
};
|
||||
|
||||
@@ -2560,7 +2560,7 @@ pub static SQL_ALTER_TABLE: CommandNode = CommandNode {
|
||||
shape: SQL_ALTER_TABLE_SHAPE,
|
||||
ast_builder: build_sql_alter_table,
|
||||
help_id: Some("ddl.sql_alter_table"),
|
||||
hint_ids: &[],
|
||||
hint_ids: &["sql_alter_table"],
|
||||
usage_ids: &["parse.usage.sql_alter_table"],
|
||||
};
|
||||
|
||||
|
||||
+88
-1
@@ -616,10 +616,13 @@ pub fn usage_keys_for_input_in_mode(
|
||||
/// form has no tier-3 block yet (the caller falls back to tier-2).
|
||||
#[must_use]
|
||||
pub fn hint_key_for_input_in_mode(source: &str, mode: crate::mode::Mode) -> Option<&'static str> {
|
||||
use crate::dsl::walker::lex_helpers::{consume_ident, skip_whitespace};
|
||||
let nodes = selected_nodes_for_input_in_mode(source, mode);
|
||||
if nodes.is_empty() {
|
||||
return None;
|
||||
}
|
||||
// Mode-ordered union (advanced-primary first in advanced mode), so a
|
||||
// shared entry word resolves to the surface the user is in.
|
||||
let mut keys: Vec<&'static str> = Vec::new();
|
||||
for (_, node, _) in &nodes {
|
||||
for k in node.hint_ids {
|
||||
@@ -628,7 +631,25 @@ pub fn hint_key_for_input_in_mode(source: &str, mode: crate::mode::Mode) -> Opti
|
||||
}
|
||||
}
|
||||
}
|
||||
pick_form_key(source, &keys)
|
||||
if keys.is_empty() {
|
||||
return None;
|
||||
}
|
||||
if keys.len() == 1 {
|
||||
return Some(keys[0]);
|
||||
}
|
||||
// A bare multi-form entry word (no form word yet — `add`⏎) has no
|
||||
// chosen form: defer to tier-2, which lists the choices.
|
||||
let start = skip_whitespace(source, 0);
|
||||
if let Some((_, entry_end)) = consume_ident(source, start)
|
||||
&& skip_whitespace(source, entry_end) >= source.len()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
// A form word picks the form (`drop column` → `drop_column`); when
|
||||
// the second token isn't a form word (`insert into …`, `update …
|
||||
// set`), fall back to the mode-primary key — in advanced mode the
|
||||
// SQL form, in simple mode the DSL form.
|
||||
pick_form_key(source, &keys).or_else(|| keys.first().copied())
|
||||
}
|
||||
|
||||
/// Shared mode-aware command-form selection for the entry word at the
|
||||
@@ -922,9 +943,75 @@ mod hint_key_tests {
|
||||
hint_key_for_input_in_mode("drop table T", Mode::Simple),
|
||||
Some("drop_table")
|
||||
);
|
||||
// Mode picks the surface for a shared entry word whose second
|
||||
// token isn't a form word: SQL form in advanced, DSL in simple.
|
||||
assert_eq!(
|
||||
hint_key_for_input_in_mode("insert into T values (1)", Mode::Advanced),
|
||||
Some("sql_insert")
|
||||
);
|
||||
assert_eq!(
|
||||
hint_key_for_input_in_mode("insert into T values (1)", Mode::Simple),
|
||||
Some("insert")
|
||||
);
|
||||
// `create table` shares a form word — advanced-first ordering
|
||||
// resolves it to the SQL form in advanced mode.
|
||||
assert_eq!(
|
||||
hint_key_for_input_in_mode("create table T (id int)", Mode::Advanced),
|
||||
Some("sql_create_table")
|
||||
);
|
||||
// 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.<id>` 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.<class>` 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)]
|
||||
|
||||
@@ -224,6 +224,10 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
|
||||
),
|
||||
("hint.ambient_expected", &["expected"]),
|
||||
("hint.getting_started", &[]),
|
||||
("hint.block.heading", &[]),
|
||||
("hint.block.what", &[]),
|
||||
("hint.block.example", &[]),
|
||||
("hint.block.concept", &[]),
|
||||
// Tier-3 teaching blocks (ADR-0053 D3) — Phase-B exemplars.
|
||||
("hint.cmd.insert.what", &[]),
|
||||
("hint.cmd.insert.example", &[]),
|
||||
@@ -234,6 +238,32 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
|
||||
("hint.err.foreign_key.child_side.what", &[]),
|
||||
("hint.err.foreign_key.child_side.example", &[]),
|
||||
("hint.err.foreign_key.child_side.concept", &[]),
|
||||
// Phase C batch 5 — runtime error-class hints.
|
||||
("hint.err.foreign_key.parent_side.what", &[]),
|
||||
("hint.err.foreign_key.parent_side.example", &[]),
|
||||
("hint.err.foreign_key.parent_side.concept", &[]),
|
||||
("hint.err.unique.what", &[]),
|
||||
("hint.err.unique.example", &[]),
|
||||
("hint.err.unique.concept", &[]),
|
||||
("hint.err.not_null.what", &[]),
|
||||
("hint.err.not_null.example", &[]),
|
||||
("hint.err.not_null.concept", &[]),
|
||||
("hint.err.check.what", &[]),
|
||||
("hint.err.check.example", &[]),
|
||||
("hint.err.check.concept", &[]),
|
||||
("hint.err.type_mismatch.what", &[]),
|
||||
("hint.err.type_mismatch.example", &[]),
|
||||
("hint.err.type_mismatch.concept", &[]),
|
||||
("hint.err.not_found.what", &[]),
|
||||
("hint.err.not_found.example", &[]),
|
||||
("hint.err.not_found.concept", &[]),
|
||||
("hint.err.already_exists.what", &[]),
|
||||
("hint.err.already_exists.example", &[]),
|
||||
("hint.err.already_exists.concept", &[]),
|
||||
("hint.err.generic.what", &[]),
|
||||
("hint.err.generic.example", &[]),
|
||||
("hint.err.invalid_value.what", &[]),
|
||||
("hint.err.invalid_value.example", &[]),
|
||||
// Phase C batch 1 — app-lifecycle command hints.
|
||||
("hint.cmd.quit.what", &[]),
|
||||
("hint.cmd.quit.example", &[]),
|
||||
@@ -306,6 +336,70 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
|
||||
("hint.cmd.change_column.what", &[]),
|
||||
("hint.cmd.change_column.example", &[]),
|
||||
("hint.cmd.change_column.concept", &[]),
|
||||
// Phase C batch 3 — DML command hints.
|
||||
("hint.cmd.update.what", &[]),
|
||||
("hint.cmd.update.example", &[]),
|
||||
("hint.cmd.update.concept", &[]),
|
||||
("hint.cmd.delete.what", &[]),
|
||||
("hint.cmd.delete.example", &[]),
|
||||
("hint.cmd.delete.concept", &[]),
|
||||
("hint.cmd.show_data.what", &[]),
|
||||
("hint.cmd.show_data.example", &[]),
|
||||
("hint.cmd.show_data.concept", &[]),
|
||||
("hint.cmd.show_table.what", &[]),
|
||||
("hint.cmd.show_table.example", &[]),
|
||||
("hint.cmd.show_table.concept", &[]),
|
||||
("hint.cmd.show_tables.what", &[]),
|
||||
("hint.cmd.show_tables.example", &[]),
|
||||
("hint.cmd.show_relationships.what", &[]),
|
||||
("hint.cmd.show_relationships.example", &[]),
|
||||
("hint.cmd.show_relationships.concept", &[]),
|
||||
("hint.cmd.show_indexes.what", &[]),
|
||||
("hint.cmd.show_indexes.example", &[]),
|
||||
("hint.cmd.show_indexes.concept", &[]),
|
||||
("hint.cmd.seed.what", &[]),
|
||||
("hint.cmd.seed.example", &[]),
|
||||
("hint.cmd.seed.concept", &[]),
|
||||
("hint.cmd.explain.what", &[]),
|
||||
("hint.cmd.explain.example", &[]),
|
||||
("hint.cmd.explain.concept", &[]),
|
||||
("hint.cmd.replay.what", &[]),
|
||||
("hint.cmd.replay.example", &[]),
|
||||
("hint.cmd.replay.concept", &[]),
|
||||
// Phase C batch 4 — advanced-mode SQL command hints.
|
||||
("hint.cmd.sql_create_table.what", &[]),
|
||||
("hint.cmd.sql_create_table.example", &[]),
|
||||
("hint.cmd.sql_create_table.concept", &[]),
|
||||
("hint.cmd.sql_alter_table.what", &[]),
|
||||
("hint.cmd.sql_alter_table.example", &[]),
|
||||
("hint.cmd.sql_alter_table.concept", &[]),
|
||||
("hint.cmd.sql_create_index.what", &[]),
|
||||
("hint.cmd.sql_create_index.example", &[]),
|
||||
("hint.cmd.sql_create_index.concept", &[]),
|
||||
("hint.cmd.sql_drop_index.what", &[]),
|
||||
("hint.cmd.sql_drop_index.example", &[]),
|
||||
("hint.cmd.sql_drop_index.concept", &[]),
|
||||
("hint.cmd.sql_drop_table.what", &[]),
|
||||
("hint.cmd.sql_drop_table.example", &[]),
|
||||
("hint.cmd.sql_drop_table.concept", &[]),
|
||||
("hint.cmd.sql_insert.what", &[]),
|
||||
("hint.cmd.sql_insert.example", &[]),
|
||||
("hint.cmd.sql_insert.concept", &[]),
|
||||
("hint.cmd.sql_update.what", &[]),
|
||||
("hint.cmd.sql_update.example", &[]),
|
||||
("hint.cmd.sql_update.concept", &[]),
|
||||
("hint.cmd.sql_delete.what", &[]),
|
||||
("hint.cmd.sql_delete.example", &[]),
|
||||
("hint.cmd.sql_delete.concept", &[]),
|
||||
("hint.cmd.select.what", &[]),
|
||||
("hint.cmd.select.example", &[]),
|
||||
("hint.cmd.select.concept", &[]),
|
||||
("hint.cmd.with.what", &[]),
|
||||
("hint.cmd.with.example", &[]),
|
||||
("hint.cmd.with.concept", &[]),
|
||||
("hint.cmd.explain_sql.what", &[]),
|
||||
("hint.cmd.explain_sql.example", &[]),
|
||||
("hint.cmd.explain_sql.concept", &[]),
|
||||
(
|
||||
"hint.ambient_invalid_ident",
|
||||
&["kind", "found"],
|
||||
@@ -557,6 +651,7 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
|
||||
("shortcut.confirm", &[]),
|
||||
("shortcut.cycle", &[]),
|
||||
("shortcut.del_word", &[]),
|
||||
("shortcut.hint", &[]),
|
||||
("shortcut.history", &[]),
|
||||
("shortcut.home_end", &[]),
|
||||
("shortcut.load", &[]),
|
||||
|
||||
@@ -391,6 +391,13 @@ hint:
|
||||
# H2 / ADR-0053: shown by `hint` / F1 when there is nothing specific
|
||||
# to expand on (no recent error, empty input).
|
||||
getting_started: "Start typing a command and press F1 for a hint, or type `help` for the full command list."
|
||||
# Tier-3 block scaffolding (ADR-0053 D4): the heading + the labels the
|
||||
# `what` / `example` / `concept` parts render under.
|
||||
block:
|
||||
heading: "Hint"
|
||||
what: "What"
|
||||
example: "Example"
|
||||
concept: "Concept"
|
||||
# ── Tier-3 teaching blocks (ADR-0053 D3) ──────────────────────────
|
||||
# Per-form command hints (`hint.cmd.<form>`) and per-class error
|
||||
# hints (`hint.err.<class>`), each a `what` (1–2 sentences) / `example`
|
||||
@@ -505,12 +512,135 @@ hint:
|
||||
what: "Change a column's type, converting the existing values."
|
||||
example: "change column Customers: status (int)"
|
||||
concept: "The database converts each stored value to the new type; if a value can't convert it refuses the change, so you don't silently lose data. Flags let you force or skip the conversion."
|
||||
# DML — querying and changing data (Phase C batch 3).
|
||||
update:
|
||||
what: "Change values in the rows that match a condition."
|
||||
example: "update Customers set email = 'new@example.io' where id = 1"
|
||||
concept: "The `where` clause picks which rows change, and it's required — pass `--all-rows` to change the whole table on purpose — so you never update more than you meant to."
|
||||
delete:
|
||||
what: "Remove the rows that match a condition."
|
||||
example: "delete from Orders where status = 'cancelled'"
|
||||
concept: "A `where` is required (use `--all-rows` to clear the table on purpose). Rows a relationship points at may be blocked or cascade-deleted, per its `on delete` action."
|
||||
show_data:
|
||||
what: "Show the rows stored in a table."
|
||||
example: "show data Customers"
|
||||
concept: "This reads the data and never changes it. Add a `where` to show only matching rows."
|
||||
show_table:
|
||||
what: "Show a table's structure — its columns, types, keys, and relationships."
|
||||
example: "show table Customers"
|
||||
concept: "Structure, not data: the column definitions and how this table links to others. Use `show data` to see the rows themselves."
|
||||
show_tables:
|
||||
what: "List all the tables in the project."
|
||||
example: "show tables"
|
||||
show_relationships:
|
||||
what: "List all the relationships between tables."
|
||||
example: "show relationships"
|
||||
concept: "Each relationship is a foreign-key link from a child column to a parent's key, with an `on delete` / `on update` rule."
|
||||
show_indexes:
|
||||
what: "List all the indexes in the project."
|
||||
example: "show indexes"
|
||||
concept: "Indexes speed up lookups; this shows which columns each one covers and whether it enforces uniqueness."
|
||||
seed:
|
||||
what: "Fill a table with generated sample rows, or fill one column on existing rows."
|
||||
example: "seed Customers 50"
|
||||
concept: "Seeding invents realistic-looking data so you have something to query. Pin a value with `set col = …`, choose a generator with `as`, or give a numeric range with `between`."
|
||||
explain:
|
||||
what: "Show how the database will run a query — without running it."
|
||||
example: "explain show data Customers where email = 'a@example.io'"
|
||||
concept: "The plan reveals whether the database scans the whole table or jumps straight to rows through an index — the payoff of `add index`. `explain` never executes, so it's safe even on a delete."
|
||||
replay:
|
||||
what: "Re-run the commands recorded in a history file."
|
||||
example: "replay session.log"
|
||||
concept: "Every successful command is journalled, so replaying re-applies them in order to reproduce a project's state — handy for scripting or redoing a sequence."
|
||||
# Advanced-mode SQL forms (Phase C batch 4). Examples are SQL, the
|
||||
# advanced surface — distinct from their simple-mode siblings.
|
||||
sql_create_table:
|
||||
what: "Create a table using SQL syntax (advanced mode)."
|
||||
example: "create table Customers (id int primary key, name text, email text)"
|
||||
concept: "Advanced mode speaks SQL: constraints go inline (`primary key`, `not null`, `unique`, `check`). This is the raw form of simple mode's `create table … with pk …`."
|
||||
sql_alter_table:
|
||||
what: "Change a table's structure with SQL `alter table` (advanced mode)."
|
||||
example: "alter table Customers add column phone text"
|
||||
concept: "`alter table` adds or drops columns, renames, and adds constraints — the SQL equivalent of simple mode's `add column` / `drop column` / `change column`."
|
||||
sql_create_index:
|
||||
what: "Create an index with SQL (advanced mode)."
|
||||
example: "create index ix_email on Customers (email)"
|
||||
concept: "Add `unique` to also forbid duplicate values. The simple-mode equivalent is `add index`."
|
||||
sql_drop_index:
|
||||
what: "Remove an index with SQL (advanced mode)."
|
||||
example: "drop index ix_email"
|
||||
concept: "Only the lookup shortcut goes; the data is untouched. Add `if exists` to ignore a missing index."
|
||||
sql_drop_table:
|
||||
what: "Remove a table with SQL (advanced mode)."
|
||||
example: "drop table Customers"
|
||||
concept: "Add `if exists` to avoid an error when the table might not be there. Relationships pointing at it may block the drop."
|
||||
sql_insert:
|
||||
what: "Insert rows with SQL (advanced mode)."
|
||||
example: "insert into Customers (name, email) values ('Ann', 'ann@example.io')"
|
||||
concept: "Naming the columns lets you supply them in any order and skip ones that have a default — the SQL form of simple mode's `insert`."
|
||||
sql_update:
|
||||
what: "Update rows with SQL (advanced mode)."
|
||||
example: "update Customers set email = 'new@example.io' where id = 1"
|
||||
concept: "`set` lists the new values; `where` picks which rows change. The SQL form of simple mode's `update`."
|
||||
sql_delete:
|
||||
what: "Delete rows with SQL (advanced mode)."
|
||||
example: "delete from Orders where status = 'cancelled'"
|
||||
concept: "`where` picks the rows to remove; foreign-key rules still apply. The SQL form of simple mode's `delete`."
|
||||
select:
|
||||
what: "Query rows with SQL `select` (advanced mode)."
|
||||
example: "select name, email from Customers where id = 1"
|
||||
concept: "`select` is read-only: choose columns (or `*`), filter with `where`, sort with `order by`, cap with `limit`. This is the heart of SQL — and the reason advanced mode exists."
|
||||
with:
|
||||
what: "Name a sub-query (a CTE) and read from it in a `select` (advanced mode)."
|
||||
example: "with recent as (select * from Orders where id > 100) select * from recent"
|
||||
concept: "A `with` clause (Common Table Expression) names a query so the main `select` can use it like a temporary table — handy for breaking a complex query into readable steps."
|
||||
explain_sql:
|
||||
what: "Show how the database will run a SQL query, without running it (advanced mode)."
|
||||
example: "explain select * from Customers where email = 'a@example.io'"
|
||||
concept: "Like simple mode's `explain`, but wraps a raw SQL statement. It reveals whether an index is used, and never executes."
|
||||
err:
|
||||
# Runtime error classes (Phase C batch 5), keyed by
|
||||
# friendly::error_hint_class. `example` is a fix recipe rather than a
|
||||
# runnable line; `concept` is the relational idea behind the rule.
|
||||
foreign_key:
|
||||
child_side:
|
||||
what: "The value you gave for the child column doesn't match any parent row, so the foreign key has nothing to point at."
|
||||
example: "First insert the parent (insert into Customers …), then the child that references it."
|
||||
concept: "A foreign key is a promise that every child points at a real parent, so the parent must exist first. To allow orphans on delete instead, set the relationship's `on delete` to `set null` or `cascade`."
|
||||
parent_side:
|
||||
what: "You're deleting or changing a row that other rows point at, which would orphan those children."
|
||||
example: "Delete the child rows first, or set the relationship's `on delete` to `cascade` (remove them too) or `set null` (keep them, unlinked)."
|
||||
concept: "A foreign key guarantees every child has a real parent, so the database won't remove a parent out from under its children unless the relationship says what should happen to them."
|
||||
unique:
|
||||
what: "A value you're inserting — or updating to — already exists in a column that must be unique."
|
||||
example: "Pick a different value, or update the existing row instead of inserting a new one."
|
||||
concept: "A unique constraint (and every primary key) forbids duplicates, so each value identifies at most one row."
|
||||
not_null:
|
||||
what: "You left a column empty that is required to have a value."
|
||||
example: "Supply a value for the column, or give it a default so new rows fill it automatically."
|
||||
concept: "A `not null` constraint means every row must have a value there — it's how you mark a fact as mandatory."
|
||||
check:
|
||||
what: "A value broke a `check` rule defined on the column."
|
||||
example: "Use a value the rule allows — for example a positive number, or one of the permitted options."
|
||||
concept: "A `check` constraint is a condition every row must satisfy, so the database enforces business rules like \"price ≥ 0\" for you."
|
||||
type_mismatch:
|
||||
what: "A value doesn't fit the column's type — for instance text where a number is expected."
|
||||
example: "Give a value of the right type: a number for `int`/`real`, a quoted string for `text`, true/false for `bool`."
|
||||
concept: "Every column has a type, and the database rejects values that don't fit, so a column's data stays consistent and comparable."
|
||||
not_found:
|
||||
what: "You named a table or column that doesn't exist."
|
||||
example: "Check the spelling, or run `show tables` (or `show table <name>`) to see what's there."
|
||||
concept: "A command can only refer to tables and columns that already exist — create them first if you need them."
|
||||
already_exists:
|
||||
what: "You tried to create a table, column, relationship, or index whose name is already taken."
|
||||
example: "Pick a different name, or drop the existing one first if you meant to replace it."
|
||||
concept: "Names must be unique within their kind so a command is never ambiguous about what it refers to."
|
||||
generic:
|
||||
what: "The database refused the command for the reason shown above."
|
||||
example: "Read that message for the specifics, adjust the command, and try again."
|
||||
invalid_value:
|
||||
what: "A value or option in the command wasn't valid for where it was used."
|
||||
example: "Check the value against the column's type and the command's accepted options."
|
||||
# Invalid identifier in a schema slot (ADR-0022 stage 8e
|
||||
# + the user's #5). Voice mirrors ADR-0019's "no such
|
||||
# {kind}" wording for consistency with engine errors.
|
||||
@@ -1042,6 +1172,7 @@ shortcut:
|
||||
browse: "browse"
|
||||
clear: "clear"
|
||||
complete: "complete"
|
||||
hint: "hint"
|
||||
history: "history"
|
||||
home_end: "home/end"
|
||||
del_word: "del word"
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
source: src/app.rs
|
||||
assertion_line: 5844
|
||||
expression: block
|
||||
---
|
||||
Hint
|
||||
What: Add one or more rows to a table.
|
||||
Example: insert into Customers values ('Ann', 'ann@example.io')
|
||||
Concept: A row is one record; each value lines up with a column, in order. Columns typed `serial`/`shortid` fill themselves — leave them out.
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+2
-2
@@ -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 <Table> [(<col>[, ...])] [values] (<value>[, ...]) │
|
||||
╰──────────────────────────────────────────────────────────────────────────────╯
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user