diff --git a/CLAUDE.md b/CLAUDE.md index a925f5b..952c2a3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -37,9 +37,9 @@ Current decisions at a glance (each backed by an ADR): simple to advanced (ADR-0003). No other sigils. - **Project format:** `project.yaml` + `data/.csv` + `history.log`; `playground.db` is a derived artifact (ADR-0004, - amended by ADR-0015). Implemented through Iteration 4 + - cleanup; export/import (Iter 5) and migration framework / - --resume / persistent input history (Iter 6) pending. + amended by ADR-0015). Fully implemented (ADR-0015 Iterations + 1–6): export/import, `--resume`, persistent input history, and + the migration framework scaffold are all done. - **Project storage runtime:** every command persists through to db + yaml + csv + history.log in one execution context, gated by the combined db persistence logic; commit-db-last ordering @@ -338,16 +338,8 @@ all of `target/`, forcing a full from-scratch rebuild). These are explicitly tracked (mostly in `requirements.md`) but not yet implemented: -- **Project storage** (track 2): largely implemented through - Iteration 4 + cleanup pass + safety hardening (Iterations - 1–4 of ADR-0015). Pending pieces: `export` / `import` (Iter - 5), `--resume` + persistent input history hydration + - migration framework scaffold (Iter 6). - **Modify relationship** (C3a): drop+add covers the use case today. -- **m:n convenience** (C4): auto-generates a junction table - with appropriate FKs — depends on relationships being solid - (they are). - **Strong syntax-help in parse errors** (H1a): point users at missing keywords/clauses rather than the unexpected character. *(H1 — the friendly **database**-error layer — is @@ -358,11 +350,8 @@ not yet implemented: - **Session log + Markdown export** (V4): the bigger UX project — scrollable session journal, smart structure rendering, save-as-markdown. -- **Readline shortcuts** (I1b): Ctrl-A/Ctrl-E, Ctrl-W/Ctrl-K/ - Ctrl-U. - **Multi-line input** (I1): Enter inserts newline, Ctrl-Enter submits. -- **Tab completion** (I3), **syntax highlighting** (I4). - **ER diagram export** (V3). - **Full TT5** (CI): the pipeline is live (see the CI decision above / `docs/ci/adr/`), but "all tiers on all OSes" isn't diff --git a/docs/adr/0047-demonstration-overlay-layer.md b/docs/adr/0047-demonstration-overlay-layer.md index 4258e95..9678648 100644 --- a/docs/adr/0047-demonstration-overlay-layer.md +++ b/docs/adr/0047-demonstration-overlay-layer.md @@ -414,5 +414,41 @@ time-boxed-`recv` path. We therefore test the **pure pieces** exhaustively (label fn, capture state machine, nearest-deadline helper) and assert plumbing via Tier-3, rather than over-claiming an integration test of the `tokio` timeout itself. - - + +## Amendment 1 — `Ctrl-G` demo-mode alias for F1 (2026-06-15) + +**Context.** The contextual `hint` overlay (ADR-0053 / H2) is opened with +**F1**. But F1 reaches the app only as an escape sequence (`\eOP` / +`\e[11~`), and the `autocast` recorder used for our screencasts **cannot +emit escape sequences** — so a cast can never trigger F1, and the single +most teaching-relevant overlay is unreachable in recordings. The same +wall already bit step-captions (which is why `Ctrl+]`, a single control +byte, was chosen over `Ctrl+!`). + +**Decision.** In **demo mode only**, **`Ctrl-G`** is an alias for F1. It +runs the exact F1 hint logic (live-input → form hint; empty input → +recent-error / getting-started) and is **badged as `[F1]`** (not +`[CTRL-G]`) so a recorded cast is visually identical to a genuine F1 +press. `Ctrl-G` is the only viable choice: it is a single legacy control +byte autocast can send, whereas `Ctrl`+digit (e.g. the mnemonic `Ctrl-1`) +is **not encodable in a legacy terminal at all** — digits have no control +byte, so `Ctrl-1` arrives as a bare `1`; the kitty protocol *would* encode +it but only as an escape sequence (the very thing autocast can't send), +and this app deliberately does not enable keyboard-enhancement flags. + +**Why demo-gated.** The shipped keymap stays F1-only — a real user never +trips the alias, and demo mode is also the mode teachers/presenters run, +so the alias is available exactly where it's wanted. Outside demo mode +`Ctrl-G` falls through to the inert catch-all (the `Char(c)` insert arm +excludes CONTROL, so no `g` is typed). + +**Scope.** `hint_key` guard in `App::handle_key` gains the demo-gated +`Ctrl-G` disjunct; `demo_badge_label` maps `Ctrl-G → [F1]` (consulted +only in demo mode). Test-first: three `app.rs` Tier-1 tests (alias fires +on input + on empty input; inert when demo off) + the badge-map +assertion. The keybinding strip (ADR-0051) is **not** changed — F1 stays +the advertised key; `Ctrl-G` is a recorder aid, and the badge already +reads `[F1]`. + +*(Editorial: this amendment also removed two stray `` / +`` lines accidentally committed at the end of this file.)* diff --git a/docs/adr/README.md b/docs/adr/README.md index 85daf6e..c831b28 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -59,7 +59,7 @@ This directory contains the project's ADRs, recorded per - [ADR-0044 — Relationship visualization (two-table connector diagrams)](0044-relationship-visualization.md) — **Accepted 2026-06-09; implemented 2026-06-10** (closes `requirements.md` V1; second `/runda` pass over the implementation; §3 last-resort helper line considered and rejected). Resolves **ADR-0016 OOS-1** and closes the open half of `requirements.md` **V1** ("a selected relationship as two tables joined by a line"). Renders a relationship as **Style A** (two structure boxes + connector). **Reach = "relationship-relevant"** (user-chosen over global / show-only): diagrams on the surfaces where the relationship is the *subject* — `show relationship ` (one full diagram), `show table ` (T's structure box then a **Relationships** section of **stacked compact** per-relationship diagrams — chosen over a focal-centred subgraph: no crossing lines, scales via scroll, two-boxes-wide fits any terminal), and relationship DDL echoes (`add`/`drop`/`modify relationship`); incidental DDL echoes (`add column`, `drop index`, `change column`, plain `create table`) keep the terse prose, via a `Diagram`|`Prose` render mode on `render_structure`. Reading convention **child(FK)-left / parent-right, arrow →, `n`…`1` cardinality**, applied uniformly; every box gets a **bold title row + rule** so the name can't read as a column. **Compound FKs** (ADR-0043) route one connector per positional pair + an explicit pairing line. **Width-aware** (first in the codebase) but **App-side**: `render_structure`/diagram rendering runs in `app.rs` (the worker only returns `TableDescription`s), a new `App::last_output_width` (set from `ui.rs`) drives side-by-side vs a **vertical-stack** fallback + last-resort "run `show relationship`" pointer; rendered once at command time, **no live reflow** (V4). `show relationship`'s worker path (`do_show_one`, prose-only) is restructured to return both endpoint `TableDescription`s. Styling reuses **ADR-0028** App-side styled runs (new classes: table-name/key/connector/cardinality/action) — no worker→UI contract change. **Partially supersedes ADR-0016 §5** (prose block replaced on relationship-subject surfaces, retained on incidental ones); extends §4 (layout width-awareness, still no cell truncation) and §6 (per-span theming). Tests: insta snapshots (single, compound, vertical fallback, helper line, self-referential, multi-rel `show table`) + width-threshold/routing unit tests + Tier-3 wiring; enumerated prose-fallout updates (`output_render.rs:121/135/793`, the relationships snapshot, `walking_skeleton.rs:477/530`). A `/runda` DA pass corrected three inverted-architecture claims (App-side rendering, untracked width, prose-in-worker show-relationship) before acceptance. OOS: user-configurable display setting (OOS-7), live reflow (V4), whole-DB ER export (V3), m:n (C4), ASCII fallback (ADR-0016 OOS-5) - [ADR-0045 — `create m:n relationship` convenience command (C4)](0045-mn-convenience.md) — **Accepted + implemented 2026-06-10** (closes `requirements.md` **C4**; all forks user-confirmed + a `/runda` DA pass that verified the `do_create_table` reuse against code and corrected the "no PK-less tables" assumption — advanced SQL `create table t (a int)` has none, so a parent-PK guard is retained). Implementation corrected a second ADR premise: "the walker already dispatches multiple nodes per entry word" held only in *advanced* mode — two simple-mode spots (dispatcher `decide`, completion continuation-merge) assumed ≤1 DSL form per entry word and were generalized **behaviour-preservingly** (dispatch reduces to the old single-candidate commit; completion merge gated on `simple_count > 1`). Junction echo wired (`render_create_m2n`, round-trips as SQL). `create m:n relationship from to [as ]` generates a junction table with one FK column per parent PK column, a **compound PK over all the FK columns** (the textbook junction — the pair is unique, no duplicate links), and **two 1:n relationships**, all in **one transaction = one undo step** (built by reusing `do_create_table`, which already takes `foreign_keys` + writes relationship metadata — no batch bracketing). Forks all user-chosen: junction PK = compound-over-FKs (vs surrogate serial / no PK); referential actions = **`CASCADE`** on delete+update (vs NO ACTION / RESTRICT); naming = auto `{T1}_{T2}` + optional `as` (vs auto-only); available in **both modes** (Simple-category DSL, like the sibling relationship commands). FK columns named `{parent_table}_{pk_column}` (disambiguates shared `id`; generalises to compound parents via ADR-0043), typed via `fk_target_type` (ADR-0011). A distinct `Command::CreateM2nRelationship` (not lowered to `CreateTable`) preserves command identity (X5) and lets the teaching echo speak in m:n terms. Cross-cutting wiring enumerated: separate `CREATE_M2N` `CommandNode` (own `help_id`/`usage_ids`), `("m","m:n")` completion composite, `HintMode`s, grammar-driven highlighting, `help`/`help create`, `parse_error_pedagogy` near-miss matrix, teaching echo. OOS: **self-referential m:n** (`from T to T`) refused outright (user-confirmed "full stop" — directional column-naming is more than this beginner convenience warrants); per-relationship action overrides; extra junction payload columns; m:n diagram echo; renaming the auto-generated relationships - [ADR-0046 — Schema sidebar focus/navigation mode and responsive input & hint layout (UI #20/#21/#23)](0046-sidebar-navigation-and-responsive-input-hint.md) — **Accepted + implemented 2026-06-10, phased A→B→C** (8 commits `9f5f76b`…`22bec61`; closes Gitea **#20** hint jumpiness, **#21** left-column improvements, **#23** long input — all forks user-confirmed, including the persistent show/hide toggle which is **deferred**: the Ctrl-O peek covers #21's "keystroke to show and hide"). Two decisions landed differently from the draft (recorded inline): relationship data on **`App`** not `SchemaCache` (DB2); the nav overlay clears **only the sidebar strip + a one-column gutter**, panels staying visible behind (DC2). Treats the three UI issues as one coupled decision because they share the terminal's width/height budget. **Phase A (input & hint):** the hint panel's height becomes a function of **terminal geometry, fixed between resizes** (not of hint content), eliminating the #20 jump at its source — measured catalog shows ≥ ~54-col right-column width never needs > 2 hint lines, so 3 lines is a rare narrow-terminal-only case; height buckets `H<40` compact (input 1 row + horizontal scroll / hint 2) vs `H≥40` comfortable (input 2 rows soft-wrap / hint 2), output `Min(5)` honoured first under degradation; input gains horizontal scroll (`input_scroll_offset`, single logical `String` — **not** I1 multi-line) and 2-row soft-wrap display when tall, preserving ADR-0027's 6-col indicator reserve. **Phase B (sidebar):** the 26-col Tables column is **kept but made optional and richer** (not deleted — pedagogy wins ties) — **width-derived session-only** visibility (visible iff width > 90 or a Ctrl-O peek is active — no stored field; hides at width ≤ 90 so the 90-col screencasts drop it; ADR-0015 format untouched), plus a **relationships panel** rendered narrow with endpoints broken at the arrow, ellipsized — a **separate sibling panel** that **overrides S2**'s nested-list extension model (relationships are cross-table). the full records live on a new **`App.relationships`** field (revised from the ADR's original `SchemaCache.relationship_details` at implementation — `SchemaCache` is walker-facing and needs only the names, kept in `relationships: Vec`; details are UI-only, so `App` mirrors `app.tables` and avoids ~23 fixture edits), delivered by `Database::read_all_relationships` + an `AppEvent::RelationshipsRefreshed`; the two left panels split vertically with the relationships panel floored at 5 rows ("(none)" when empty) and capped at 50 % of the column (DB4). **Phase C (navigation mode):** **`Ctrl-O`** enters a focus cycle (Input → Tables → Relationships → Input; `Esc` exits) orthogonal to the ADR-0003 input mode — **`Ctrl-B` was rejected on review as the default tmux prefix** (unreachable inside tmux); the focused panel **expands to ~40–50 cols as a `Clear` overlay** (right panels stay unchanging underneath) and scrolls via **Up/Down (line) + PageUp/PageDown (page)** (context-rebind, reusing the output-scroll viewport mechanism), with an accent focus border; all non-nav keys inert in nav mode (and nav keys inert while a modal is open). Forks all user-chosen: keep-optional-richer (vs remove/narrow); navigation-mode (vs modeless modifier scroll); `Ctrl-O` (Ctrl-B rejected = tmux prefix); overlay (vs layout re-split); inert-non-nav-keys; geometry-fixed hint height; `H<40/≥40` thresholds; session-only persistence; Up/Down line-scroll; **separate relationships panel overriding S2**; **no hint-area toggle** (S4's stale "keyboard-toggleable" claim struck — never implemented, unwanted). A pre-build `/runda` DA pass drove these corrections: caught the `Ctrl-B`/tmux collision, the `SchemaCache` retype that would have broken completion, the 2-row-input/indicator placement, the missing nav-mode key disposition + modal gate, and three unreferenced requirements (S1 evolved, S2 overridden, S4 corrected); also cross-checked open issue **#22** (overlay/annotation layer — separate ADR, adjacent). OOS: true multi-line input (I1); readline shortcuts (I1b); cross-session sidebar persistence; output as a third nav focus; relationship search/edit from the panel; hint-area toggle; #22's annotation layer. Accepted consequence: the 90-col visibility threshold makes a terminal's output *narrower* when widened across the boundary (sidebar appears); **Amendment 1, 2026-06-12** (issue #25): DC3's focus accent is now a **non-bold accent colour** (`theme.mode_simple`, blue) rather than bold bright-`fg` — bold box-drawing glyphs render as broken/gapped line-art in the asciinema cast player (and are fragile in some terminals), so `panel_border_style` carries no `Modifier::BOLD` on a border (bold stays fine on text spans); pure style change — the text-only Tier-2 snapshots were unaffected, the Tier-1 assertion was updated, and a render-level test now checks the focused border cells carry the accent and no bold -- [ADR-0047 — Demonstration overlay layer (keystroke badges + step captions)](0047-demonstration-overlay-layer.md) — **Accepted 2026-06-10; implemented 2026-06-11, phased A→B→C (closes Gitea #22)** (commits `f879d54`→`2d0f4b2`; no `requirements.md` item — tracked by issue + ADR per convention; all forks user-confirmed + a pre-build `/runda` pass that produced 10 tightening findings and a whole-implementation `/runda` pass that returned PASS, no blockers). An in-app **demonstration mode** (`--demo` flag / `RDBMS_PLAYGROUND_DEMO` env, **off by default, zero footprint when off**) that renders two transient overlays so `autocast` screencasts — and live teaching, and a future guided-lesson system — can show otherwise-invisible interactions. **Keystroke badges** (`[TAB]`, `[ENTER]`, `[UP]`, …): **automatic, app-detected** over a fixed set of glyph-less keys (the app already sees every key, so it re-records for free), label via a pure `demo_badge_label(&KeyEvent)`; the badge **auto-expires on a ~1.5 s timer** that extends the runtime's existing time-boxed-`recv` arm condition (`debounce.is_armed() || badge_pending`; expiry `Instant` in the runtime, `App.demo_badge` the render mirror — mirroring the `input` vs `input_indicator` split). **Step captions**: a **stealth, control-code-delimited input buffer** toggled by **`Ctrl+]`** (byte `0x1D` → arrives as `Char('5')+CONTROL`, verified against crossterm 0.29 `parse.rs:110-113`; chosen over `Ctrl+!`, which is **not a single ASCII byte so autocast cannot send it** — the same wall as arrow keys, R4) — typed characters accumulate **invisibly** (prompt untouched, no echo/history), `Backspace` edits, other keys inert, a second `Ctrl+]` **commits** to the caption box (empty commit dismisses); lives in pure-sync `App::update()`, **intercepted before the modal gate** so captions/badges work **over the load picker** (the `#24` projects cast). Both render as **floating flat black-on-yellow rectangles** (solid fill, **no border glyphs** — a one-cell text margin, deliberately unlike the app's bordered panels; user decision post-build, `2d0f4b2`) **at the output panel's inner bottom-right**, drawn **last over modals**, badge **stacked above** the caption, **no layout reflow**; caption **word-wraps to ≤ 3 lines** (3–5 rows), badge fixed 3 rows; clamp/skip guard for tiny terminals; a new **`App.last_output_area: Rect`** (set in `render_output_panel`) gives the top-level draw the anchor. Caption persists **until the next keystroke**; badge suppressed while capturing. Forks all user-chosen: `--demo` activation (vs hidden command / chord); automatic badges (vs scripted); stealth buffer (vs typed-command / preloaded-file); floating bottom-right boxes (vs HUD / banner / subtitle); `Ctrl+]` trigger; wrap-to-3-line captions; ~1.5 s badge / next-keystroke caption timing. Tested test-first across Tier 1 (label fn, capture state machine incl. over-modal + demo-off gate, nearest-deadline helper), Tier 2 (insta snapshots: badge/caption/both-stacked at 90×26 light+dark, short-terminal clamp), Tier 3 (`--demo` plumbing, badge set/suppressed, caption-without-input wiring), CLI (`--demo` parse + env fallback) — with an **honest limit** noted: the `tokio` timer wiring inside `run_loop` is exercised via the pure pieces + Tier-3 plumbing, not a standalone integration test of the timeout (same posture as the existing `IndicatorDebounce`). One intentional, user-acknowledged behaviour: `Ctrl-C` is inert while capturing (every non-`Ctrl+]` key is, by spec). Final tally **2290 passing / 0 failing / 0 skipped** (1 long-standing ignored doctest), clippy clean. OOS: scripted/manual badge push; badges for glyph keys; configurable styling/placement; the guided-lesson system itself (own ADR); cross-session/-switch persistence; localised caption content; arrow-only cast interactions (output-pane scroll); wiring the overlays into the website `casts.mjs` scripts (website-branch follow-up). Implementation phased **A** (`--demo` plumbing) → **B** (badges) → **C** (captions) + a flat-rectangle restyle +- [ADR-0047 — Demonstration overlay layer (keystroke badges + step captions)](0047-demonstration-overlay-layer.md) — **Accepted 2026-06-10; implemented 2026-06-11, phased A→B→C (closes Gitea #22)** (commits `f879d54`→`2d0f4b2`; no `requirements.md` item — tracked by issue + ADR per convention; all forks user-confirmed + a pre-build `/runda` pass that produced 10 tightening findings and a whole-implementation `/runda` pass that returned PASS, no blockers). An in-app **demonstration mode** (`--demo` flag / `RDBMS_PLAYGROUND_DEMO` env, **off by default, zero footprint when off**) that renders two transient overlays so `autocast` screencasts — and live teaching, and a future guided-lesson system — can show otherwise-invisible interactions. **Keystroke badges** (`[TAB]`, `[ENTER]`, `[UP]`, …): **automatic, app-detected** over a fixed set of glyph-less keys (the app already sees every key, so it re-records for free), label via a pure `demo_badge_label(&KeyEvent)`; the badge **auto-expires on a ~1.5 s timer** that extends the runtime's existing time-boxed-`recv` arm condition (`debounce.is_armed() || badge_pending`; expiry `Instant` in the runtime, `App.demo_badge` the render mirror — mirroring the `input` vs `input_indicator` split). **Step captions**: a **stealth, control-code-delimited input buffer** toggled by **`Ctrl+]`** (byte `0x1D` → arrives as `Char('5')+CONTROL`, verified against crossterm 0.29 `parse.rs:110-113`; chosen over `Ctrl+!`, which is **not a single ASCII byte so autocast cannot send it** — the same wall as arrow keys, R4) — typed characters accumulate **invisibly** (prompt untouched, no echo/history), `Backspace` edits, other keys inert, a second `Ctrl+]` **commits** to the caption box (empty commit dismisses); lives in pure-sync `App::update()`, **intercepted before the modal gate** so captions/badges work **over the load picker** (the `#24` projects cast). Both render as **floating flat black-on-yellow rectangles** (solid fill, **no border glyphs** — a one-cell text margin, deliberately unlike the app's bordered panels; user decision post-build, `2d0f4b2`) **at the output panel's inner bottom-right**, drawn **last over modals**, badge **stacked above** the caption, **no layout reflow**; caption **word-wraps to ≤ 3 lines** (3–5 rows), badge fixed 3 rows; clamp/skip guard for tiny terminals; a new **`App.last_output_area: Rect`** (set in `render_output_panel`) gives the top-level draw the anchor. Caption persists **until the next keystroke**; badge suppressed while capturing. Forks all user-chosen: `--demo` activation (vs hidden command / chord); automatic badges (vs scripted); stealth buffer (vs typed-command / preloaded-file); floating bottom-right boxes (vs HUD / banner / subtitle); `Ctrl+]` trigger; wrap-to-3-line captions; ~1.5 s badge / next-keystroke caption timing. Tested test-first across Tier 1 (label fn, capture state machine incl. over-modal + demo-off gate, nearest-deadline helper), Tier 2 (insta snapshots: badge/caption/both-stacked at 90×26 light+dark, short-terminal clamp), Tier 3 (`--demo` plumbing, badge set/suppressed, caption-without-input wiring), CLI (`--demo` parse + env fallback) — with an **honest limit** noted: the `tokio` timer wiring inside `run_loop` is exercised via the pure pieces + Tier-3 plumbing, not a standalone integration test of the timeout (same posture as the existing `IndicatorDebounce`). One intentional, user-acknowledged behaviour: `Ctrl-C` is inert while capturing (every non-`Ctrl+]` key is, by spec). Final tally **2290 passing / 0 failing / 0 skipped** (1 long-standing ignored doctest), clippy clean. OOS: scripted/manual badge push; badges for glyph keys; configurable styling/placement; the guided-lesson system itself (own ADR); cross-session/-switch persistence; localised caption content; arrow-only cast interactions (output-pane scroll); wiring the overlays into the website `casts.mjs` scripts (website-branch follow-up). Implementation phased **A** (`--demo` plumbing) → **B** (badges) → **C** (captions) + a flat-rectangle restyle. **Amendment 1 (2026-06-15):** in demo mode, **`Ctrl-G` aliases F1** (the ADR-0053 hint overlay) and **badges as `[F1]`**, so an `autocast` cast — which can't emit F1's escape sequence — looks identical to a real F1 press; `Ctrl-G` is the only fit (`Ctrl`+digit is unencodable in a legacy terminal, e.g. `Ctrl-1` arrives as a bare `1`), demo-gated so the shipped keymap stays F1-only; also cleaned two stray ``/`` lines from the ADR file. - [ADR-0048 — `seed` fake-data generation command](0048-seed-fake-data-generation.md) — **Accepted 2026-06-11; Phase 1 + Phase 2 implemented 2026-06-11** (Phase 1 commits `202e25a`→`fbd219b`; design settled with the user across an extended fork dialogue, hardened by a pre-build `/runda` pass (six blockers folded in), a post-implementation `/runda` pass (eight gaps closed — FK/shortid determinism so **D4 holds with no exceptions**, plus six untested ADR decisions), and a Phase-2 pre-build `/runda` pass (which caught the no-date-literal-token reality → the D2 quoted-dates amendment), and a post-implementation `/runda` pass (which added a friendly error for a bounded override on a UNIQUE column — see D2); **2400 tests pass, clippy clean**). Closes `requirements.md` **SD1** and the core of **SD2**; closes the `seed` half of **A1**. **Phase 1 shipped:** whole-row `seed
[count] [--seed ]` with realistic name-aware generation (the `fake` crate + a type-gated heuristic catalogue, table-context name disambiguation, hand-rolled `product` generator, bounded dates), identifier + constraint uniqueness incl. junction distinct-combos, FK sampling from existing parent rows (empty-parent error), `IN`-CHECK derivation + complex-CHECK advisory, a required-column block guard, `--seed` reproducibility (serial/FK/shortid all deterministic), undo as one batch step, replay as a data write, a capped auto-show preview, the enum/CHECK advisory, and an O(N) single-transaction insert path. **Phase 2 shipped (2026-06-11):** the `set` override clause (D2 — fixed value / pick-list / `as ` / `between` range, **quoted** dates per the D2 amendment, type-aware, override drops the column from the advisory) and the `
.` column-fill form (D1 form 2 — an UPDATE over existing rows, refusing PK/autogen targets, empty-table no-op, FK/unique-respecting, one undo step), with the new `KNOWN_GENERATORS` vocabulary (D9), a range `Generator`, full completion/highlight (`HighlightClass::Function`)/validity (`IdentSource::Generators`)/help/pedagogy wiring, and the D13 advisory's Phase-2/3 wording. Further SD2 increments (custom generators, NULL injection, multi-locale, recursive auto-seed) out of scope. Closes `requirements.md` **SD1** and the core of **SD2**; closes the `seed` half of **A1** (the other being `hint`/**H2**). A dedicated `seed` command (own AST variant + `do_seed` executor, **both modes**) generating **realistic, name-aware** fake data. Two forms: **`seed
[count]`** (new rows, default **20**, capped) and **`seed
.`** (fill a column on existing rows, an UPDATE). Generation adds the **`fake` crate** (v5, English) driven by a **type-gated, token-matched name-heuristic catalogue** (~30 patterns, documented false-positive guards), with **table-context** disambiguating the `name`/`title` family (`products.name`→product, `users.name`→person, `vendors.name`→company), a **hand-rolled `product` generator** (`fake` has no commerce module), **bounded dates** (`date`/`timestamp`/`dob`/`*_at` recognised, recent windows — never "all of history"), the **identifier family** (`id`/`code`/`ref`/`number`, non-FK/non-PK) → **unique sequential**, and **enum-ish names** (`role`/`status`/`type`/…) left generic + a **post-seed Hint advisory** pointing at `set … in (…)`. A **`set` override clause** — `= value` / `in (a,b,c)` / `as ` / `between a and b` (numeric **and** date), reusing ADR-0026 operators — answers the heuristic-miss case. **`--seed `** makes runs reproducible (and enables exact-value tests). **FK** columns sampled uniformly from existing parent rows (**empty parent → friendly error**, no recursion v1); **junction/compound-PK** tables seeded with **distinct combinations**, capped + noted (SD1). A **required-column block guard** refuses rather than NULL-violate a `NOT NULL` column it can't fill (e.g. `NOT NULL blob`). Full ambient wiring (completion incl. a new generator-name vocabulary highlighted as `tok_function`, hints, `help seed`, ADR-0042 near-miss matrix, ADR-0027 validity); **no DSL→SQL teaching echo** (seed is a utility command, not a SQL twin). Honours **X5** — `do_seed` reuses insert/update *mechanics as helpers*, not by emitting `Command::Insert`. Implementation phased: (1) core whole-row seed → (2) `set` overrides → (3) column-fill. Deferred (future SD2): recursive auto-seed, NULL injection, multi-locale, user-defined custom generators, full per-column report. **Amendment 1, 2026-06-12** (issues #33/#34): two additive D7 catalogue rules — **year-as-int** (`year`/`*_year`/`published`/`founded` → a bounded `int` year, 1950–2025, or the `dob`-style birth window 1945–2007 for `birth`/`born`/`dob`; fixes nonsense like `9419`; `int`-gated, after the quantity rule so `year_count` stays a count; two new `YearRecent`/`YearBirth` generators, *not* added to the D9 vocabulary) and **conventional choice sets** (`priority`/`prio`, `severity`, `rating`/`stars` → type-gated built-in `PickFrom` value sets reusing the existing generator; `priority` leaves `ENUM_TOKENS`). `status` is **deliberately excluded** (user-confirmed — values too domain-specific; keeps the D12 "don't guess" + advisory); a user `IN`-CHECK still wins. Website `seed` cast re-record tracked on the `website` branch - [ADR-0049 — Input-field readline keymap: Esc-clear + Ctrl-A/E/W/K/U (I1b)](0049-input-field-readline-keymap.md) — **Accepted + implemented 2026-06-12 (issue #29)**, closes Gitea **#29** and the deferred **I1b** readline requirement. **Amends ADR-0046**, which listed "readline shortcuts (I1b)" as out-of-scope — that item is now in scope and decided here; orthogonal to ADR-0003's input-*mode* model and extends the I1a single-line cursor editing already shipped. Binds, in the input field (non-modal, non-nav, both modes): **`Esc`** clears a partly-typed command (empty buffer, cursor→0, scroll→0); **`Ctrl-A`/`Ctrl-E`** alias Home/End (line start/end); **`Ctrl-W`** deletes the previous word (readline-style — eats trailing whitespace then the preceding non-whitespace run, UTF-8-safe on char boundaries, only back to the cursor); **`Ctrl-K`** kills to end of line; **`Ctrl-U`** kills to start. **Esc precedence:** a live Tab-completion memo still wins (Esc undoes the completion first, ADR-0022; Esc clears only when no memo) — Esc-once backs out the completion, Esc-again clears. Forks all user-chosen: **single-Esc-clears** (not double-Esc — discoverable over accident-proof; an unsubmitted draft can be lost, a submitted line is always in history); the **full I1b set** (not just the issue's literal Ctrl-A/E + Esc); a **new ADR** (not an ADR-0046 amendment / no-ADR). Cursor-only keys (Ctrl-A/E) leave history navigation intact like Home/End; buffer-mutating keys (Esc-clear, Ctrl-W/K/U) end it like Backspace. Helpers `clear_input`/`delete_prev_word`/`kill_to_end`/`kill_to_start` in `src/app.rs`; **22 new Tier-1 tests, 2458 pass / 0 fail / 0 skip (1 ignored), clippy clean**. OOS: on-screen keybinding hints (issue #27 owns surfacing per-focus keybindings in the bottom status line — this ADR makes the keys *work*, #27 makes them *discoverable*); demo-mode badges for the new chords (ADR-0047 follow-up — Esc already badges `[ESC]`, the glyph-less Ctrl-chords are flagged but not added); multi-line input (I1); word-wise cursor motion (Alt-B/F) / transpose / yank - [ADR-0050 — Incidental-DDL confirmations omit relationship info (structure-only)](0050-incidental-ddl-confirmations-omit-relationships.md) — **Accepted + implemented 2026-06-12 (issue #28)**, closes Gitea **#28**. **Supersedes** the incidental-DDL clause of **ADR-0044 §1** and the relationship-block half of **ADR-0016 §5**. Incidental-DDL confirmation echoes (`create table`, `add`/`drop`/`rename`/`change column`, `add`/`drop index`) now render **structure only** — header + column box + `Indexes:` + constraints — with **no `References:` / `Referenced by:` block** (neither prose nor diagram), even when the table carries relationships the user did not touch. Rationale (owner): a confirmation echo reports the change just made, not untouched relationships; ADR-0044's terse prose was the lesser of "prose vs diagram", but the right answer for these surfaces is **neither**. **Relationship-subject surfaces are unchanged** — `show table`, `add`/`drop relationship`, `show relationship` still render ADR-0044 diagrams; relationships appear only when the user asks for (`show table`) or acts on (`add`/`drop relationship`) one, and are one `show table ` away — **no information lost**. Forks both user-chosen: **scope = all incidental DDL** (not just `add column` — the rationale is uniform, the mental model clean, and it's the simpler edit) and **delete the prose renderer** (not retain it dormant — no dead code). **Mechanism:** the `handle_dsl_success` `matches!` routing is unchanged (relationship-subject → diagrams; else → `render_structure`); the change is one line inside `render_structure` (`output_render.rs` — drop the relationship-block call) since all its callers are incidental DDL, plus deletion of the orphaned `relationship_prose_lines` + `cols_disp` helpers. The prose format survives in ADR-0016 §5 + git history for a future OOS-7 always-prose setting. **Tests:** the prose-presence unit test + its snapshot removed; a new unit test asserts `render_structure` on a description carrying **both** inbound and outbound relationships emits the box but no prose; the misnamed `add_relationship_flow_shows_inbound_section_on_parent` integration test (which sent an `AddColumn`) inverted + renamed to assert the add-column echo omits the prose; the diagram tests (`show table`, `add relationship`) unaffected. **2458 pass / 0 fail / 0 skip (1 ignored), clippy clean**. `requirements.md` unaffected (ADR-tracked refinement of a decided area, like ADR-0044 itself) diff --git a/docs/handoff/20260615-handoff-72.md b/docs/handoff/20260615-handoff-72.md new file mode 100644 index 0000000..c90b5be --- /dev/null +++ b/docs/handoff/20260615-handoff-72.md @@ -0,0 +1,113 @@ +# Session handoff — 2026-06-15 (72) + +Short, focused handover. Continues from handoff-71, which asked the next +session to run a **systematic semantic verification pass over every +`hint` block** (handoff-70 shipped H2 / ADR-0053, but a user smoke-test +found a wrong hint and implicated the whole corpus). **That pass is now +done.** Four content errors fixed, a durable parse-guard added, two stale +docs corrected. Commit `5a37437`. + +## §1. State + +**Branch:** `main`, clean, all committed (local; **push pending** — your +step). **2500 pass / 0 fail / 1 ignored** (the long-standing `friendly` +doctest), **clippy clean** (nursery, all targets). The +1 vs handoff-71's +2499 is the new guard test. Open Gitea issues unchanged: **#35–#38**. + +## §2. The verification pass (commit `5a37437`) + +Method: cross-checked every `hint.cmd.*` example against its +`parse.usage.*` template, ground-truthed every concept claim against the +authoritative ADR **and a named existing test** (not grep+extrapolation — +the trap handoff-71 §3 warned about), and parse-validated all 49 command +examples via a new guard. + +### Four content errors fixed (`src/friendly/strings/en-US.yaml`) + +| Block | Bug | Fix | +|---|---|---| +| `cmd.create_table` | Example `with pk id(serial), name(text), email(text)` declares a **3-column compound PK**, not a PK + regular columns. Every `with pk` column is a key member — confirmed by the grammar test comment *"Every `create table` column is a primary-key column"* (`ddl.rs`), ADR-0005. | Single-column PK + `add column` for the rest; `what`/`concept` aligned. | +| `cmd.save` | `save as my-shop` **does not parse** — `build_save` yields `AppCommand::SaveAs` with **no inline name**; `save as` opens a path-entry modal (`iteration4b` tests). | Example → `save as`; `what` de-implied; added an accurate temp-vs-named-auto-save `concept`. | +| `cmd.import` | Target `shop-copy` **does not parse** — the `as ` slot is an `IdentSource::NewName` ident that tokenises only up to the hyphen. (The zip path is a BarePath and *does* accept hyphens, hence `export my-shop.zip` is fine.) | → `shop_copy`. | +| `err.foreign_key.child_side.concept` | Offered `on delete set null/cascade` as the remedy — but `error_hint_class` maps child_side to **insert/update** violations; `on delete` governs the **parent** direction. The tier-1 hint (line 64) correctly omits it. | Corrected: parent must exist first; clarified `on delete` is the *other* direction. | + +### Durable guard added + +`every_cmd_hint_example_parses_in_its_mode` (`src/dsl/grammar/mod.rs`, +in the `hint_key_tests` module). **Catalog-driven** — it iterates +`catalog().keys()` for `hint.cmd.*.example` rather than the REGISTRY, so +an orphaned/mis-keyed block can't slip past; floor-asserts ≥49 examples. +Each parses in its taught mode (advanced for the SQL surface, simple +otherwise). It caught the `save` and `import` errors **test-first** (red +before the YAML fix). Registered the new `hint.cmd.save.concept` key in +`keys.rs` (the `keys_validate_against_catalog` test requires every catalog +key be declared). + +### Verified correct (not changed) + +All other `cmd`/`err` blocks. Notably the guard-*concept* claims were each +confirmed against a named runtime test, not assumed: +`drop_column_refuses_primary_key` / `…_column_in_a_relationship`, +`drop_table_with_inbound_relationship_errors`, +`add_not_null_column_without_default_to_populated_table_is_refused`. The +corrected `create_table` story stays coherent with the `Customers`- +referencing examples (id serial PK → `add column` name/email → `insert` +skips the auto id). + +## §3. Docs corrected (same commit) + +Discovered while verifying `create_m2n` (which **is** implemented — +`db.rs::do_create_m2n_relationship` + `tests/it/m2n.rs`): + +- **CLAUDE.md** carried two **stale "deferred" claims**, both already + implemented. Removed/updated: (a) the at-a-glance project-format line + said export/import (Iter 5) + `--resume`/input-history/migration (Iter + 6) were "pending" — all `[x]` in `requirements.md` (ADR-0015); (b) the + "Things deliberately deferred" list still had the **m:n convenience + (C4)** bullet and the same project-storage bullet. `requirements.md` + was already correct (C4 done 2026-06-10, ADR-0045), so only a + verification-pass note was appended to its **H2** entry. + +## §4. Scope note — what the guard does *not* do + +The bug class here is **semantic** (an example that parses and runs but +misrepresents the prose — e.g. `create_table`). The guard enforces only +the **syntactic floor**: examples parse in their mode. It backstops +future typos/clause-drift but cannot police meaning. Semantic correctness +of the current corpus rests on this session's review (recorded in the +commit + requirements.md H2). A stronger-but-brittler option was offered +to the user and **not built pending their call**: per-form assertions +that each example resolves to the *expected command shape* (e.g. +create_table → single-column PK). `hint.err.*` examples are fix-recipe +prose, not runnable, so they're verified by review only — inherent. + +## §5. Next session — start here + +The hint corpus is now trustworthy. Open roadmap (verify against the CI +merge first, per handoff-70 §5): + +1. **Push** (your step) — this commit + the still-unpushed backlog from + handoffs 70/71 (the CI merge + all of H2). +2. **#35 (cargo fmt gate)** — the natural pairing with the merged CI; the + user wanted it done once, before first publication. The tree is **not** + fmt-clean (~1800 pre-existing diffs). +3. Other `requirements.md` open items: **TT4** PTY tier-4 (unwired), + **I1** multi-line input, **I5/B3** in-flight cancellation, **V4** + session journal (own ADR), **TU1** tutorial system (own ADR). +4. Hint follow-ups if wanted: **#37** clause-concept hints, **#38** + diagnostic route + `diagnostic.*` blocks, **#36** `help` advanced-SQL. + +## §6. How to take over + +1. Read handoffs 70 → 71 → 72, `CLAUDE.md`, `docs/requirements.md`. +2. Confirm green: `cargo test` (**2500 / 1 ignored**) + `cargo clippy + --all-targets` (clean). +3. For anything in the `hint` area, read **ADR-0053** first. For the + corpus, `src/friendly/strings/en-US.yaml` (`hint.cmd.*` / `hint.err.*`) + is the content; the guard in `src/dsl/grammar/mod.rs` is the regression + net. +4. Workflow unchanged: phased, test-first, `/runda` + DA before commits, + ADR amendment + README index-upkeep for decided-area changes, confirm + commit messages with the user. +5. Consider a `cargo sweep` at this milestone (`target/` grows; see + CLAUDE.md "Build hygiene"). diff --git a/docs/requirements.md b/docs/requirements.md index a30fc57..f578a1c 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -820,7 +820,12 @@ since ADR-0027.) (`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).)* + clause-concept hints (#37). **Content verified 2026-06-15 (handoff-71):** + a semantic pass over every `hint.cmd.*`/`hint.err.*` block fixed four + errors — `create_table` (compound-PK misread), `save` (no inline name), + `import` (hyphen-rejecting target), and `foreign_key.child_side` (wrong + `on delete` remedy) — and added a catalogue-driven guard test that parses + every command example in its taught mode.)* - [x] **H3** `help` provides general reference and per-command help. *(Done 2026-06-07: the **general reference** is `help` (no arg) — diff --git a/src/app.rs b/src/app.rs index 5750535..1db2815 100644 --- a/src/app.rs +++ b/src/app.rs @@ -520,10 +520,11 @@ const HISTORY_CAPACITY: usize = 1000; /// produces a glyph of its own (and so needs no badge) — ADR-0047 D2. /// /// The set is exactly the *otherwise-invisible* keys: motion, editing, -/// submission, and the `Ctrl-O` navigation toggle. Plain character keys -/// already appear on the input line, and `Ctrl-C` (quit) / `Ctrl+]` -/// (the caption toggle) are deliberately excluded. Pure and total, so -/// it is exhaustively unit-testable without a running app. +/// submission, the `Ctrl-O` navigation toggle, and the `Ctrl-G` F1-alias +/// (ADR-0047 amendment). Plain character keys already appear on the input +/// line, and `Ctrl-C` (quit) / `Ctrl+]` (the caption toggle) are +/// deliberately excluded. Pure and total, so it is exhaustively +/// unit-testable without a running app. pub const fn demo_badge_label(key: &KeyEvent) -> Option<&'static str> { match (key.code, key.modifiers) { (KeyCode::Tab, _) => Some("[TAB]"), @@ -541,8 +542,12 @@ pub const fn demo_badge_label(key: &KeyEvent) -> Option<&'static str> { (KeyCode::PageDown, _) => Some("[PGDN]"), (KeyCode::Backspace, _) => Some("[BKSP]"), (KeyCode::Delete, _) => Some("[DEL]"), - // The only badged control chord: the ADR-0046 navigation toggle. + // The ADR-0046 navigation toggle. (KeyCode::Char('o'), m) if m.contains(KeyModifiers::CONTROL) => Some("[CTRL-O]"), + // ADR-0047 amendment: the Ctrl-G F1-alias badges AS [F1] so a + // cast recorded by a tool that can't send F1 looks identical to a + // real F1 press. (Only consulted in demo mode — the caller gates.) + (KeyCode::Char('g'), m) if m.contains(KeyModifiers::CONTROL) => Some("[F1]"), _ => None, } } @@ -1223,7 +1228,18 @@ impl App { // the memo-clearing completion match below. Non-empty input → // a hint for the command being typed; empty input → expand on // the most recent error (or a getting-started pointer). - if key.code == KeyCode::F(1) { + // + // ADR-0047 amendment: in demo mode, Ctrl-G is an alias for F1. + // The cast recorder (autocast) can't emit F1 (an escape + // sequence) but can send the single control byte Ctrl-G; it + // badges AS [F1] (see `demo_badge_label`) so the cast is visually + // identical to a real F1 press. Demo-gated, so the shipped keymap + // stays F1-only. + let hint_key = key.code == KeyCode::F(1) + || (self.demo_mode + && (key.code, key.modifiers) + == (KeyCode::Char('g'), KeyModifiers::CONTROL)); + if hint_key { if self.input.trim().is_empty() { self.note_hint_for_recent_error(); } else { @@ -3459,6 +3475,13 @@ mod tests { demo_badge_label(&ke(KeyCode::Char('o'), KeyModifiers::CONTROL)), Some("[CTRL-O]") ); + // ADR-0047 amendment: the Ctrl-G F1-alias badges AS [F1], so a + // cast recorded with autocast (which can't send F1) is visually + // identical to a real F1 press. + assert_eq!( + demo_badge_label(&ke(KeyCode::Char('g'), KeyModifiers::CONTROL)), + Some("[F1]") + ); } #[test] @@ -5756,6 +5779,56 @@ mod tests { assert_eq!(app.input, input, "F1 must not change the buffer"); } + // ── ADR-0047 amendment: Ctrl-G is a demo-mode alias for F1 ────── + // The cast recorder (autocast) cannot emit F1 — an escape sequence — + // but Ctrl-G is a single legacy control byte it can send. Demo-gated + // so the real keymap stays F1-only, and badged as [F1] (see + // `demo_badge_label`) so a recorded cast looks identical to a genuine + // F1 press. + + fn ctrl_g() -> AppEvent { + key_mod(KeyCode::Char('g'), KeyModifiers::CONTROL) + } + + #[test] + fn ctrl_g_in_demo_mode_aliases_f1_on_input() { + let mut app = App::new(); + app.demo_mode = true; + type_str(&mut app, "insert into T"); + let input = app.input.clone(); + let before = app.output.len(); + app.update(ctrl_g()); + assert_eq!(app.input, input, "Ctrl-G must not change the buffer (no `g` typed)"); + assert!(app.output.len() > before, "Ctrl-G must emit the same hint F1 does"); + } + + #[test] + fn ctrl_g_in_demo_mode_aliases_f1_on_empty_input() { + let mut app = App::new(); + app.demo_mode = true; + let before = app.output.len(); + app.update(ctrl_g()); + assert!( + app.output.len() > before, + "Ctrl-G on empty input emits the getting-started hint" + ); + assert!(output_contains(&app, "press F1")); + } + + #[test] + fn ctrl_g_outside_demo_mode_is_inert() { + // Not in demo mode: Ctrl-G is neither the hint alias nor a typed + // glyph (the `Char(c)` insert arm excludes CONTROL), so it falls + // through to the inert catch-all — no `g`, no hint. + let mut app = App::new(); + type_str(&mut app, "insert"); + let input = app.input.clone(); + let before = app.output.len(); + app.update(ctrl_g()); + assert_eq!(app.input, input, "Ctrl-G must not insert a `g`"); + assert_eq!(app.output.len(), before, "Ctrl-G does nothing when demo mode is off"); + } + #[test] fn dsl_failure_sets_hint_class_and_a_later_dsl_command_clears_it() { let mut app = App::new(); diff --git a/src/dsl/grammar/mod.rs b/src/dsl/grammar/mod.rs index 3842073..337b039 100644 --- a/src/dsl/grammar/mod.rs +++ b/src/dsl/grammar/mod.rs @@ -1012,6 +1012,75 @@ mod hint_key_tests { assert!(cat.get(&key).is_some(), "missing tier-3 error block `{key}`"); } } + + /// Semantic-verification guard (handoff-71): every `hint.cmd.
` + /// **example** must parse in the mode the form is taught for. This + /// backstops the bug class found in the H2 corpus pass — an example + /// that drifts out of the real grammar (a typo, a removed clause, or + /// an argument the command never accepted, e.g. an inline name on + /// `save as` which opens a modal instead). It cannot police the + /// *semantics* of an example that happens to parse (that is the + /// manual pass), but it locks the syntactic floor so future edits + /// can't ship an unparseable teaching line. + /// + /// The mode per form mirrors `hint_key_for_input_in_mode`: the + /// advanced-SQL forms are taught in advanced mode; everything else + /// (DSL + app commands) in simple mode. + #[test] + fn every_cmd_hint_example_parses_in_its_mode() { + use crate::dsl::parser::parse_command_in_mode; + use crate::mode::Mode; + + // Advanced-mode forms — the SQL surface (ADR-0030–0039). Every + // other form (DSL + app commands) is taught in simple mode. This + // mirrors the mode split `hint_key_for_input_in_mode` resolves. + const ADVANCED: &[&str] = &[ + "sql_create_table", + "sql_alter_table", + "sql_create_index", + "sql_drop_index", + "sql_drop_table", + "sql_insert", + "sql_update", + "sql_delete", + "select", + "with", + "explain_sql", + ]; + + // Iterate the *catalog* (the corpus is the source of truth), not the + // REGISTRY: this reaches every `hint.cmd.` block including any + // not owned by a command node, so an orphaned or mis-keyed example + // can't slip past the guard. + let cat = crate::friendly::catalog(); + let mut checked = 0usize; + for key in cat.keys() { + let Some(id) = key + .strip_prefix("hint.cmd.") + .and_then(|rest| rest.strip_suffix(".example")) + else { + continue; + }; + let example = cat.get(key).expect("key came from the catalog"); + let mode = if ADVANCED.contains(&id) { + Mode::Advanced + } else { + Mode::Simple + }; + assert!( + parse_command_in_mode(example, mode).is_ok(), + "hint.cmd.{id}.example does not parse in {mode:?} mode: {example:?}", + ); + checked += 1; + } + // Floor guard: the corpus had 49 command forms at the time of + // writing (ADR-0053). If this drops, a block (and its example + // coverage) silently vanished. + assert!( + checked >= 49, + "expected at least 49 hint.cmd.* examples, checked {checked}", + ); + } } #[cfg(test)] diff --git a/src/friendly/keys.rs b/src/friendly/keys.rs index 08a5bc5..341ee54 100644 --- a/src/friendly/keys.rs +++ b/src/friendly/keys.rs @@ -277,6 +277,7 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[ ("hint.cmd.rebuild.concept", &[]), ("hint.cmd.save.what", &[]), ("hint.cmd.save.example", &[]), + ("hint.cmd.save.concept", &[]), ("hint.cmd.new.what", &[]), ("hint.cmd.new.example", &[]), ("hint.cmd.load.what", &[]), diff --git a/src/friendly/strings/en-US.yaml b/src/friendly/strings/en-US.yaml index 87b820e..50cc352 100644 --- a/src/friendly/strings/en-US.yaml +++ b/src/friendly/strings/en-US.yaml @@ -430,8 +430,9 @@ hint: example: "rebuild" concept: "The text files (project.yaml + the data folder) are the source of truth; the database is derived and can always be rebuilt from them." save: - what: "Save the current project under a name; `save as` copies it to a new one." - example: "save as my-shop" + what: "Save the current project; `save as` copies it to a new name or location." + example: "save as" + concept: "On a temporary project, `save` opens a prompt to give it a permanent name; a named project auto-saves as you work, so `save` on one is already done. `save as` always prompts for a new name or path — use it to copy a project." new: what: "Close the current project and start a fresh temporary one." example: "new" @@ -444,7 +445,7 @@ hint: concept: "The zip carries the schema and data as text, so anyone can rebuild the very same database from it." import: what: "Unpack a project zip into a new project and switch to it." - example: "import my-shop.zip as shop-copy" + example: "import my-shop.zip as shop_copy" mode: what: "Switch between simple mode (the guided teaching commands) and advanced mode (raw SQL)." example: "mode advanced" @@ -465,9 +466,9 @@ hint: example: "copy last" # DDL — schema-shaping commands (Phase C batch 2). create_table: - what: "Create a new table — its columns, their types, and a primary key." - example: "create table Customers with pk id(serial), name(text), email(text)" - concept: "A table is a set of rows that share the same columns. The primary key uniquely identifies each row; a `serial` key numbers the rows for you." + what: "Create a new table and declare its primary key." + example: "create table Customers with pk id(serial)" + concept: "A table is a set of rows sharing the same columns. `with pk` declares the primary key — one column, or several for a compound key; add the other columns afterwards with `add column`. A `serial` key numbers the rows for you." create_m2n: what: "Create a junction table linking two tables many-to-many." example: "create m:n relationship from Students to Courses" @@ -606,7 +607,7 @@ hint: child_side: what: "The value you gave for the child column doesn't match any parent row, so the foreign key has nothing to point at." example: "First insert the parent (insert into Customers …), then the child that references it." - concept: "A foreign key is a promise that every child points at a real parent, so the parent must exist first. To allow orphans on delete instead, set the relationship's `on delete` to `set null` or `cascade`." + concept: "A foreign key is a promise that every child points at a real parent, so the parent must exist before a child can reference it. (`on delete` actions like `cascade` or `set null` govern the other direction — what happens to children when their parent is removed — not this one.)" 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)."