10 Commits

Author SHA1 Message Date
claude@clouddev1 8ae0eedd44 Merge branch 'ci'
ci / gate (push) Successful in 3m7s
release / test (push) Successful in 2m43s
release / build (aarch64-pc-windows-gnullvm) (push) Successful in 4m17s
release / build (aarch64-unknown-linux-musl) (push) Successful in 4m15s
release / build (x86_64-pc-windows-gnu) (push) Successful in 4m48s
release / build (x86_64-unknown-linux-musl) (push) Successful in 4m10s
2026-06-15 16:57:18 +00:00
claude@clouddev1 5f28de8ac3 docs(ci): move CI handoff into docs/ci/handoff (avoid main collision)
main independently wrote its own docs/handoff/20260615-handoff-70.md the
same day, so my global-sequence handoff-70 was an add/add conflict waiting
to merge. Relocate it to docs/ci/handoff/20260615-handoff-ci-01.md (its own
namespace, like docs/ci/adr) + a README index. main's handoff-70 is
untouched; the merge becomes conflict-free.
2026-06-15 16:56:50 +00:00
claude@clouddev1 888be16090 docs: handoff 70 — ADR-0052 follow-up + H2 hint shipped (ADR-0053)
ADR-0052 vestigial-source unwind; H2 contextual hint (F1 keybinding +
`hint` command) fully implemented Phases A–D, closing A1 + H2. 4 issues
filed (#35–#38, incl. 3 hint/help deferrals). CI branch merged into main
mid-session (D1 release work now on main). 2499 pass / 1 ignored, clippy
clean.
2026-06-15 16:47:18 +00:00
claude@clouddev1 329adfc935 fix(hint): labelled tier-3 block format + snapshot (ADR-0053 /runda)
Final /runda found the rendered block was three bare unlabelled lines,
deviating from the approved exemplar format. Fix:
- emit_tier3_block now renders a `Hint` heading + aligned `What:` /
  `Example:` / `Concept:` lines (hint.block.* labels); concept stays muted
- lock the format with an insta snapshot (hint_block_insert)
- amend ADR-0053 D2/D4 + exemplars: drop the `Next:` line (tier-2 ambient
  already owns live position-awareness — user-confirmed), align exemplars
  to the shipped format

2499 pass / 1 ignored, clippy clean.
2026-06-15 16:45:47 +00:00
claude@clouddev1 447112b17f feat(hint): H2 Phase D — coverage gate, F1 strip, status flips (ADR-0053)
Completes H2:
- comprehensiveness coverage tests: every REGISTRY command form has a
  hint_id resolving to a hint.cmd.* block, and every runtime error class
  resolves to a hint.err.* block (enforces ADR-0053 D6)
- ADR-0051 keybinding strip advertises F1 in the editing (leads) and
  default states; +shortcut.hint label; 12 full-panel snapshots
  re-accepted (status-bar line only)
- flip ADR-0053 -> implemented, requirements H2 + A1 -> [x], README

2498 pass / 1 ignored, clippy clean.
2026-06-15 16:34:10 +00:00
claude@clouddev1 984bc30256 docs: record CI branch work — D1/D2 done, TT5 partial, handoff 70
requirements.md: D1 (all six cross-platform binaries) and D2 (no-runtime-
deps, per-platform) done; D3 noted (binaries shipped, package managers
pending); TT5 partial (gate + macOS test live; Windows build-only; Tier-4
unwired). CLAUDE.md: add the CI/release decision (-> docs/ci/adr) + update
the deferred list. Adds handoff 70 summarising the pipeline + follow-ups
(incl. the versioning gap).
2026-06-15 16:31:58 +00:00
claude@clouddev1 417cbc8df9 docs(hint): defer pre-submit-diagnostic route + diagnostic.* blocks (ADR-0053)
Phase C scope decision: Diagnostic carries no class key, so the F1
diagnostic route would need a class field threaded through every
diagnostic site (broad change) for marginal value — tier-2 already
surfaces diagnostics and many duplicate the runtime error classes. Defer
the route + the ~33 diagnostic.* tier-3 blocks to issue #38. v1 ships
command-form hints + 9 runtime error-class hints (comprehensive for
those). Updates ADR-0053 D2/D6/Status/OOS + README.
2026-06-15 16:28:54 +00:00
claude@clouddev1 b6b98ad30f feat(hint): H2 Phase C batch 5 — runtime error-class tier-3 hints (ADR-0053)
what/example(fix recipe)/concept for the 9 runtime error classes:
foreign_key parent_side (child_side was the exemplar), unique, not_null,
check, type_mismatch, not_found, already_exists, generic, invalid_value.
Keyed by friendly::error_hint_class; catalogue + keys.rs registered.
+1 spot test; 2496 pass / 1 ignored, clippy clean.
2026-06-15 16:16:49 +00:00
claude@clouddev1 97970f2a2c feat(hint): H2 Phase C batch 4 — advanced-mode SQL tier-3 hints (ADR-0053)
Distinct SQL-syntax hints for the 11 advanced-mode forms: sql create
table / alter table / create index / drop index / drop table / insert /
update / delete, select, with, explain. hint_ids wired on all 11 nodes.

Hardened hint_key_for_input_in_mode for shared entry words: a bare
multi-form entry word defers to tier-2; when the second token isn't a
form word (insert into / update … set), it falls back to the
mode-primary key — so advanced mode resolves to the SQL form and simple
mode to the DSL form. catalogue + keys.rs registered. +2 spot tests +
grammar mode-disambiguation asserts; 2495 pass / 1 ignored, clippy clean.
2026-06-15 16:14:23 +00:00
claude@clouddev1 9c4d520d5c feat(hint): H2 Phase C batch 3 — DML tier-3 hints (ADR-0053)
Per-form hints for querying/changing data: update, delete, show
data/table/tables/relationships/indexes, seed, explain, replay
(insert was the Phase-B exemplar). hint_ids wired on UPDATE/DELETE/
SHOW/SEED/EXPLAIN/REPLAY; catalogue + keys.rs registered. +2 spot
tests (incl. multi-form SHOW disambiguation); 2493 pass / 1 ignored,
clippy clean.
2026-06-15 16:08:57 +00:00
29 changed files with 927 additions and 141 deletions
+25 -2
View File
@@ -108,6 +108,23 @@ Current decisions at a glance (each backed by an ADR):
SQL `select` / `with` / `insert` / `update` / `delete` SQL `select` / `with` / `insert` / `update` / `delete`
(ADR-0039). `EXPLAIN QUERY PLAN` never executes, so (ADR-0039). `EXPLAIN QUERY PLAN` never executes, so
explaining a destructive command is safe. 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 ## Repository layout
@@ -344,8 +361,14 @@ not yet implemented:
Ctrl-Enter submits. Ctrl-Enter submits.
- **Tab completion** (I3), **syntax highlighting** (I4). - **Tab completion** (I3), **syntax highlighting** (I4).
- **ER diagram export** (V3). - **ER diagram export** (V3).
- **CI** (TT5): test infrastructure exists; CI workflow not - **Full TT5** (CI): the pipeline is live (see the CI decision
yet configured. 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 ## Handoff notes
@@ -2,7 +2,15 @@
## Status ## 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 (2026-06-14): corrected the verbosity-default fact; re-keyed tier-3
content off `help_id`; split the pre-submit-diagnostic and runtime-error content off `help_id`; split the pre-submit-diagnostic and runtime-error
paths; added a comprehensiveness coverage test. Revised again during 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`/ keying is too coarse for multi-form commands (`add`/`drop`/`show`/
`create`), so D3 now keys tier-3 content **per form** via a `create`), so D3 now keys tier-3 content **per form** via a
`hint_ids: &[&str]` array mirroring `usage_ids` — and **clause-concept `hint_ids: &[&str]` array mirroring `usage_ids` — and **clause-concept
hints** are recorded as a deferred extension (separate tracking issue). hints** are recorded as a deferred extension (issue #37). During Phase C
The parallel question of whether the in-app `help` command should the **pre-submit-diagnostic route + the ~33 `diagnostic.*` blocks** were
likewise distinguish advanced-SQL forms is tracked **separately** as **deferred** (issue #38) — `Diagnostic` doesn't carry its class key, so
Gitea issue #36 (it touches shipped, ADR-backed `help` behaviour). 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** Decided in conversation 2026-06-14. Closes the last open piece of **A1**
(the canonical app-command set, ADR-0003): every app command is (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 | | 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, 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) | | **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) | | **`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 focus (consistent with the existing `handle_key` gates, ADR-0046); it is
active in the input context in both Simple and Advanced mode. active in the input context in both Simple and Advanced mode.
**Two error sources, one namespace.** Errors come in two kinds and reach **Error routes.** **Runtime errors** (the 9 `translate_error` classes)
`hint` by different routes: occur *after* submit; the **`hint` command / empty-input F1** path reads
them via the stored `last_error_hint_key` (D5) and renders their
- **Pre-submit diagnostics** (the ~33 `diagnostic.*` classes — arity, `hint.err.<class>` block. (A second route for **pre-submit diagnostics**
type, unknown table/column) are computed *while typing* by the walker. on the F1 live-input path was specified but is **deferred** — D6 / issue
The **F1 live-input path** reads the current under-cursor diagnostic #38; with a diagnostic present, F1 shows the command block and tier-2
directly from the walker (the same source the ambient panel uses) and shows the diagnostic.) **`:`-prefix handling:**
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:**
on the simple-mode one-shot escape (`: SELECT …`), command on the simple-mode one-shot escape (`: SELECT …`), command
identification for the F1 path strips the leading `:` first, so the identification for the F1 path strips the leading `:` first, so the
advanced form is matched. advanced form is matched.
@@ -200,14 +205,17 @@ mechanics (e.g. `quit`); `what` + `example` are always present.
### D4 — Rendering ### D4 — Rendering
Both surfaces render through one new renderer, `App::note_hint*` (sibling Both surfaces render through the `App::note_hint*` family (sibling of
of `note_help`/`note_help_topic`, `src/app.rs`), emitting a small framed `note_help`/`note_help_topic`, `src/app.rs`) via `emit_tier3_block`,
block into the `output` buffer as `OutputKind::System` with emitting into the `output` buffer as `OutputKind::System`: a **`Hint`
`OutputStyleClass::Hint` on the `what`/`concept` prose and `Neutral` on heading** followed by aligned **`What:` / `Example:` / `Concept:`** lines
the `example` line. The block is **persistent** (scrolls in the journal), (labels + heading from `hint.block.*`). The `concept` line is muted
unlike the transient ambient panel — pressing F1 is an explicit request (`OutputStyleClass::Hint`); the rest are plain. The block is
to *keep* the deeper guidance on screen. The bottom keybinding strip **persistent** (scrolls in the journal), unlike the transient ambient
(ADR-0051) advertises F1 in the editing/typing state. 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 ### 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" cleared when a later command succeeds. Absent → the "getting started"
pointer. pointer.
The **pre-submit-diagnostic route** (the F1 live-input path) needs no The **pre-submit-diagnostic route** (the F1 live-input path reading the
stored state: it reads the current diagnostic from the walker at F1 time under-cursor diagnostic) is **deferred** — see the scope note in D6.
(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.
### D6 — Content scope: comprehensive for v1 ### D6 — Content scope for v1
v1 ships tier-3 content for the **whole inventory**, not a subset (the v1 ships tier-3 content for the **command forms and runtime error
graceful tier-2 fallback below is a safety net, not the plan): 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 - **~37 command forms** — every distinct node in `REGISTRY` gets its own
`hint.cmd.<hint_id>` block (app + DSL + DDL + advanced-mode SQL forms), `hint.cmd.<hint_id>` block (app + DSL + DDL + advanced-mode SQL forms),
each with a **mode-correct example** (the advanced-SQL forms show SQL each with a **mode-correct example** (the advanced-SQL forms show SQL
syntax, their simple siblings show DSL — no sharing). syntax, their simple siblings show DSL — no sharing).
- **9 runtime error classes** — `unique`, `foreign_key` (×4 sides), - **9 runtime error classes** — `unique`, `foreign_key` (child/parent
`not_null`, `check`, `type_mismatch`, `not_found`, `already_exists`, side), `not_null`, `check`, `type_mismatch`, `not_found`,
`generic`, `invalid_value` — each gets a `hint.err.*` block. `already_exists`, `generic`, `invalid_value` — each gets a
- **~33 `diagnostic.*` pre-submit classes** — arity, type, unknown `hint.err.*` block.
table/column, etc. — 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 The full enumerated checklist is the implementation plan's tracking
artifact (see *Content inventory*, below). 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, **reviewable batches** (grouped by area: DDL, DML, app commands,
error classes), not one monolithic drop. 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. 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 Concept: A row is one record; each value lines up with a column, in
order. Columns typed serial/shortid fill themselves — leave order. Columns typed serial/shortid fill themselves — leave
them out. 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:** **Error (`hint` command), foreign-key child-side violation:**
``` ```
Hint — no parent row to point at Hint
What: The value you inserted into Orders.customer_id doesn't match What: The value you gave for the child column doesn't match any
any Customers row, so the foreign key has nothing to point at. parent row, so the foreign key has nothing to point at.
Example: First insert into Customers values ('Ann', ...) Example: First insert the parent (insert into Customers …), then the
Then insert into Orders values (..., 'Ann') child that references it.
Concept: A foreign key is a promise that every child points at a real 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 delete instead, set the relationship's `on delete` to
`set null` or `cascade`. `set null` or `cascade`.
``` ```
@@ -295,7 +313,7 @@ Hint — no parent row to point at
**Command (F1 live-input), `add 1:n relationship`:** **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. 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 Example: add 1:n relationship from Customers.id to Orders.customer_id
Concept: The "1:n" means one parent, many children. The child column 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 `App` state (`last_error_hint_key`), and one new renderer family
(`note_hint*`); the `AppCommand` enum gains `Hint`, the grammar a `HINT` (`note_hint*`); the `AppCommand` enum gains `Hint`, the grammar a `HINT`
node, the REGISTRY one entry. node, the REGISTRY one entry.
- **A large, durable content corpus** (~37 command blocks + ~42 error/ - **A durable content corpus** (~37 command blocks + 10 runtime
diagnostic blocks ≈ 80) enters the catalogue under `hint.cmd.*` / error-class blocks) enters the catalogue under `hint.cmd.*` /
`hint.err.*`, validated by `keys.rs`. This is ongoing surface area: new `hint.err.*`, validated by `keys.rs`. This is ongoing surface area: new
commands/error classes should ship with their tier-3 hint (a checklist 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 - **Testing:** Tier-1 unit tests for the trigger matrix (F1 with
empty/non-empty input; `hint` with/without a recent error; empty/non-empty input; `hint` with/without a recent error;
`last_error_hint_key` set on the `translate_error` sites and cleared on `last_error_hint_key` set on the `translate_error` sites and cleared on
success; the pre-submit-diagnostic vs runtime-error routing; the `:` success; the mode-aware form resolution; the `:` strip), the
strip), the command-identification logic, and the tier-2 fallback; command-identification logic, and the tier-2 fallback; Tier-2 `insta`
Tier-2 `insta` snapshots for a representative rendered hint block; snapshots for a representative rendered hint block; Tier-3 integration
Tier-3 integration tests for the end-to-end flows (type a partial tests for the end-to-end flows (type a partial command → F1 → block
command → F1 → block appears, **buffer and completion memo untouched**; appears, **buffer and completion memo untouched**; run a failing
run a failing command → `hint` → error expansion). **A command → `hint` → error expansion). **A comprehensiveness coverage
comprehensiveness coverage test** (enforces D6): iterate the REGISTRY test** (enforces D6): iterate the REGISTRY and assert every node with a
and assert every node has a `hint_id` resolving to a `hint.cmd.*` block, `hint_ids` entry resolves to a `hint.cmd.*` block, and every runtime
and every runtime-error/diagnostic class has a `hint.err.*` block — error class resolves to a `hint.err.*` block — `keys.rs` only checks
`keys.rs` only checks that *referenced* keys resolve, not that every that *referenced* keys resolve, not that every command/error *has* one,
command/error *has* one, so this test is what makes "comprehensive" so this test is what makes the scope enforceable rather than
enforceable rather than aspirational. aspirational. (Diagnostic classes are out of this scope — D6 / #38.)
## Out of scope ## Out of scope
@@ -381,6 +399,11 @@ Hint — add relationship
recognized clause, deeper than tier-2's candidate list but narrower than 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 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. 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) ## 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`, - **`hint.err.*`** — one per runtime error class (`unique`,
`foreign_key.{child,parent}_side`, `not_null`, `check`, `foreign_key.{child,parent}_side`, `not_null`, `check`,
`type_mismatch`, `not_found`, `already_exists`, `generic`, `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
View File
@@ -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-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-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-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 AD; 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)
+104
View File
@@ -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.
+21
View File
@@ -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`.
+165
View File
@@ -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 AD). 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 AD — **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
View File
@@ -61,11 +61,32 @@ since ADR-0027.)
## Distribution and install ## Distribution and install
- [ ] **D1** Cross-platform binaries: Linux, macOS, Windows on - [x] **D1** Cross-platform binaries: Linux, macOS, Windows on
x86_64 and aarch64. 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, - [ ] **D3** Released via prebuilt binaries plus Homebrew, Scoop,
`winget`, and `cargo binstall`. `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 ## TUI shell
@@ -250,16 +271,13 @@ since ADR-0027.)
## App-level commands (per ADR-0003) ## 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`, available in both modes: `save`, `save as`, `load`, `new`,
`rebuild`, `export`, `import`, `seed`, `replay`, `undo`, `rebuild`, `export`, `import`, `seed`, `replay`, `undo`,
`redo`, `mode`, `help`, `hint`, `quit`. `redo`, `mode`, `help`, `hint`, `quit`.
*(Partial: **14 of 15** implemented and available in both modes — *(Done 2026-06-15: the last command, **`hint`**, landed with H2
`quit`/`q`, `mode simple|advanced`, `help`, `save`, `save as`, (ADR-0053). All 15 canonical app commands are now registered and
`load`, `new`, `rebuild`, `export`, `import`, `replay`, `undo`, available in both modes.)*
`redo`, and now **`seed`** (ADR-0048 / SD1, done 2026-06-11).
**Only `hint`** (tracked as H2) remains unregistered. A1 closes
when H2 lands.)*
## DSL data commands ## DSL data commands
@@ -793,8 +811,16 @@ since ADR-0027.)
`returning `) still shows the raw expression first-set — `returning `) still shows the raw expression first-set —
typing-time completion already offers the right candidates typing-time completion already offers the right candidates
there, so the payoff is small. 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. 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 - [x] **H3** `help` provides general reference and per-command
help. help.
*(Done 2026-06-07: the **general reference** is `help` (no arg) — *(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 PTY. Correcting a stale `CLAUDE.md` line that read "Tier 4 is
wired only for the listed critical flows" — it was not wired at wired only for the listed critical flows" — it was not wired at
all. Genuinely deferred.)* 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. 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 13) 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 ## Cross-cutting
+88 -6
View File
@@ -3205,17 +3205,32 @@ impl App {
/// polish (the framed block) lands with the corpus. /// polish (the framed block) lands with the corpus.
fn emit_tier3_block(&mut self, stem: &str) -> bool { fn emit_tier3_block(&mut self, stem: &str) -> bool {
let cat = crate::friendly::catalog(); 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; 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() { 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() { if cat.get(&format!("{stem}.concept")).is_some() {
self.push_category_three_prose(crate::friendly::translate( self.push_category_three_prose(labelled(
&format!("{stem}.concept"), &crate::t!("hint.block.concept"),
&[], &crate::friendly::translate(&format!("{stem}.concept"), &[]),
)); ));
} }
true 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] #[test]
fn f1_on_add_relationship_renders_the_relationship_block() { fn f1_on_add_relationship_renders_the_relationship_block() {
let mut app = App::new(); let mut app = App::new();
@@ -5882,6 +5913,57 @@ mod tests {
assert!(!output_contains(&app, "Remove a table")); 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] #[test]
fn messages_command_toggles_verbosity_and_reports() { fn messages_command_toggles_verbosity_and_reports() {
let mut app = App::new(); let mut app = App::new();
+18 -12
View File
@@ -1790,7 +1790,13 @@ pub static SHOW: CommandNode = CommandNode {
shape: SHOW_SHAPE, shape: SHOW_SHAPE,
ast_builder: build_show, ast_builder: build_show,
help_id: Some("data.show"), help_id: Some("data.show"),
hint_ids: &[], hint_ids: &[
"show_data",
"show_table",
"show_tables",
"show_relationships",
"show_indexes",
],
usage_ids: &[ usage_ids: &[
"parse.usage.show_data", "parse.usage.show_data",
"parse.usage.show_table", "parse.usage.show_table",
@@ -1806,7 +1812,7 @@ pub static SEED: CommandNode = CommandNode {
shape: SEED_SHAPE, shape: SEED_SHAPE,
ast_builder: build_seed, ast_builder: build_seed,
help_id: Some("data.seed"), help_id: Some("data.seed"),
hint_ids: &[], hint_ids: &["seed"],
usage_ids: &["parse.usage.seed"], usage_ids: &["parse.usage.seed"],
}; };
@@ -1824,7 +1830,7 @@ pub static UPDATE: CommandNode = CommandNode {
shape: UPDATE_SHAPE, shape: UPDATE_SHAPE,
ast_builder: build_update, ast_builder: build_update,
help_id: Some("data.update"), help_id: Some("data.update"),
hint_ids: &[], hint_ids: &["update"],
usage_ids: &["parse.usage.update"],}; usage_ids: &["parse.usage.update"],};
pub static DELETE: CommandNode = CommandNode { pub static DELETE: CommandNode = CommandNode {
@@ -1832,7 +1838,7 @@ pub static DELETE: CommandNode = CommandNode {
shape: DELETE_SHAPE, shape: DELETE_SHAPE,
ast_builder: build_delete, ast_builder: build_delete,
help_id: Some("data.delete"), help_id: Some("data.delete"),
hint_ids: &[], hint_ids: &["delete"],
usage_ids: &["parse.usage.delete"],}; usage_ids: &["parse.usage.delete"],};
pub static REPLAY: CommandNode = CommandNode { pub static REPLAY: CommandNode = CommandNode {
@@ -1840,7 +1846,7 @@ pub static REPLAY: CommandNode = CommandNode {
shape: REPLAY_PATH, shape: REPLAY_PATH,
ast_builder: build_replay, ast_builder: build_replay,
help_id: Some("data.replay"), help_id: Some("data.replay"),
hint_ids: &[], hint_ids: &["replay"],
usage_ids: &["parse.usage.replay"],}; usage_ids: &["parse.usage.replay"],};
pub static EXPLAIN: CommandNode = CommandNode { pub static EXPLAIN: CommandNode = CommandNode {
@@ -1848,7 +1854,7 @@ pub static EXPLAIN: CommandNode = CommandNode {
shape: EXPLAIN_SHAPE, shape: EXPLAIN_SHAPE,
ast_builder: build_explain, ast_builder: build_explain,
help_id: Some("data.explain"), help_id: Some("data.explain"),
hint_ids: &[], hint_ids: &["explain"],
usage_ids: &["parse.usage.explain"],}; usage_ids: &["parse.usage.explain"],};
/// `explain` over advanced-mode SQL (ADR-0039). /// `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` // too). Mirrors the `SQL_INSERT`/`SQL_UPDATE`/`SQL_DELETE`
// precedent; otherwise `note_help` would print `explain` twice. // precedent; otherwise `note_help` would print `explain` twice.
help_id: None, help_id: None,
hint_ids: &[], hint_ids: &["explain_sql"],
usage_ids: &[],}; usage_ids: &[],};
/// SQL `SELECT` (ADR-0030 §6, ADR-0031, ADR-0032). /// 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), shape: Node::Subgrammar(&sql_select::SQL_SELECT_TAIL),
ast_builder: build_select, ast_builder: build_select,
help_id: None, help_id: None,
hint_ids: &[], hint_ids: &["select"],
usage_ids: &["parse.usage.select"],}; usage_ids: &["parse.usage.select"],};
/// `WITH …` top-level statement (ADR-0032 §4 / sub-phase 2c). /// `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), shape: Node::Subgrammar(&sql_select::SQL_WITH_TAIL),
ast_builder: build_select, ast_builder: build_select,
help_id: None, help_id: None,
hint_ids: &[], hint_ids: &["with"],
usage_ids: &["parse.usage.with"],}; usage_ids: &["parse.usage.with"],};
/// SQL `INSERT` — the `Advanced`-category node of the shared /// 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), shape: Node::Subgrammar(&sql_insert::SQL_INSERT_SHAPE),
ast_builder: build_sql_insert, ast_builder: build_sql_insert,
help_id: None, help_id: None,
hint_ids: &[], hint_ids: &["sql_insert"],
usage_ids: &[], usage_ids: &[],
}; };
@@ -1931,7 +1937,7 @@ pub static SQL_UPDATE: CommandNode = CommandNode {
shape: Node::Subgrammar(&sql_update::SQL_UPDATE_SHAPE), shape: Node::Subgrammar(&sql_update::SQL_UPDATE_SHAPE),
ast_builder: build_sql_update, ast_builder: build_sql_update,
help_id: None, help_id: None,
hint_ids: &[], hint_ids: &["sql_update"],
usage_ids: &[], usage_ids: &[],
}; };
@@ -1947,7 +1953,7 @@ pub static SQL_DELETE: CommandNode = CommandNode {
shape: Node::Subgrammar(&sql_delete::SQL_DELETE_SHAPE), shape: Node::Subgrammar(&sql_delete::SQL_DELETE_SHAPE),
ast_builder: build_sql_delete, ast_builder: build_sql_delete,
help_id: None, help_id: None,
hint_ids: &[], hint_ids: &["sql_delete"],
usage_ids: &[], usage_ids: &[],
}; };
+5 -5
View File
@@ -1879,7 +1879,7 @@ pub static SQL_CREATE_TABLE: CommandNode = CommandNode {
shape: Node::Subgrammar(&super::sql_create_table::SQL_CREATE_TABLE_SHAPE), shape: Node::Subgrammar(&super::sql_create_table::SQL_CREATE_TABLE_SHAPE),
ast_builder: build_sql_create_table, ast_builder: build_sql_create_table,
help_id: Some("ddl.sql_create_table"), help_id: Some("ddl.sql_create_table"),
hint_ids: &[], hint_ids: &["sql_create_table"],
usage_ids: &["parse.usage.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, shape: SQL_DROP_TABLE_SHAPE,
ast_builder: build_sql_drop_table, ast_builder: build_sql_drop_table,
help_id: Some("ddl.sql_drop_table"), help_id: Some("ddl.sql_drop_table"),
hint_ids: &[], hint_ids: &["sql_drop_table"],
usage_ids: &["parse.usage.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, shape: SQL_DROP_INDEX_SHAPE,
ast_builder: build_sql_drop_index, ast_builder: build_sql_drop_index,
help_id: Some("ddl.sql_drop_index"), help_id: Some("ddl.sql_drop_index"),
hint_ids: &[], hint_ids: &["sql_drop_index"],
usage_ids: &["parse.usage.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, shape: SQL_CREATE_INDEX_SHAPE,
ast_builder: build_sql_create_index, ast_builder: build_sql_create_index,
help_id: Some("ddl.sql_create_index"), help_id: Some("ddl.sql_create_index"),
hint_ids: &[], hint_ids: &["sql_create_index"],
usage_ids: &["parse.usage.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, shape: SQL_ALTER_TABLE_SHAPE,
ast_builder: build_sql_alter_table, ast_builder: build_sql_alter_table,
help_id: Some("ddl.sql_alter_table"), help_id: Some("ddl.sql_alter_table"),
hint_ids: &[], hint_ids: &["sql_alter_table"],
usage_ids: &["parse.usage.sql_alter_table"], usage_ids: &["parse.usage.sql_alter_table"],
}; };
+88 -1
View File
@@ -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). /// form has no tier-3 block yet (the caller falls back to tier-2).
#[must_use] #[must_use]
pub fn hint_key_for_input_in_mode(source: &str, mode: crate::mode::Mode) -> Option<&'static str> { 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); let nodes = selected_nodes_for_input_in_mode(source, mode);
if nodes.is_empty() { if nodes.is_empty() {
return None; 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(); let mut keys: Vec<&'static str> = Vec::new();
for (_, node, _) in &nodes { for (_, node, _) in &nodes {
for k in node.hint_ids { 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 /// 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), hint_key_for_input_in_mode("drop table T", Mode::Simple),
Some("drop_table") 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). // Unknown entry word → None (tier-2 fallback).
assert_eq!(hint_key_for_input_in_mode("zzz", Mode::Simple), None); 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)] #[cfg(test)]
+95
View File
@@ -224,6 +224,10 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
), ),
("hint.ambient_expected", &["expected"]), ("hint.ambient_expected", &["expected"]),
("hint.getting_started", &[]), ("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. // Tier-3 teaching blocks (ADR-0053 D3) — Phase-B exemplars.
("hint.cmd.insert.what", &[]), ("hint.cmd.insert.what", &[]),
("hint.cmd.insert.example", &[]), ("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.what", &[]),
("hint.err.foreign_key.child_side.example", &[]), ("hint.err.foreign_key.child_side.example", &[]),
("hint.err.foreign_key.child_side.concept", &[]), ("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. // Phase C batch 1 — app-lifecycle command hints.
("hint.cmd.quit.what", &[]), ("hint.cmd.quit.what", &[]),
("hint.cmd.quit.example", &[]), ("hint.cmd.quit.example", &[]),
@@ -306,6 +336,70 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
("hint.cmd.change_column.what", &[]), ("hint.cmd.change_column.what", &[]),
("hint.cmd.change_column.example", &[]), ("hint.cmd.change_column.example", &[]),
("hint.cmd.change_column.concept", &[]), ("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", "hint.ambient_invalid_ident",
&["kind", "found"], &["kind", "found"],
@@ -557,6 +651,7 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
("shortcut.confirm", &[]), ("shortcut.confirm", &[]),
("shortcut.cycle", &[]), ("shortcut.cycle", &[]),
("shortcut.del_word", &[]), ("shortcut.del_word", &[]),
("shortcut.hint", &[]),
("shortcut.history", &[]), ("shortcut.history", &[]),
("shortcut.home_end", &[]), ("shortcut.home_end", &[]),
("shortcut.load", &[]), ("shortcut.load", &[]),
+131
View File
@@ -391,6 +391,13 @@ hint:
# H2 / ADR-0053: shown by `hint` / F1 when there is nothing specific # H2 / ADR-0053: shown by `hint` / F1 when there is nothing specific
# to expand on (no recent error, empty input). # 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." 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) ────────────────────────── # ── Tier-3 teaching blocks (ADR-0053 D3) ──────────────────────────
# Per-form command hints (`hint.cmd.<form>`) and per-class error # Per-form command hints (`hint.cmd.<form>`) and per-class error
# hints (`hint.err.<class>`), each a `what` (12 sentences) / `example` # hints (`hint.err.<class>`), each a `what` (12 sentences) / `example`
@@ -505,12 +512,135 @@ hint:
what: "Change a column's type, converting the existing values." what: "Change a column's type, converting the existing values."
example: "change column Customers: status (int)" 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." 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: 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: foreign_key:
child_side: 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." 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." 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`." 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 # Invalid identifier in a schema slot (ADR-0022 stage 8e
# + the user's #5). Voice mirrors ADR-0019's "no such # + the user's #5). Voice mirrors ADR-0019's "no such
# {kind}" wording for consistency with engine errors. # {kind}" wording for consistency with engine errors.
@@ -1042,6 +1172,7 @@ shortcut:
browse: "browse" browse: "browse"
clear: "clear" clear: "clear"
complete: "complete" complete: "complete"
hint: "hint"
history: "history" history: "history"
home_end: "home/end" home_end: "home/end"
del_word: "del word" 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 source: src/ui.rs
assertion_line: 2836 assertion_line: 2839
expression: snapshot expression: snapshot
--- ---
╭ Output ──────────────────────────────────────────────────────────────────────╮ ╭ Output ──────────────────────────────────────────────────────────────────────╮
@@ -26,4 +26,4 @@ expression: snapshot
│ │ │ │
╰──────────────────────────────────────────────────────────────────────────────╯ ╰──────────────────────────────────────────────────────────────────────────────╯
Project: Term Planner 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 source: src/ui.rs
assertion_line: 2819 assertion_line: 2822
expression: snapshot expression: snapshot
--- ---
╭ Output ──────────────────────────────────────────────────────────────────────╮ ╭ Output ──────────────────────────────────────────────────────────────────────╮
@@ -26,4 +26,4 @@ expression: snapshot
│for SQL │ │for SQL │
╰──────────────────────────────────────────────────────────────────────────────╯ ╰──────────────────────────────────────────────────────────────────────────────╯
Project: Term Planner 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 source: src/ui.rs
assertion_line: 2827 assertion_line: 2830
expression: snapshot expression: snapshot
--- ---
╭ Output ──────────────────────────────────────────────────────────────────────╮ ╭ Output ──────────────────────────────────────────────────────────────────────╮
@@ -26,4 +26,4 @@ expression: snapshot
│for SQL │ │for SQL │
╰──────────────────────────────────────────────────────────────────────────────╯ ╰──────────────────────────────────────────────────────────────────────────────╯
Project: Term Planner 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 source: src/ui.rs
assertion_line: 3442 assertion_line: 3445
expression: snapshot expression: snapshot
--- ---
╭ Output ────────────────────────────────────────────────────────────────────────────────╮ ╭ Output ────────────────────────────────────────────────────────────────────────────────╮
@@ -28,4 +28,4 @@ expression: snapshot
│ │ │ │
╰────────────────────────────────────────────────────────────────────────────────────────╯ ╰────────────────────────────────────────────────────────────────────────────────────────╯
Project: Term Planner 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 source: src/ui.rs
assertion_line: 3388 assertion_line: 3391
expression: snapshot expression: snapshot
--- ---
╭ Output ────────────────────────────────────────────────────────────────────────────────╮ ╭ Output ────────────────────────────────────────────────────────────────────────────────╮
@@ -28,4 +28,4 @@ expression: snapshot
│ │ │ │
╰────────────────────────────────────────────────────────────────────────────────────────╯ ╰────────────────────────────────────────────────────────────────────────────────────────╯
Project: Term Planner 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 source: src/ui.rs
assertion_line: 3378 assertion_line: 3381
expression: snapshot expression: snapshot
--- ---
╭ Output ────────────────────────────────────────────────────────────────────────────────╮ ╭ Output ────────────────────────────────────────────────────────────────────────────────╮
@@ -28,4 +28,4 @@ expression: snapshot
│ │ │ │
╰────────────────────────────────────────────────────────────────────────────────────────╯ ╰────────────────────────────────────────────────────────────────────────────────────────╯
Project: Term Planner 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 source: src/ui.rs
assertion_line: 3431 assertion_line: 3434
expression: snapshot expression: snapshot
--- ---
╭ Output ────────────────────────────────────────────────────────────────────────────────╮ ╭ Output ────────────────────────────────────────────────────────────────────────────────╮
@@ -28,4 +28,4 @@ expression: snapshot
│ │ │ │
╰────────────────────────────────────────────────────────────────────────────────────────╯ ╰────────────────────────────────────────────────────────────────────────────────────────╯
Project: Term Planner 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 source: src/ui.rs
assertion_line: 3457 assertion_line: 3460
expression: snapshot expression: snapshot
--- ---
╭ Output ────────────────────────────────────────────────────────────────────────────────╮ ╭ Output ────────────────────────────────────────────────────────────────────────────────╮
@@ -28,4 +28,4 @@ expression: snapshot
│ │ │ │
╰────────────────────────────────────────────────────────────────────────────────────────╯ ╰────────────────────────────────────────────────────────────────────────────────────────╯
Project: Term Planner 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 source: src/ui.rs
assertion_line: 2880 assertion_line: 2882
expression: snapshot expression: snapshot
--- ---
╭ Output ──────────────────────────────────────────────────────────────────────╮ ╭ Output ──────────────────────────────────────────────────────────────────────╮
@@ -26,4 +26,4 @@ expression: snapshot
│insert into <Table> [(<col>[, ...])] [values] (<value>[, ...]) │ │insert into <Table> [(<col>[, ...])] [values] (<value>[, ...]) │
╰──────────────────────────────────────────────────────────────────────────────╯ ╰──────────────────────────────────────────────────────────────────────────────╯
Project: Term Planner 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 source: src/ui.rs
assertion_line: 2896 assertion_line: 2898
expression: snapshot expression: snapshot
--- ---
╭ Output ──────────────────────────────────────────────────────────────────────╮ ╭ Output ──────────────────────────────────────────────────────────────────────╮
@@ -26,4 +26,4 @@ expression: snapshot
│ │ │ │
╰──────────────────────────────────────────────────────────────────────────────╯ ╰──────────────────────────────────────────────────────────────────────────────╯
Project: Term Planner 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 source: src/ui.rs
assertion_line: 3099 assertion_line: 3102
expression: snapshot expression: snapshot
--- ---
╭ Tables ──────────────────╮╭ Output ────────────────────────────────────────────────────────────────────────╮ ╭ Tables ──────────────────╮╭ Output ────────────────────────────────────────────────────────────────────────╮
@@ -26,4 +26,4 @@ expression: snapshot
│ ││for SQL │ │ ││for SQL │
╰──────────────────────────╯╰────────────────────────────────────────────────────────────────────────────────╯ ╰──────────────────────────╯╰────────────────────────────────────────────────────────────────────────────────╯
Project: Term Planner 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 source: src/ui.rs
assertion_line: 2909 assertion_line: 2912
expression: snapshot expression: snapshot
--- ---
╭ Output ──────────────────────────────────────────────────────────────────────╮ ╭ Output ──────────────────────────────────────────────────────────────────────╮
@@ -26,4 +26,4 @@ expression: snapshot
│for SQL │ │for SQL │
╰──────────────────────────────────────────────────────────────────────────────╯ ╰──────────────────────────────────────────────────────────────────────────────╯
Project: Term Planner 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 source: src/ui.rs
assertion_line: 3209 assertion_line: 3212
expression: snapshot expression: snapshot
--- ---
╭ Tables ──────────────────╮╭ Output ────────────────────────────────────────────────────────────────────────╮ ╭ Tables ──────────────────╮╭ Output ────────────────────────────────────────────────────────────────────────╮
@@ -26,4 +26,4 @@ expression: snapshot
│ Orders.customer_id ││for SQL │ │ Orders.customer_id ││for SQL │
╰──────────────────────────╯╰────────────────────────────────────────────────────────────────────────────────╯ ╰──────────────────────────╯╰────────────────────────────────────────────────────────────────────────────────╯
Project: Term Planner 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… │ │with `mode advanced`, or prefix the line with `:` to run… │
╰──────────────────────────────────────────────────────────╯ ╰──────────────────────────────────────────────────────────╯
Project: Term Planner 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
+9 -6
View File
@@ -1894,22 +1894,25 @@ fn status_bar_bindings(app: &App) -> Vec<(&'static str, String)> {
("Enter", crate::t!("shortcut.run")), ("Enter", crate::t!("shortcut.run")),
]; ];
} }
// 4. Editing — the input has text: surface the readline edit keys // 4. Editing — the input has text: F1 (the contextual hint for what
// (ADR-0049). The highest-value subset stays within the width // you're typing, ADR-0053) leads, then the readline edit keys
// budget; Ctrl-K/U remain unadvertised muscle memory. // (ADR-0049). Ctrl-K/U remain unadvertised muscle memory.
if !app.input.is_empty() { if !app.input.is_empty() {
return vec![ return vec![
("F1", crate::t!("shortcut.hint")),
("Esc", crate::t!("shortcut.clear")), ("Esc", crate::t!("shortcut.clear")),
("Ctrl-A/E", crate::t!("shortcut.home_end")), ("Ctrl-A/E", crate::t!("shortcut.home_end")),
("Ctrl-W", crate::t!("shortcut.del_word")), ("Ctrl-W", crate::t!("shortcut.del_word")),
("Enter", crate::t!("shortcut.run")), ("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![ vec![
("Ctrl-O", crate::t!("shortcut.nav")), ("Ctrl-O", crate::t!("shortcut.nav")),
("Tab", crate::t!("shortcut.complete")), ("Tab", crate::t!("shortcut.complete")),
("", crate::t!("shortcut.history")), ("", crate::t!("shortcut.history")),
("F1", crate::t!("shortcut.hint")),
("Enter", crate::t!("shortcut.run")), ("Enter", crate::t!("shortcut.run")),
] ]
} }
@@ -2664,7 +2667,7 @@ mod tests {
#[test] #[test]
fn strip_default_state_is_nav_complete_history_run() { fn strip_default_state_is_nav_complete_history_run() {
let app = App::new(); 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] #[test]
@@ -2675,7 +2678,7 @@ mod tests {
app.input.push_str("create ta"); app.input.push_str("create ta");
assert_eq!( assert_eq!(
strip_keys(&app), strip_keys(&app),
vec!["Esc", "Ctrl-A/E", "Ctrl-W", "Enter"], vec!["F1", "Esc", "Ctrl-A/E", "Ctrl-W", "Enter"],
); );
} }