Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ae0eedd44 | |||
| 5f28de8ac3 | |||
| 888be16090 | |||
| 329adfc935 | |||
| 447112b17f | |||
| 984bc30256 | |||
| 417cbc8df9 | |||
| b6b98ad30f | |||
| 97970f2a2c | |||
| 9c4d520d5c |
@@ -108,6 +108,23 @@ Current decisions at a glance (each backed by an ADR):
|
|||||||
SQL `select` / `with` / `insert` / `update` / `delete`
|
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
@@ -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 A–D; closes **A1** + requirements **H2**). Settles the `hint` slot ADR-0003 left "ADR pending"; closes the last open piece of **A1** and tracks requirements **H2**. **Two surfaces:** an **F1 keybinding** that renders a deep hint for the *live* partial input without submitting (the primary path — a submitted `hint` command can't see the buffer it would help with, since Enter empties it), and a submitted **`hint` command** that expands on the *most recent error*. **No topic argument** (contextual only — `help <topic>` already owns explicit reference). Introduces a **tier-3 teaching layer**, deeper than the existing tier-1 (colour / error headline) and tier-2 (ambient one-liner; and the error `hint:`, which is shown **by default** since `Verbosity::Verbose` is the default — `messages short` is the opt-*out*); without it `hint` would just duplicate what's already on screen. Tier-3 content lives in the catalogue under `hint.cmd.<hint_id>` (per command form) and `hint.err.<class>` (per error/diagnostic class), each a structured `what`/`example`/`concept` block rendered via a new `note_hint*` family with `OutputStyleClass::Hint`. **Keyed per-form via a new `hint_ids: &[&str]` field on `CommandNode` mirroring `usage_ids`** (revised in Phase B): a per-*node* key proved too coarse — `add`/`drop`/`show`/`create` are each one node spanning many forms, and a live-input hint for `add 1:n relationship` must be specific to relationships; `hint_key_for_input_in_mode` reuses `usage_key_for_input_in_mode`'s form-word disambiguation, and covers the advanced-SQL forms whose `usage_ids` are empty. Not keyed off `help_id` (it is `None` on the advanced-SQL nodes purely to dedup the `help` list; that parallel gap is issue **#36**). **Clause-concept hints** (`on delete` actions, constraint slots, `with pk`, cardinality) are a recorded **deferred extension** (`hint.concept.<topic>`, issue **#37**) — per-form is the right tier-3 granularity, with position-awareness owned by tier-2 + the live `Next:` line. Runtime `translate_error` classes resolve via stored `last_error_hint_key` (`hint` command / empty-F1). (The second route — pre-submit `diagnostic.*` read live from the walker on the F1 path — is **deferred**, issue **#38**: `Diagnostic` carries no class key.) Adds `AppCommand::Hint`, a `HINT` grammar node + REGISTRY entry, the `hint_ids` field, and `last_error_hint_key`; F1 is a read-only overlay (buffer + completion memo untouched). **Content is the bulk of the work** (the mechanism is ~a day): v1 scope = ~37 command forms + 9 runtime error classes (comprehensive for those, ~57 blocks), authored **exemplars-first** (voice approved in this ADR's `/runda` review, then mass-authored in batches), enforced by a **comprehensiveness coverage test**, with graceful fall-back to tier-2 if a key is ever missing. The **pre-submit-diagnostic route + ~33 `diagnostic.*` blocks were deferred** (issue **#38**) — `Diagnostic` carries no class key, so the route needs a broad change for marginal value (tier-2 already surfaces diagnostics; many duplicate runtime classes). Forks user-chosen: two-surface model; **F1** (vs `?` / a chord); no-arg; comprehensive-for-commands-and-errors scope; exemplars-first; diagnostics deferred. OOS: per-topic `hint <topic>` (rejected — overlaps `help`); always-on tier-3 (rejected — keeps ambient terse); non-`en-US` locales + success-command teaching (deferred); clause-concept hints (issue #37); the diagnostic route (issue #38); the `help`-side advanced-SQL gap (issue #36)
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
# CI subproject handoff — 2026-06-15 (ci-01)
|
||||||
|
|
||||||
|
First handover for the **CI / release subproject** (the `ci` branch). Kept in
|
||||||
|
`docs/ci/handoff/`, a namespace separate from the project's global
|
||||||
|
`docs/handoff/` session sequence so it can't collide with `main`'s numbering —
|
||||||
|
the same split as `docs/ci/adr/`, and needed for the same reason: `main`
|
||||||
|
independently wrote its own **handoff-70** this same day (just as it took
|
||||||
|
**ADR-0049**), which would have collided.
|
||||||
|
|
||||||
|
A dedicated infrastructure session that built the project's **entire CI/CD
|
||||||
|
pipeline** on the self-hosted Gitea Actions runner — from nothing to a live
|
||||||
|
gate plus a six-target cross-platform release. Net: the **CI** /
|
||||||
|
`requirements.md` **TT5** item and **D1**/**D2** are now done; **D3** and a
|
||||||
|
couple of TT5 tails remain. Decisions are recorded in the sibling ADR namespace
|
||||||
|
**`docs/ci/adr/`** (ADR-ci-001/002/003).
|
||||||
|
|
||||||
|
## §1. State at handoff
|
||||||
|
|
||||||
|
**Branch:** `ci` (worktree). **`main` has been merged into `ci`** (commit
|
||||||
|
`138e766`, clean — `ci` and `main` touched disjoint files) so the gate runs
|
||||||
|
against current `main` before CI lands there. Working tree clean except the
|
||||||
|
in-progress doc updates from this handoff. Pushes/promotion are the user's
|
||||||
|
step.
|
||||||
|
|
||||||
|
**Gate verified locally on the merged code:** `clippy -D warnings` clean;
|
||||||
|
**`cargo test` 2488 passing / 0 failing / 1 ignored** (the long-standing
|
||||||
|
`friendly` doctest). main's features came in with their tests (2424 → 2488).
|
||||||
|
|
||||||
|
**Pipeline (`.gitea/workflows/`):**
|
||||||
|
|
||||||
|
- `build-ci-image.yaml` — builds + pushes the CI image (`node:22-bookworm-slim`
|
||||||
|
+ single-user nix + the flake's devShell pre-warmed) to the Gitea registry.
|
||||||
|
Triggers only on image-input changes (Dockerfile / flake / toolchain).
|
||||||
|
- `ci.yaml` — the gate: `clippy -D warnings` + `cargo test`, branch pushes + PRs
|
||||||
|
(docs-only changes skipped).
|
||||||
|
- `release.yaml` — on a `v*` tag: `test` → `build` matrix over the **four
|
||||||
|
non-macOS** targets via `cargo-zigbuild`, upload to the Gitea release.
|
||||||
|
- `release-macos.yaml` — **workflow_dispatch** (tag input) on the Tart
|
||||||
|
Apple-Silicon runner (`runs-on: macos`): test → build both `*-apple-darwin`
|
||||||
|
→ de-nix `libiconv` + ad-hoc re-sign → upload.
|
||||||
|
|
||||||
|
**Verified live this session:** the 4-target release published **8 assets**
|
||||||
|
(binary + `.sha256` each) for tag `v.0.0.0-citest3`; the macOS build was proven
|
||||||
|
portable (system-only deps) + signed + launches on the runner.
|
||||||
|
|
||||||
|
## §2. What was built (and the non-obvious bits)
|
||||||
|
|
||||||
|
- **Nix flake** (ADR-ci-002, relocated from a would-be `main` ADR-0049): one
|
||||||
|
pinned toolchain (`1.95.0`) for dev *and* CI; `cargo-zigbuild` + `zig` (Linux
|
||||||
|
only) for the cross targets; `apple-sdk` on darwin.
|
||||||
|
- **Runner facts** (ADR-ci-001): jobs run *inside* a container (`ci-public` →
|
||||||
|
`catthehacker/ubuntu`), so host nix is unreachable — hence the baked image.
|
||||||
|
The Mac runner is **host execution**; its label is `macos` (`:host` in the
|
||||||
|
registration is the act_runner backend, not part of the label).
|
||||||
|
- **Cross-compile** (ADR-ci-003): `cargo-zigbuild` for the 4 non-macOS targets.
|
||||||
|
Windows needs an **empty `libsynchronization.a` stub** (`ci/winstub/`, wired
|
||||||
|
via `.cargo/config.toml`) — std links `-lsynchronization`, absent from
|
||||||
|
rust-overlay's toolchain + zig's mingw, but forwarded by `kernel32`.
|
||||||
|
- **macOS** (ADR-ci-003 amendment): built on **real Apple hardware** (Tart), so
|
||||||
|
the SDK is fully licensed — no osxcross grey area. The darwin stdenv bakes a
|
||||||
|
`/nix/store` `libiconv` path into the binary; the build rewrites it to
|
||||||
|
`/usr/lib/libiconv.2.dylib` (`install_name_tool`) and re-signs ad-hoc
|
||||||
|
(`codesign -f -s -`; `install_name_tool` invalidates the signature, arm64
|
||||||
|
refuses unsigned). A guard fails the build on any remaining `/nix/store` dep.
|
||||||
|
- **Cache hygiene (Mac):** the runner wipes the workspace each run, so cargo
|
||||||
|
`target/` never accumulates; the persistent nix store is bounded by
|
||||||
|
**generation** (record the devShell in a persistent profile, keep the 2
|
||||||
|
newest via `nix-env --delete-generations +2`, GC the rest). First sweep
|
||||||
|
reclaimed a ~3.8 GB one-time backlog of build scaffolding (source + build-only
|
||||||
|
deps, *not* re-installed toolchains).
|
||||||
|
|
||||||
|
## §3. Immediate next steps (user)
|
||||||
|
|
||||||
|
1. **Push `ci`** → the gate re-runs in CI (should be green; no image rebuild —
|
||||||
|
the merge didn't touch the flake/Dockerfile).
|
||||||
|
2. **Promote:** `git checkout main && git merge ci` — a **fast-forward** (`ci`
|
||||||
|
already contains `main`) — then push `main`. CI goes live; `release-macos`
|
||||||
|
becomes dispatchable (workflow_dispatch needs the default branch).
|
||||||
|
3. **First real release:** tag `v0.1.0` (auto-builds the 4 Linux/Windows
|
||||||
|
assets), then **dispatch `release-macos` for `v0.1.0`** with the Mac up (adds
|
||||||
|
the 2 macOS assets) → a full 6-binary release.
|
||||||
|
4. **Cleanup:** delete the `v.0.0.0-citest*` test tags + their releases.
|
||||||
|
5. **Runner-side:** add `min-free`/`max-free` to the Mac's `/etc/nix/nix.conf`
|
||||||
|
as a hands-off nix-store backstop.
|
||||||
|
|
||||||
|
## §4. Known gaps / follow-ups
|
||||||
|
|
||||||
|
- **Versioning is not wired into the binary** (flagged by the user). The release
|
||||||
|
**git tag is nowhere in the produced binary** — there is no `--version` flag,
|
||||||
|
no `CARGO_PKG_VERSION` use anywhere in `src/`, and the release workflows use
|
||||||
|
the tag only for the *release name* + *asset filenames*
|
||||||
|
(`rdbms-playground-<tag>-<target>`). `Cargo.toml` is a static `version =
|
||||||
|
"0.1.0"`, decoupled from the tag. So a `v0.5.0` tag yields a `…-v0.5.0-…`
|
||||||
|
asset whose binary knows nothing of "0.5.0". To fix later: add a `--version`
|
||||||
|
flag, and inject the tag at build time (e.g. a `build.rs` reading a
|
||||||
|
CI-provided env, or bumping `Cargo.toml` as part of tagging) so the binary and
|
||||||
|
the release agree.
|
||||||
|
- **D3 packaging** — Homebrew / Scoop / winget / `cargo binstall` manifests
|
||||||
|
(asset naming is already binstall-friendly).
|
||||||
|
- **TT5 tails** — Windows is build-only (no execution runner); Tier-4 PTY (TT4)
|
||||||
|
is unwired in CI.
|
||||||
|
- **`fmt` gate** — deliberately off (tree isn't stock-`rustfmt`-clean); revisit
|
||||||
|
on `main`.
|
||||||
|
- **Website → Cloudflare** deploy — the separate, simpler workflow, still to do.
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# CI / Build subproject — session handoffs
|
||||||
|
|
||||||
|
Handover notes for the **CI / release pipeline** work (the Gitea Actions
|
||||||
|
workflows under `.gitea/`, the nix flake, the release tooling). Kept in their
|
||||||
|
own namespace, separate from the project-wide session handoffs in
|
||||||
|
[`docs/handoff/`](../../handoff/), so a CI-branch handoff never competes with
|
||||||
|
`main`'s global handoff sequence for numbers — the same split the CI ADRs use
|
||||||
|
([`docs/ci/adr/`](../adr/README.md)). This is not hypothetical: `main`
|
||||||
|
independently wrote a `handoff-70` the same day this subproject's first handoff
|
||||||
|
was drafted.
|
||||||
|
|
||||||
|
**Numbering.** Files are named `<date>-handoff-ci-<NN>.md` and referenced in
|
||||||
|
prose as `handoff-ci-NN`. Assign the next free `NN` from this index.
|
||||||
|
|
||||||
|
## Index
|
||||||
|
|
||||||
|
- [handoff-ci-01 — the CI/release pipeline build-out](20260615-handoff-ci-01.md)
|
||||||
|
— Gitea Actions gate (clippy + test) + a six-target release (four via
|
||||||
|
`cargo-zigbuild` on a `v*` tag, two macOS via dispatch on a Tart runner), all
|
||||||
|
on a nix flake; decisions in `docs/ci/adr/`. Built on the `ci` branch, merged
|
||||||
|
`main` in, gate green (2488 tests), ready to promote to `main`.
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
# Session handoff — 2026-06-15 (70)
|
||||||
|
|
||||||
|
Seventieth handover. Continues from handoff-69 (which closed the last
|
||||||
|
four Gitea issues and left the tracker empty). This session did the
|
||||||
|
**ADR-0052 follow-up** (unwinding vestigial worker `source` plumbing),
|
||||||
|
then **designed and fully implemented H2 — the contextual `hint`
|
||||||
|
command + F1 keybinding (ADR-0053)** end to end (Phases A–D). The CI
|
||||||
|
branch was also merged into `main` mid-session (not my work — see §5).
|
||||||
|
|
||||||
|
Net: **2 feature areas shipped, 1 new ADR (0053) + 1 ADR amendment
|
||||||
|
(0052), 4 new Gitea issues (#35–#38), the `hint` corpus (~57 teaching
|
||||||
|
blocks), and A1 + H2 closed in `requirements.md`.**
|
||||||
|
|
||||||
|
## §1. State at handoff
|
||||||
|
|
||||||
|
**Branch:** `main`. Working tree **clean**; all work committed. Commits
|
||||||
|
are local (push is the user's step).
|
||||||
|
|
||||||
|
**Tests: 2499 passing / 0 failing / 0 skipped / 1 ignored** (the
|
||||||
|
long-standing `friendly` doctest). **Clippy clean** (nursery, all
|
||||||
|
targets). Breakdown: 1799 lib + 500 `it` + 200 typing-surface-matrix.
|
||||||
|
|
||||||
|
**Open Gitea issues (4, all enhancement, all filed this session):**
|
||||||
|
- **#35** — enforce `cargo fmt` across the codebase (single reformat +
|
||||||
|
CI gate). The tree is *not* fmt-clean (~1800 pre-existing diffs); do it
|
||||||
|
once, coordinated with CI, before first publication.
|
||||||
|
- **#36** — `help` collapses advanced-SQL forms onto their simple sibling
|
||||||
|
(a `help`-list dedup artifact); they deserve distinct help content.
|
||||||
|
- **#37** — `hint` clause-concept hints (`on delete` actions, constraint
|
||||||
|
slots, `with pk`, cardinality) — a deferred `hint.concept.<topic>`
|
||||||
|
layer.
|
||||||
|
- **#38** — `hint` pre-submit-diagnostic route + the ~33 `diagnostic.*`
|
||||||
|
tier-3 blocks (deferred; `Diagnostic` carries no class key).
|
||||||
|
|
||||||
|
## §2. ADR-0052 follow-up — vestigial worker `source` unwind (`e8fa859`)
|
||||||
|
|
||||||
|
The first task from handoff-69 §3. ADR-0052 moved success-journaling out
|
||||||
|
of the worker, leaving the `source` that handlers threaded purely for the
|
||||||
|
old `history.log` write dead. **Bigger than the handoff estimated** (it
|
||||||
|
framed it as ~28 call-site edits): the cascade ran through ~30 worker
|
||||||
|
handlers + the `DescribeTable`/`QueryData`/`RunSelect` request fields +
|
||||||
|
their `DatabaseHandle` methods (~164 mostly-test call sites). Fully
|
||||||
|
unwound, compiler-guided, **no behaviour change** (journaling uses a
|
||||||
|
`source_for_journal` clone at the spawn, independent of the worker). The
|
||||||
|
only worker `source` left is the snapshot/undo label. Amended ADR-0052
|
||||||
|
*Consequences* + README. (Two scope forks escalated + user-approved.)
|
||||||
|
|
||||||
|
## §3. H2 — contextual `hint` (ADR-0053), Phases A–D — **shipped**
|
||||||
|
|
||||||
|
The bulk of the session. ADR-0053 settles the `hint` slot ADR-0003 left
|
||||||
|
"ADR pending"; **closes A1** (all 15 app commands now exist) and
|
||||||
|
**requirements H2**. Read ADR-0053 before touching this area — it went
|
||||||
|
through three revisions and several user decisions.
|
||||||
|
|
||||||
|
### The design (all user-chosen)
|
||||||
|
- **Two surfaces:** an **F1 keybinding** → tier-3 hint for the *live*
|
||||||
|
partial input (read-only overlay — never touches buffer/cursor/memo);
|
||||||
|
a submitted **`hint` command** → expands on the *most recent runtime
|
||||||
|
error*. No topic arg (contextual only; `help <topic>` owns reference).
|
||||||
|
- **Tier-3 teaching layer** beneath the existing tier-1 (colour / error
|
||||||
|
headline) and tier-2 (ambient one-liner; the error `hint:` shown **by
|
||||||
|
default** since `Verbosity::Verbose` is the default). Each block is
|
||||||
|
`what` / `example` / `concept`, rendered as a `Hint` heading + aligned
|
||||||
|
labels.
|
||||||
|
- **Per-form keying** (Phase-B revision — the original per-node `hint_id`
|
||||||
|
was too coarse for multi-form commands like `add`/`drop`/`show`): a new
|
||||||
|
**`hint_ids: &[&str]`** field on `CommandNode` mirroring `usage_ids`,
|
||||||
|
resolved by `hint_key_for_input_in_mode` (reuses `usage_key`'s
|
||||||
|
form-word disambiguation + a mode-primary fallback for shared entry
|
||||||
|
words so advanced `insert` → `sql_insert`, simple → `insert`).
|
||||||
|
- **Comprehensive for v1 = command forms + 9 runtime error classes**
|
||||||
|
(the ~33 `diagnostic.*` classes were **deferred**, #38 — see §4).
|
||||||
|
|
||||||
|
### Key files
|
||||||
|
- `src/dsl/command.rs` — `AppCommand::Hint`.
|
||||||
|
- `src/dsl/grammar/app.rs` — `HINT` node + `build_hint`.
|
||||||
|
- `src/dsl/grammar/mod.rs` — the `hint_ids` field, `hint_key_for_input_in_mode`,
|
||||||
|
the factored `pick_form_key`, and the two **comprehensiveness coverage
|
||||||
|
tests** (every node has a resolving `hint.cmd.*`; every runtime error
|
||||||
|
class has a `hint.err.*`).
|
||||||
|
- `src/app.rs` — F1 arm in `handle_key` (read-only overlay, placed before
|
||||||
|
the completion-memo clear); `note_hint_for_input` / `note_hint_for_recent_error`
|
||||||
|
/ `note_getting_started` / `emit_tier3_block`; `last_error_hint_key`
|
||||||
|
state (set in `handle_dsl_failure`, cleared in `submit` for DSL
|
||||||
|
commands).
|
||||||
|
- `src/friendly/translate.rs` — `error_hint_class` (maps a `DbError` +
|
||||||
|
ctx to its `hint.err.<class>`; mirrors `translate`'s dispatch — keep in
|
||||||
|
sync, unit-tested).
|
||||||
|
- `src/friendly/strings/en-US.yaml` + `keys.rs` — the corpus under
|
||||||
|
`hint.cmd.<form>` / `hint.err.<class>` + `hint.block.*` labels +
|
||||||
|
`shortcut.hint`.
|
||||||
|
- `src/ui.rs` — ADR-0051 strip advertises **F1** (editing + default
|
||||||
|
states); 12 full-panel snapshots re-accepted.
|
||||||
|
|
||||||
|
### Phases (one commit each unless noted)
|
||||||
|
- **A** (`050b363`) skeleton + tier-2 fallback; **B** (`4a5fd1b`) per-form
|
||||||
|
keying + 3 exemplars; **C** content in 5 batches (`4bdfce6` app,
|
||||||
|
`6429b56` DDL, `9c4d520` DML, `97970f2` advanced-SQL, `b6b98ad` runtime
|
||||||
|
errors) + `417cbc8` diagnostic deferral; **D** (`447112b`) coverage gate
|
||||||
|
+ F1 strip + status flips; **/runda fix** (`329adfc`) — see §3.1.
|
||||||
|
|
||||||
|
### 3.1 — what the final `/runda` caught (don't skip)
|
||||||
|
Per-batch substring tests masked a **presentation gap**: `emit_tier3_block`
|
||||||
|
was emitting three *bare, unlabelled* lines, deviating from the approved
|
||||||
|
exemplar format. Fixed to render a `Hint` heading + aligned `What:` /
|
||||||
|
`Example:` / `Concept:` lines, **locked by an `insta` snapshot**
|
||||||
|
(`hint_block_insert`). Also confirmed the `Next:` line (ADR D2 exemplar)
|
||||||
|
is correctly **omitted** — tier-2 ambient already owns live
|
||||||
|
position-awareness. Lesson for the next content/UI work: **add a rendered
|
||||||
|
snapshot early**; substring asserts don't see layout.
|
||||||
|
|
||||||
|
## §4. Deferrals (all tracked, all user-confirmed)
|
||||||
|
|
||||||
|
- **#38 diagnostic route + `diagnostic.*` blocks** — `Diagnostic`
|
||||||
|
(`walker/outcome.rs`) carries only its rendered `message`, not a class
|
||||||
|
key, so the F1 diagnostic route would need a `class` field threaded
|
||||||
|
through every diagnostic site (broad) for marginal value (tier-2
|
||||||
|
already surfaces diagnostics; many duplicate runtime classes). F1 still
|
||||||
|
shows the useful command block when a diagnostic is present.
|
||||||
|
- **#37 clause-concept hints** — per-form is the right tier-3 granularity;
|
||||||
|
clause-level concepts are a separate `hint.concept.<topic>` layer for
|
||||||
|
later.
|
||||||
|
- **#36 `help` advanced-SQL** — out of H2's scope (touches shipped `help`).
|
||||||
|
|
||||||
|
## §5. CI branch merged into `main` (not my work)
|
||||||
|
|
||||||
|
Mid-session the **`ci` branch was merged** (commits `47a0816`, `138e766`
|
||||||
|
+ the `ci:`/`build:`/`docs(ci):` commits). `main` now carries a CI
|
||||||
|
pipeline, a nix flake, and **D1 cross-platform release builds** (matrix +
|
||||||
|
macOS), documented under a **new `docs/ci/adr/` namespace** (ci-001..003).
|
||||||
|
Implications for the roadmap: **D1 (cross-platform binaries) is now
|
||||||
|
substantially underway** — re-assess D1/D2/D3 status against what landed
|
||||||
|
before treating them as open. My H2 work is layered cleanly on top (all
|
||||||
|
green post-merge).
|
||||||
|
|
||||||
|
## §6. Next session — start here
|
||||||
|
|
||||||
|
1. **Push** (user step) — 30-odd local commits incl. the CI merge + all
|
||||||
|
of H2.
|
||||||
|
2. **Re-baseline the roadmap** against the merged CI work: D1/D2/D3 and
|
||||||
|
**TT5 CI** are partly/largely done now — read `docs/ci/adr/` and the
|
||||||
|
workflows before assuming they're open (handoff-69 §5 predates this).
|
||||||
|
3. **#35 (cargo fmt gate)** is the natural pairing with the now-merged CI
|
||||||
|
— the user wanted it done once, before first publication.
|
||||||
|
4. Other `requirements.md` open items (verify against CI merge first):
|
||||||
|
**TT4** PTY tier-4 (still unwired), **I1** multi-line input, **I5/B3**
|
||||||
|
in-flight cancellation, **V4** session journal (own ADR), **TU1**
|
||||||
|
tutorial system (own ADR). H2/A1 are now **done**.
|
||||||
|
5. The H2 deferrals (#36/#37/#38) are available if the user wants to
|
||||||
|
round out the hint/help surface.
|
||||||
|
|
||||||
|
## §7. How to take over
|
||||||
|
|
||||||
|
1. Read handoffs 68 → 69 → 70, `CLAUDE.md`, `docs/requirements.md`.
|
||||||
|
2. Confirm green: `cargo test` (expect **2499 pass / 1 ignored**) +
|
||||||
|
`cargo clippy --all-targets` (clean).
|
||||||
|
3. Read `docs/ci/adr/` (the merged CI work) before touching CI/release/D*.
|
||||||
|
4. For anything in the `hint` area, read **ADR-0053** first (3 revisions
|
||||||
|
+ deferrals #37/#38). For journaling, ADR-0052 (+ its 2026-06-14
|
||||||
|
follow-up note).
|
||||||
|
5. Project workflow unchanged: phased, test-first, `/runda` + DA before
|
||||||
|
commits, ADR amendment + README index-upkeep for decided-area changes,
|
||||||
|
confirm commit messages with the user.
|
||||||
|
6. Consider a `cargo sweep` at this milestone (`target/` grows; see
|
||||||
|
CLAUDE.md "Build hygiene").
|
||||||
+47
-11
@@ -61,11 +61,32 @@ since ADR-0027.)
|
|||||||
|
|
||||||
## Distribution and install
|
## 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 1–3) on the **Linux** runner for every branch
|
||||||
|
push / PR, and `release-macos` runs the suite natively on the
|
||||||
|
**macOS** runner. **Windows is build-only** — cross-compiled, not
|
||||||
|
executed (no Windows runner). **Tier 4** (PTY, TT4) is still
|
||||||
|
unwired, so "all tiers" is not yet fully met. "Stable Rust" is
|
||||||
|
satisfied by the flake's pinned `1.95.0` (a stable release, not
|
||||||
|
nightly). Remaining for full TT5: a Windows execution runner and
|
||||||
|
Tier-4 PTY in CI.)*
|
||||||
|
|
||||||
## Cross-cutting
|
## Cross-cutting
|
||||||
|
|
||||||
|
|||||||
+88
-6
@@ -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
@@ -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: &[],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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)]
|
||||||
|
|||||||
@@ -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", &[]),
|
||||||
|
|||||||
@@ -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` (1–2 sentences) / `example`
|
# hints (`hint.err.<class>`), each a `what` (1–2 sentences) / `example`
|
||||||
@@ -505,12 +512,135 @@ hint:
|
|||||||
what: "Change a column's type, converting the existing values."
|
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
|
||||||
|
|||||||
+2
-2
@@ -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
|
||||||
|
|||||||
+2
-2
@@ -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
|
||||||
|
|||||||
@@ -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"],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user