Merge branch 'main' into website

This commit is contained in:
claude@clouddev1
2026-06-12 13:22:52 +00:00
38 changed files with 6222 additions and 142 deletions
+677
View File
@@ -0,0 +1,677 @@
# ADR-0048: `seed` — fake-data generation command (SD1, opens SD2)
## Status
**Accepted (2026-06-11); Phase 1 + Phase 2 implemented (2026-06-11).** Design
settled with the user across an extended fork dialogue (every decision
below was escalated and user-chosen), then hardened by a pre-build
`/runda` Devil's-Advocate pass that found six blockers — undo
integration (D15), replay semantics (D16), `set` value quoting (D2),
CHECK-constraint handling (D17), a phase-ordering bug in the advisory
(D13), and auto-show flooding (D18) — plus refinements (state-relative
reproducibility, compound-FK tuple sampling, column-fill constraint
rules, the `fake` dependency scan), all folded in.
**Phase 1 shipped** test-first across commits `202e25a` (generation
library + `fake` dependency) → `f1e9484` (command skeleton) →
`73493fa` (FK sampling) → `9c13501` (uniqueness / junction / IN-CHECK)
`0b3ab3c` (`SeedResult` / preview / advisory / count cap) →
`e6ff63d` (single-transaction O(N) path) → `fbd219b` (`--seed` flag,
ambient wiring, and a whole-implementation `/runda` pass). The
post-implementation `/runda` found eight gaps — FK-sampling
determinism (now `ORDER BY`), shortid reproducibility (now from the
seeded RNG, so **D4 holds with no exceptions**), and six untested
ADR decisions (D5/D15/D16/D17 + atomicity + zero-count), all closed.
**2358 tests pass / 0 fail / 0 skip; clippy clean.**
**Implemented in Phase 1:** the whole-row `seed <table> [count]
[--seed <n>]` form and every D1D18 decision *except* the two
Phase-2 surfaces.
**Phase 2 implemented (2026-06-11):** both remaining surfaces — the
**`set` override clause** (D2: fixed value / pick-list / named
generator / range, quoted literals, type-aware) and the
**`<table>.<column>` column-fill** form (D1 form 2: an UPDATE over
existing rows, refusing PK/autogen targets, empty-table no-op, one undo
step). The named-generator vocabulary (D9) lives in `src/seed`
(`KNOWN_GENERATORS` / `generator_for_name`); a new range `Generator`
(`src/seed/generators.rs`) backs `between`; the override clause is
folded from the flat matched path (`build_seed_overrides`,
`src/dsl/grammar/data.rs`) and applied to the per-column plan
(`apply_seed_overrides`, `src/db.rs`), with column-fill in
`do_seed_column_fill`. Full ambient wiring: completion (the generator
vocabulary after `as`, the `set`/`.col` column slots), highlighting
(`HighlightClass::Function``tok_function`, the generator slot), the
validity indicator (`IdentSource::Generators` — an unknown name flagged
`[ERR]`), help, and parse-error pedagogy rows. The D13 advisory now
carries its Phase-2/3 wording (points at `set` and the column-fill
repair). A post-implementation `/runda` pass then added one
user-chosen refinement: a **bounded override on a UNIQUE column** (a
fixed value / too-short pick-list) is now a **friendly error** rather
than a silent uniqueness cap (see D2). **2400 tests pass / 0 fail / 0
skip; clippy clean.** Two
implementation refinements vs. this ADR's wording, both met the
user-facing contract: dates in the range form are **quoted** (the D2
amendment, above — no date-literal token exists); and the `set` value
slots reuse `update`'s typed `current_column_value` (no spurious
column-ref match) rather than the raw expression operand.
Further SD2 increments (custom user generators, NULL injection,
multi-locale, recursive parent auto-seed) remain out of scope (see Out
of scope).
Closes `requirements.md` **SD1** and delivers the core of **SD2**
(per-type generators, determinism, the `fake`-backed catalogue). It
also closes one of the two remaining gaps in **A1** ("all canonical
app-level commands") — `seed`; the other, `hint` (**H2**), is
separate.
Builds on: ADR-0014 (data operations, the `Value`/`Bound` value model,
the auto-show pattern, FK-error enrichment), ADR-0005/0011 (the type
vocabulary and `Type::fk_target_type()`), ADR-0012/0013 (the column /
relationship metadata tables, the rebuild-table primitive — *read* by
seed for schema introspection), ADR-0024 (the unified grammar tree /
`CommandNode` registration that gives completion, hints, help-id,
usage-id for free), ADR-0022 (ambient typing assistance — the
`KNOWN_SQL_FUNCTIONS` curated-vocabulary pattern that the
generator-name list mirrors), ADR-0026 (the `in (...)` / `between ...
and ...` expression grammar the override clause reuses), ADR-0027 (the
validity-indicator diagnostics model), and ADR-0038 (the
`OutputStyleClass::Hint` styled output used for the post-seed
advisory). Honours ADR-0003 (both modes, no sigil), ADR-0009 (DSL
conventions — keyword grammar, `--` flags for opt-in choices, one
sigil only), ADR-0002 (no engine name in user-facing strings), and
ADR-0015 (per-command write-through persistence).
## Context
`seed <table> [count]` is the last unbuilt **data-authoring** command
in the requirements. The pedagogical value is high: a learner who has
just modelled a schema wants rows to query against *now*, without
hand-typing dozens of `insert`s. A teacher wants a one-liner that
fills a demo database with believable data. SD1 commits to "plausible
fake data; junction tables seeded with valid foreign-key references
drawn from existing parent rows." SD2 deferred the *how* — "per-type
generators, locale, determinism, override hooks" — explicitly pending
this ADR.
The design conversation widened the scope deliberately, with the user
confirming each step:
- **Realism matters more than minimalism** for a teaching tool. Random
`text_a3f9` values teach nothing; `Alice Martinez` /
`alice.m@example.com` make queries feel real. → adopt a faker
library and make generation **name-aware**.
- **The column *name* is the strongest signal** for what a value should
look like, but it is **ambiguous** without the **table** for the
`name`/`title` family (`products.name``users.name`).
- **Heuristics will miss**, so a **manual override** surface is
required, not optional — this is SD2's "override hooks", brought
forward.
- **Identifiers and enums** are special: `id`-ish columns want
uniqueness; `status`-ish columns have no sensible generic value and
should be *flagged*, not guessed.
The novel work is the **generation layer**. Everything downstream —
type validation, autogen autofill (`serial`/`shortid`), FK
enforcement, per-command persistence, the auto-show outcome — is
reused from the existing insert/update machinery as **shared helper
functions**, per the X5 architecture preference (unique commands, with
mechanics shared as library functions — *not* by emitting
`Command::Insert` to borrow `do_insert`).
## Decision
Add a dedicated **`seed`** command (its own AST variant and its own
`do_seed` worker executor) available in **both modes**, with the
surface and behaviour below. Generation is realistic, name- and
table-aware, type-gated, with a manual override clause and a
reproducibility flag.
**Command classification (important, set by the replay decision
D16).** Although `requirements.md` A1 lists `seed` among the
"app-level commands" (meaning: part of the canonical command surface,
no sigil, both modes), `seed` is architecturally a **data-authoring
command** — a sibling of `insert`/`update`/`delete`, **not** an
app-lifecycle `AppCommand`. It is therefore **not** added to
`is_app_lifecycle_entry_word` / completion's
`empty_input_offers_app_command_entry_keywords` (those mirror the
`AppCommand` set and must match — `seed` belongs in neither): `replay`
re-runs it as a data write (D16).
### D1 — Command surface (fork, user-chosen: "whole-row + column-fill")
Two forms:
1. **Whole-row generation**`seed <table> [count]`
Generates `count` new rows (an INSERT path). `count` **defaults to
20** (D6) when omitted. Every user-fillable column is filled per the
generation rules (D7D12); `serial`/`shortid` autogen columns are
left to the existing autofill helpers.
2. **Column-fill on existing rows**`seed <table>.<column>`
Fills `<column>` across the table's **existing** rows (an UPDATE
path) — the natural follow-up to `add column`. Combined with the
`set` clause (D2) this is also the precise repair for a single
mis-guessed column: `seed users.work_addr set work_addr as email`.
Column-fill **refuses** PK columns and autogen (`serial`/`shortid`)
columns (a friendly error — you don't "fill" an identity column),
and **respects** the same UNIQUE / FK / required rules as whole-row
generation (a UNIQUE target gets collision-free values; an FK
target samples from the parent, D14). On an **empty** table it is a
friendly no-op ("no rows to fill").
**Zero / over-cap counts.** `seed <table> 0` is a friendly no-op;
`count` over the maximum (D6) is a friendly error.
The column-restricted-*insert* form (`seed t (a, b)` — new rows, only
some columns filled) was considered and **rejected** as marginal and
constraint-fragile (see Alternatives).
**Required-column block guard (user requirement).** If seed cannot
produce a value for a `NOT NULL` column — the only real case is a
`NOT NULL blob` column, which has no DSL value path — it **refuses the
whole operation with a friendly error** naming the column, rather than
attempting a NULL insert that would violate the constraint. The check
is a pre-flight over the resolved per-column plan, before any write.
### D2 — Manual override: the `set` clause (fork, user-chosen: "value + list + generator + range")
An optional, comma-separated `set` clause overrides generation per
column. Four forms, all reusing existing grammar vocabulary so there
is nothing new to learn:
| Form | Example | Meaning |
|---|---|---|
| Fixed value | `set status = 'pending'` | every row gets the constant |
| Pick-from-list | `set role in ('admin', 'editor', 'viewer')` | uniform random choice from the list |
| Explicit generator | `set work_addr as email` | force a named generator (D9) |
| Range | `set price between 10 and 100` | uniform in range; **also dates**`set signup between '2023-01-01' and '2024-12-31'` |
Multiple clauses combine: `seed users 20 set role in ('admin',
'user'), status = 'active', signup between '2023-01-01' and
'2024-12-31'`.
**Override × UNIQUE capacity (post-implementation `/runda`, user-chosen:
"friendly error").** A *bounded* override — a fixed value, or a
pick-list — on a **single-column-UNIQUE** target (a `UNIQUE` column or a
single-column PK) that offers fewer **distinct** values than the row
count cannot fill the run; rather than let the D10 uniqueness machinery
silently cap it (e.g. `seed users 100 set email = 'x'` → 1 row), seed
**refuses up front** with a friendly error pointing at the fixes (use a
generator, or a longer list). Generators and ranges are treated as
effectively unbounded sources — if one genuinely exhausts, the D14
distinct-combination cap still applies. Compound uniqueness is exempt
(the *other* key columns can still vary).
**Quoting (fork, user-chosen: "quoted, grammar-consistent").** Text
values and list items are **quoted string literals** (`'admin'`),
exactly as everywhere else in the DSL — only **numbers** stay
unquoted. **Amendment (2026-06-11, Phase 2 build):** the original
wording said "numbers *and dates* stay unquoted", but this DSL has
**no date-literal token**`Value` is `Number`/`Text` only, and a
date is a **quoted string** validated by `bind_date` (`'2023-01-01'`)
everywhere else (insert / update / `where`). An unquoted `2023-01-01`
lexes as `2023`,`-`,`01`,… and cannot parse. So **dates in the range
form are quoted** (`between '2023-01-01' and '2024-12-31'`) — which is
in fact *more* faithful to this decision's own "quoted,
grammar-consistent" principle. Numbers remain unquoted (`NumberLit`).
This reuses the ADR-0026 expression grammar **unchanged**:
the DA pass confirmed that the `in (...)` form's operands are typed
value slots, so a *bare* `admin` would parse as a **column reference**
(→ "unknown column"), not a string. Quoting is therefore not a style
preference but a correctness requirement of grammar reuse. The range
form is **type-aware**: numeric bounds for numeric columns, date
bounds for date/datetime columns; a type-incompatible bound is a
friendly error. `=`, `in (...)`, and `between ... and ...` are the
ADR-0026 expression operators; `set` is the ADR-0014 UPDATE keyword;
`as` is borrowed from the SQL alias slot. The `as <generator>` operand
is a bare name from the curated generator vocabulary (D9), not a
value. The override takes precedence over every heuristic.
### D3 — Generation library: `fake` crate + hand-rolled gaps (fork, user-chosen: "name-aware + realistic")
Add the **`fake`** crate (v5.x at time of writing; English locale for
v1 per X2) for realistic values: names, emails, usernames, addresses,
companies, phone numbers, lorem text, dates. Generation is driven by a
per-column **generator** chosen by the heuristics (D7) or the override
(D2), falling back to **type-based** generation (D8).
**Implementation-time verifications (resolved 2026-06-11 when the
dependency was added):**
- **`rand` de-duplication — clean.** `fake` 5.1.0 depends on
`rand = "0.10"`, the **same major** as the project's `rand 0.10.1`,
so `cargo tree -e normal` resolves a **single** `rand 0.10.1` (no
runtime duplication; the `rand 0.8.6` visible to `cargo tree -i
rand` is only `fake`'s own dev-dependency, never compiled for us).
Consequence for D4: one seeded `rand 0.10` `StdRng` can drive
**both** `fake`'s `fake_with_rng` and the hand-rolled generators —
determinism is single-RNG, single-version, and shares `shortid.rs`'s
`rand` version.
- **`fake` module inventory / features — confirmed.** Default features
(`["either"]`) cover the core string fakers used here
(Name/Internet/Address/Company/Lorem/PhoneNumber); `fake`'s `chrono`
feature is **deliberately omitted** (dates generated in-house for
D8's bounded windows). No commerce/product module exists → `product`
is hand-rolled (D9). (The exact faker call sites are pinned when the
generation library is built.)
- **Security (new-dependency posture) — clean.** The `fake` tree (296
packages total) scanned clean by **all three** mandated scanners:
`osv-scanner` (no issues), `grype` (no vulnerabilities), `trivy fs
--scanners vuln` (0). No findings to document or accept.
### D4 — Determinism: `--seed <n>` (fork, user-chosen: "optional flag")
Generation is **random by default**. The optional `--seed <n>` flag
makes a run **reproducible**: **same database state + same `--seed`
identical data**. The "database state" qualifier matters (DA
refinement) — FK sampling (D14), identifier sequencing (D10), and
UNIQUE collision-avoidance all *read existing rows*, so reproducibility
is relative to the data already present, not absolute. Value: teachers
hand out one dataset; demos are stable; and the feature's own tests
can assert **exact** output (against a known starting state).
Implemented with a seedable RNG threaded through every generator (no
`thread_rng` on the seeded path). `--` flag per ADR-0009 (opt-in
choice). Naming note: the flag `--seed` and the command `seed` share a
word but never collide grammatically (`seed users 20 --seed 42` parses
unambiguously). This flag is also the determinism lever for **replay**
(D16): a recorded `seed … --seed N` line reproduces on replay; a bare
`seed …` line regenerates fresh data.
### D5 — Both modes (A1)
`seed` is a canonical app-level command, available in **simple and
advanced** mode, no sigil — like `save`/`load`/`export`/`replay`.
### D6 — Default count: 20; bounded maximum
Omitted `count`**20** rows: enough to make `where`, `group by`,
`order by`, and `limit` meaningful without flooding the output pane.
A **maximum** is enforced (proposed 10 000) to prevent a typo
(`seed t 1000000`) from hanging the app or bloating the project; over
the cap → friendly error stating the limit.
### D7 — Name-aware heuristics, type-gated (the catalogue)
A column's **name** selects a generator, but a name rule only fires
when the column's **type** is compatible (a column named `email` typed
`int` does **not** get a string — it falls through to type-based int).
Matching is **case-insensitive**, **token-based** (split on `_`,
camelCase, kebab), **most-specific-first**, with documented
false-positive guards. The catalogue (representative; full table lives
with the implementation):
| Column name (tokens) | Generator | Type gate |
|---|---|---|
| `first_name`/`fname` · `last_name`/`surname`/`lname` | first / last name | text |
| `name`/`full_name` · `title` | **table-context** name (D11) | text |
| `email`/`*_email` | email | text |
| `username`/`login`/`handle` | username | text |
| `password`/`pwd` | password | text |
| `phone`/`mobile`/`cell`/`tel` | phone number | text |
| `city`/`town` · `country` · `state`/`province` | address parts | text |
| `street`/`address`/`addr` · `zip`/`postcode`/`postal` | address parts | text |
| `company`/`employer`/`org` · `job`/`position`/`profession` | company / job | text |
| `description`/`bio`/`notes`/`summary`/`comment` | sentence / paragraph | text |
| `url`/`website`/`homepage` · `color`/`colour` | URL / hex colour | text |
| `price`/`amount`/`cost`/`salary`/`balance`/`total` | currency-range number | numeric |
| `age` · `quantity`/`qty`/`stock`/`count` | 1880 · small int | numeric |
| `date`/`*_date` | date, recent ~3 yr window | date |
| `dob`/`birthday` | date, adult window (1880 yr ago) | date |
| `timestamp`/`datetime` · `created_at`/`updated_at`/`*_at` | datetime, recent window (`updated_at``created_at`) | datetime |
| `is_*`/`has_*`/`active`/`enabled` | boolean | bool |
| **identifier family** (D10) | unique sequential | int/text |
| **enum-ish family** (D12) | generic text + flag | (text) |
**False-positive guards (documented):** `username`/`filename`/
`table_name`/`*_name` handled before the bare `name` rule so they do
**not** resolve to person-name; the bare `name`/`title` rule requires a
standalone token or a recognised `*_name` suffix.
### D8 — Type-based fallback
When no name rule matches (or to satisfy a name rule's type gate),
generate by **type**: `text`→realistic words/short phrase, `int`
bounded random, `real`→random double, `decimal`→formatted number,
`bool`→random, `date`/`datetime`→**bounded recent** value (never "any
point in all of history" — per the user's date concern), `serial`/
`shortid`→omitted (autogen helpers fill them), `blob`→unsupported
(nullable→NULL; `NOT NULL`→D1 block guard).
### D9 — Named generators + the `product` generator
The generators addressable via `set ... as <generator>` (D2) and
chosen by D7 form a **curated, named vocabulary**`name`,
`first_name`, `last_name`, `email`, `username`, `phone`, `city`,
`country`, `street`, `zip`, `company`, `job`, `sentence`, `paragraph`,
`url`, `color`, `price`, `age`, `date`, `datetime`, `bool`, `product`,
… — the single source of truth shared by the executor, the completion
source, and the highlighter (mirroring `KNOWN_SQL_FUNCTIONS`,
ADR-0022 Amд6).
**`product`** is **hand-rolled** (the `fake` crate has no
commerce/product module — D3): `{adjective} {material} {noun}` from
three small baked-in word lists (~20 each) → "Sleek Bamboo Keyboard",
"Vintage Leather Backpack". Seedable through the D4 RNG. Always
addressable as `set <col> as product`, and auto-selected by D11 for
the `name`/`title` family in product-ish tables.
### D10 — Identifier family → unique by name (fork, user-chosen: "unique sequential")
A column in the identifier family — `id`, `*_id` **that is not an FK**,
`code`, `sku`, `ref`/`reference`, `number`/`no`, `barcode` — that is
**not** a serial/shortid autogen column and **not** the PK is treated
as an identifier and gets **unique** values: **int → sequential**
(`MAX(col)+1` ascending, reads like real ids, never collides);
**text → unique short code** (generate-with-retry). Precedence:
**FK detection wins** over this rule (an FK `user_id` *should* have
duplicates — many children per parent), so `*_id` only triggers
uniqueness when the column is not a foreign key.
**Constraint-driven uniqueness is independent and mandatory:** any
column with a `UNIQUE` constraint (or a user-fillable single-column
PK) gets guaranteed-unique generation regardless of name — a
correctness requirement, not a heuristic. Generation for such columns
uses retry/sequence to guarantee no collision within the batch and
against existing rows.
### D11 — Table-context disambiguation for `name`/`title` (fork, user-chosen: "table-context-aware")
For the `name`/`title` family **only**, the heuristic also reads the
**table** name token:
- `product`/`item`/`goods`/`merchandise`/`catalog`/`inventory`
`product` generator (D9)
- `company`/`companies`/`vendor`/`supplier`/`manufacturer`/`brand`
company name
- `user`/`customer`/`person`/`people`/`employee`/`member`/`contact`/
`author`/`student` → person name
- unrecognised table → generic word
This resolves the real ambiguity (`products.name` → "Sleek Bamboo
Keyboard"; `users.name` → "Alice Martinez"; `vendors.name` → "Globex
Corp"). It is a deliberately **scoped** use of table context — the only
place the table name influences generation.
### D12 — Enum-ish names → generic + post-seed advisory (fork, user-chosen: "flag enum-ish only")
Enum-ish names — `role`, `status`, `type`, `state`, `kind`,
`category`, `level`, `tier`, `stage`, `priority`, `gender` — have **no
sensible generic generator**, so they are **not guessed**: they fall
through to generic text (they must still be filled — a `NOT NULL`
status cannot be left empty). Seed then emits a **post-seed advisory**
(D13) naming them and pointing at the `set ... in (...)` override.
### D13 — Reporting: post-seed advisory (fork, user-chosen: "flag enum-ish only")
After a successful seed, in addition to the normal auto-show outcome
(row count + the affected rows, per ADR-0014), seed appends a
**`OutputStyleClass::Hint`** advisory **only** when one or more
enum-ish columns (D12) — **or columns guarded by a CHECK that seed
could not derive values from** (D17) — were filled generically.
The wording is **phase-aware** (DA finding: the advisory must not name
features that ship later). In **Phase 1** (no `set` clause yet) it
names the columns and explains they were filled generically. From
**Phase 2/3** it points at the concrete repair:
```
# Phase 1 wording:
✓ Seeded 20 rows into users
status, role were filled with generic text — they look like
fixed value sets you may want to choose deliberately.
# Phase 2/3 wording (set clause + column-fill exist):
✓ Seeded 20 rows into users
status, role filled generically. Fix existing rows with
seed users.status set status in ('active','inactive'),
or pass set … on the next seed.
```
Note the repair for **already-seeded rows** is the **column-fill**
form (`seed users.status set …`), not "re-seed" (which would add more
rows) — DA correction. This is a **result-time** note (cheap, reusing
ADR-0038's hint rendering), not a typing-time warning. The fuller
"per-column report" (every column → its generator) was considered and
**deferred** (see Alternatives / Out of scope).
### D14 — Foreign keys (SD1; fork on empty-parent, user-chosen: "friendly error")
- **Each FK** is filled by sampling **uniformly** from the **existing
rows** of the parent table's referenced column(s). Duplicates are
expected and correct (many children per parent). For a **compound
FK**, the referenced **tuple is sampled jointly** (a whole existing
parent key), never per-column independently — independent sampling
could fabricate a `(a, b)` pair that exists in no parent row and
would fail FK enforcement (DA refinement).
- **Empty parent** → seed **refuses with a friendly error** naming the
parent and the FK column ("seed `users` first — `orders.user_id`
references it"). Safe, predictable, teaches FK dependency order.
Recursive parent auto-seed is **deferred** to a future `--recursive`
opt-in (Out of scope).
- **Junction / compound-PK tables** (SD1's explicit case): sample
**distinct combinations** of the parent PK tuples to satisfy the
compound PK's uniqueness; if `count` exceeds the number of available
distinct combinations, **cap** at the maximum and note it in the
outcome.
- **Self-referential FK** (`manager_id → id`): if nullable, leave NULL
or point at an earlier row in the same batch; if `NOT NULL` on an
otherwise-empty table, friendly error. Documented edge case.
- **Nullable FKs** are **always filled** in v1 (predictable);
occasional-NULL injection is deferred.
### D15 — Undo: one snapshot per seed (DA finding; ADR-0006)
Seed is a mutation, so it must participate in undo. The draft omitted
this; the DA found the codebase already has the right primitive —
`BeginBatch` / `EndBatch` (`db.rs`), used by `replay` so a multi-write
run collapses to **one** boundary snapshot. `do_seed` wraps its
generated writes in `begin_batch` / `end_batch`, so **`seed users 20`
is a single undo step**, not 20 — matching ADR-0006 Amendment 1's
batch model. Column-fill's bulk UPDATE is likewise one step. (`import`
remains the only data-affecting op outside undo, per ADR-0015 §11;
seed is firmly inside it.)
### D16 — Replay: seed re-runs as a data write (fork, user-chosen)
`replay` re-executes a recorded `seed` line as a **data-write
command** — it is **not** in the app-lifecycle skip-set (see Command
classification, above). Consequence, accepted by the user: a **bare**
`seed users 20` regenerates **fresh, divergent** data on each replay;
a `seed users 20 --seed 42` line (the determinism lever, D4)
**reproduces** the original data. This keeps seed faithful to its
nature as a data write and puts reproducibility exactly where the
`--seed` flag already lives. (Seeded *data* is in any case durable
independently of replay, via the ADR-0015 CSV store + `rebuild`;
replay is the scripting re-run path, U4.) The DA confirmed the wiring
trap: because seed is *not* an `AppCommand`, it is correctly absent
from `is_app_lifecycle_entry_word` and replay dispatches it through
the normal data path rather than aborting.
### D17 — CHECK constraints: derive from simple `IN`, else friendly-fail (fork, user-chosen)
A CHECK on a generically-filled column would otherwise fail the whole
batch (DA finding — the block guard only covered `NOT NULL blob`).
Two-tier handling, per the user:
1. **Derive from simple `IN`-CHECKs.** When a column's CHECK is the
common enum-as-CHECK shape — `col IN ('a', 'b', …)` (the column's
own CHECK, single-column, literal list) — seed **parses out the
allowed values and uses them as the generator** (uniform choice).
The frequent `CHECK (status IN ('active','closed'))` case then
"just works" with no override needed.
2. **Best-effort + friendly fail for the rest.** For CHECKs seed
cannot interpret (ranges, expressions, multi-column), it generates
best-effort; if a generated row violates the CHECK, the insert
fails through the existing **H1 friendly-error layer** (ADR-0019)
naming the constraint and pointing at `set`. Such CHECK-guarded
columns are also **pre-flagged in the advisory** (D13) alongside
enum-ish names, so the user is warned before hitting the failure.
No new CHECK engine — tier 1 is a narrow literal-`IN` parse over the
CHECK text already stored in metadata; tier 2 is the existing failure
path.
### D18 — Auto-show is capped for large seeds (DA finding)
ADR-0014 auto-show renders "the affected rows" — fine for one insert,
a wall for a 10 000-row seed. Seed's outcome shows a **capped
preview** (proposed first **20** rows) with a `(showing 20 of N)`
note, not the full set. The row **count** is always reported in full;
only the rendered table is capped.
## Grammar, AST, and cross-cutting wiring
Per ADR-0024, `seed` is registered as a `CommandNode` so completion,
hints, help, and usage flow from one definition. The wiring, as
**explicit acceptance criteria** (a `/runda` pass must verify each —
ADR-0045 showed "claimed verified" is not verified):
- **AST + executor.** A dedicated command variant (`Seed { table,
target_column: Option<String>, count: Option<u32>, overrides:
Vec<SeedOverride>, rng_seed: Option<u64> }`) and a dedicated
`do_seed` worker executor. `do_seed` **reuses shared helpers**
(value binding `impl_value_for`, autogen autofill, FK enrichment,
the multi-row parameterised-insert pattern of `plan_autogen_autofill`,
the UPDATE path for column-fill, per-command persistence, the
`begin_batch`/`end_batch` undo primitive of D15) as library
functions — it does **not** emit `Command::Insert`/`Command::Update`
(X5).
- **Replay / undo classification (D15/D16).** `do_seed` brackets its
writes in one batch (one undo step). The `seed` entry word is
**deliberately absent** from `is_app_lifecycle_entry_word` and
completion's `empty_input_offers_app_command_entry_keywords` (the
`AppCommand` mirror) so replay re-runs it as a data write — an
explicit acceptance check, since the default for an unlisted
recognised command must be "replayed", not "abort".
- **Completion sources:** table-name (existing tables); `.column` and
`set`-clause column slots (columns of the named table); the
generator-name vocabulary (D9) after `as`; `count` number; `set` /
`=` / `in` / `as` / `between` / `and` keywords; `--seed` flag.
- **Syntax highlighting:** `seed` keyword; the generator-name
vocabulary highlighted as **`tok_function`** (reuse the existing
ADR-0022 Amд6 blue — no new theme colour).
- **Hints:** ambient per-slot "what's next" and usage hints, both
modes.
- **Help:** `help seed` topic (`help_id` + per-command block); the
general `help` list picks it up automatically via REGISTRY.
- **Parse-error pedagogy (ADR-0042):** near-miss matrix rows for `seed`
(bare / missing-table / wrong-token / malformed `set`), both modes.
- **Validity indicator (ADR-0027):** typing-time `[ERR]`/`[WRN]` for
unknown table, unknown column (in `.column` or `set`), unknown
generator name after `as`.
- **No DSL→SQL teaching echo (ADR-0038).** `seed` is a utility/app
command, not a DSL form of a SQL statement, so the echo does not
apply. (A future "show the generated INSERTs" is out of scope —
it would dump `count` statements.)
## Implementation phasing
Design is whole; the **implementation** is phased into reviewable,
test-first commits:
1. **Core whole-row seed** *(done, Phase 1)* — grammar/AST/executor;
type-based generation + the `fake`-backed name heuristics
(D7/D8/D11); identifier uniqueness (D10) + constraint uniqueness; FK
sampling (joint tuples) + empty-parent error + junction
distinct-combos (D14); `--seed` determinism (D4); default count + cap
+ zero-no-op (D6/D1); required-column block guard (D1); **undo batch
(D15)**; **replay-as-data-write classification (D16)**; **CHECK
derive / friendly-fail (D17)**; **capped auto-show (D18)**; the
enum/CHECK advisory in its **Phase-1 wording** (D12/D13); full
ambient wiring; both modes.
2. **The `set` override clause** (D2) *(done, Phase 2)* — value / list /
generator / range, type-aware, with completion + highlight +
validity for the generator-name slot.
3. **Column-fill mode** (`seed <table>.<column>`, D1 form 2) *(done,
Phase 2)* — the UPDATE path.
Each phase is independently green before the next. (Phases 2 and 3
landed together — they share the `set`-override executor machinery, so
splitting them risked a state where `set` parsed but column-fill
silently no-op'd.)
## Testing (ADR-0008 tiers 13; test-first)
- **Tier 1 (unit, deterministic via `--seed`):** generator selection
(name × type-gate matrix, including every false-positive guard of
D7); table-context disambiguation (D11); identifier uniqueness and
the FK-wins-over-`*_id` precedence (D10); bounded-date windows (D8);
the `product` generator shape; override resolution + precedence (D2);
the required-column block guard (D1); the count cap (D6). Exact-value
assertions are possible because `--seed` fixes the RNG.
- **Tier 2 (insta snapshots):** the seeded data table render and the
enum advisory (D13) at representative sizes, light + dark.
- **Tier 3 (integration, full event loop):** `seed users 20` end to
end (rows land in db + CSV + history, auto-show, persistence);
FK sampling against a populated parent (incl. a **compound FK** —
every child tuple exists in the parent); **empty-parent friendly
error**; **junction** seeding with distinct combinations and the
over-cap note; the `set` clause forms (quoted literals); **column-
fill** on existing rows (incl. refusal of PK/autogen targets, empty-
table no-op); reproducibility (`--seed 42` twice → identical data
from a fixed state); both modes. Plus the DA-driven cases:
**one-undo-step** (seed then a single `undo` removes all rows);
**replay** of a bare `seed` line (divergent) vs a `--seed` line
(reproduced); **`IN`-CHECK auto-derivation** ("just works") and a
**complex-CHECK friendly failure**; **capped auto-show** on a large
seed.
"All green, no skips" is the only acceptable end state; the Phase-1
baseline (2290 passing / 0 failing / 0 skipped / 1 ignored doctest) is
the regression floor.
## Out of scope / deferred (future SD2 work)
- **Recursive parent auto-seed** (`--recursive`) — D14 errors instead.
- **NULL injection** for nullable columns (teaching optional
relationships / `IS NULL`) — v1 always fills.
- **Multi-locale** generation — English only (X2).
- **User-defined custom generators** (true "override hooks" — register
a named generator) — the `set ... as <builtin>` surface covers the
common need; custom generators are a later SD2 increment.
- **Full per-column seed report** — D13 flags enum-ish only.
- **Column-restricted insert** (`seed t (a, b)`) — rejected (D1).
- **"Show the generated SQL"** teaching echo for seed.
## Alternatives considered
- **Hand-rolled generators only (no `fake`):** minimal dependency, but
synthetic-looking data (`text_a3f9`) — rejected on pedagogy
(pedagogy wins ties).
- **Type-only generation (no name awareness):** simpler, but misses
the biggest UX win (a `users` table that reads like real people) —
rejected.
- **Column-name-only `name` (no table context):** leaves
`products.name` → person names, requiring a manual override on every
product/company table — rejected for the `name`/`title` family
(D11).
- **No override clause (heuristics + type only):** could not answer
"the heuristic guessed wrong, fix it" or enum columns — rejected;
the `set` clause (D2) is the answer to the user's Q3.
- **Recursive auto-seed of empty parents:** powerful but magical and
can seed tables the user did not name — deferred behind a future
flag (D14).
- **Always-random (no `--seed`):** simplest, but no reproducible
datasets and weaker tests — rejected (D4).
- **Full per-column report by default:** a nice teaching artifact but
verbose on wide tables — deferred; flag-only advisory chosen (D13).
- **Reuse `Command::Insert`/`do_insert` directly** from seed: tempting
for code reuse, but collapses command identity and violates X5 —
rejected in favour of a dedicated `do_seed` that calls shared
*helpers*.
- **Skip seed on replay** (classify as app-lifecycle, D16): consistent
with A1's "app-level" label and avoids divergent data, but seed is a
data write and silently skipping it on a scripted re-run is
surprising — rejected; `--seed` is the determinism lever instead.
- **Bare-word `set` list items** (`in (admin, …)`, D2): matched the
early mockups and reads cleaner, but bare words are column
references in the reused grammar (would error) and would force a
custom list form — rejected for quoted literals (grammar reuse +
DSL consistency).
- **Pre-flight refuse any CHECK-bearing table** (D17): safest but
blocks seeding too many legitimate tables — rejected for the
derive-`IN`-else-friendly-fail tier.
- **`set`-driven NULL / per-column report / recursive parent seed:**
deferred — see Out of scope.
+1
View File
@@ -60,3 +60,4 @@ This directory contains the project's ADRs, recorded per
- [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 <T1> to <T2> [as <name>]` 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<String>`; 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 ~4050 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)
- [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** (35 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-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 <table> [count] [--seed <n>]` 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 <generator>` / `between` range, **quoted** dates per the D2 amendment, type-aware, override drops the column from the advisory) and the `<table>.<column>` 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 <table> [count]`** (new rows, default **20**, capped) and **`seed <table>.<column>`** (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 <generator>` / `between a and b` (numeric **and** date), reusing ADR-0026 operators — answers the heuristic-miss case. **`--seed <n>`** 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
+14 -11
View File
@@ -8,9 +8,8 @@ to end across three phases + a restyle).
## §1. State at handoff
**Branch:** `main`. **HEAD `2d0f4b2`** plus an **uncommitted docs
finalization** (ADR-0047 status → implemented, README index, this
handoff — see §6). Push is the user's step.
**Branch:** `main`. **HEAD `f0afec3`** — all work committed, nothing
pending. Unpushed (push is the user's step; normal working state).
**Tests: 2290 passing / 0 failing / 0 skipped / 1 ignored** (the 1
ignored is the long-standing `friendly` doctest). **Clippy clean**
@@ -18,6 +17,7 @@ ignored is the long-standing `friendly` doctest). **Clippy clean**
**This session's commits:**
```
f0afec3 docs: session handoff 64 + ADR-0047 implemented (#22/#24)
2d0f4b2 feat(ui): flat filled rectangles for demo overlays (#22, ADR-0047 D4)
241f60c feat(ui): demo-mode step-caption stealth buffer (#22, ADR-0047 D3/D4)
2584e76 feat(ui): demo-mode keystroke badges (#22, ADR-0047 D2/D4/D5)
@@ -26,8 +26,9 @@ e9eb1b1 docs: ADR-0047 — demonstration overlay layer for casts/teaching (#22)
638b4c9 feat(app): vi-style j/k/g/G navigation in the load picker (#24)
```
**Issues closed:** **#24** (vi nav) and **#22** (demo overlays) — close
#22 once the docs finalization commit lands.
**Issues closed:** both **#24** (vi nav) and **#22** (demo overlays) are
**closed on Gitea** with closing comments — verified via the filtered
issue list. Nothing left open from this session's scope.
## §2. #24 — vi-style load-picker navigation (commit `638b4c9`)
@@ -107,13 +108,15 @@ existing `IndicatorDebounce` already takes. A future Tier-4 PTY harness
## §6. How to take over
**Nothing is pending from this session** — both issues are closed, all
docs landed (`f0afec3`), tree is green. The next session **returns to the
open requirements backlog** (§7). Suggested start: run `/whatsnext`
(it reads this handoff), or pick from §7 below.
1. Read handoffs 62 → 63 → 64, `CLAUDE.md`, `docs/requirements.md`,
`docs/adr/README.md`, and **ADR-0047** (fully landed).
2. **Pending:** the docs finalization commit (ADR-0047 status →
implemented; README index; this handoff). Commit as
`docs: session handoff 64 + ADR-0047 implemented (#22/#24)` (the user
confirms commit messages). Then close **#22** on Gitea.
3. **For demo-overlay work:** `App` has `demo_mode`, `demo_badge`,
`docs/adr/README.md`. ADR-0047 is fully landed; revisit only for
demo-overlay follow-ups.
2. **For demo-overlay work:** `App` has `demo_mode`, `demo_badge`,
`demo_badge_seq`, `demo_caption`, `demo_caption_capturing`,
`demo_caption_buffer`, `last_output_area`. Rendering:
`render_demo_overlays` / `render_badge_box` / `render_caption_box` /
+144
View File
@@ -0,0 +1,144 @@
# Session handoff — 2026-06-11 (65)
Sixty-fifth handover. Continues from handoff-64 (ADR-0047 demo
overlays). This session designed and shipped **ADR-0048 — the `seed`
fake-data generation command (SD1)**, Phase 1, end to end: an ADR with
an extended fork dialogue + two `/runda` passes, then a phased
test-first build.
## §1. State at handoff
**Branch:** `main`. **HEAD will be the doc-wrap-up commit** (see §6) —
all seed work committed, nothing pending. Unpushed (push is the user's
step; normal working state).
**Tests: 2358 passing / 0 failing / 0 skipped / 1 ignored** (the long
-standing `friendly` doctest). **Clippy clean** (nursery, all targets).
+68 over handoff-64's 2290.
**`cargo sweep` run** at wrap-up: `target/` 1.6 G → 183 M.
**This session's commits:**
```
202e25a feat(seed): fake-data generation library + fake dependency (P1.1)
f1e9484 feat(seed): command plumbing + walking skeleton (P1.2)
73493fa feat(seed): FK sampling, empty-parent error, block guard (P1.3a)
9c13501 feat(seed): uniqueness, junction distinct-combos, IN-CHECK (P1.3b)
0b3ab3c feat(seed): SeedResult outcome, capped preview, advisory, count cap (P1.3c)
e6ff63d perf(seed): single-transaction multi-row insert path (P1.3d)
fbd219b feat(seed): --seed flag, ambient wiring, and /runda hardening (P1.4 + DA)
```
(plus the earlier `4d0ae77` multi-tab-scope withdrawal and `0af7f56`
ADR-0048 doc, and the wrap-up doc commit.)
## §2. What `seed` does (Phase 1 — read ADR-0048)
`seed <table> [count] [--seed <n>]` — populate a table with realistic
fake data. **Available in both modes** (A1).
- **Realistic, name-aware generation:** the **`fake` crate** (v5,
English) driven by a **type-gated heuristic catalogue** (`src/seed/
heuristics.rs`) — `email`→email, `first_name`→first name, `price`→
currency, etc., each only firing when the column *type* is
compatible. **Table-context** disambiguates `name`/`title`
(`products.name`→a hand-rolled **product** name, `users.name`→person,
`vendors.name`→company). **Bounded dates** (`dob`/`created_at`/
`date`/`timestamp` → recent windows, never "all of history", anchored
to a fixed reference epoch for reproducibility). Type-based fallback
otherwise.
- **Uniqueness (D10):** the user-fillable PK, compound UNIQUE
constraints, single-column UNIQUE, and identifier-named columns
(`id`/`code`/…) stay distinct across the batch and vs existing rows;
**junction tables** get **distinct FK combinations** (capped at the
available product, reported). Identifier ints get a monotonic
sequence.
- **FK (D14):** every FK column samples an existing parent row (compound
FK reads one consistent parent row); **empty parent → friendly
error**.
- **`IN`-CHECK (D17):** a simple `col IN ('a','b')` CHECK becomes the
value source (enum-as-CHECK just works); complex CHECKs are flagged in
the advisory and best-effort generated (a violation rolls the batch
back).
- **Reproducibility (D4):** `--seed <n>` → identical data on the same DB
state. **Holds with no exceptions** — serial (rowid/MAX+1), FK
(`ORDER BY`), **shortid (seeded RNG)**, all generators.
- **Output:** the seeded-row count, a **capped preview** (first 20
rows), and a **Hint-styled advisory** naming enum-ish / underivable-
CHECK columns filled generically. Count cap 10 000; `seed t 0` no-op.
- **Safety:** one **undo** step (snapshot wraps the whole seed);
**replay** re-runs it as a data write; the insert path is a single
transaction (O(N), atomic, commit-db-last preserved).
## §3. Where the code lives
- **`src/seed/`** — the pure generation library (no DB): `mod.rs`
(`ColumnSpec`, `Generator`, `SeedRng`, `make_rng`), `heuristics.rs`
(`choose_generator` + the catalogue + `is_enum_ish`), `generators.rs`
(`generate_value` + the `product` generator + bounded dates),
`check.rs` (`parse_in_check_values`). ~40 Tier-1 tests, deterministic.
- **`src/db.rs`** — `do_seed` (+ `SeedColPlan`, `sample_parent_key_
tuples`, `seed_value_list_key`, `seed_max_int`, `SeedResult`,
`DEFAULT_SEED_COUNT`/`MAX_SEED_COUNT`/`SEED_PREVIEW_CAP`), the new
**`insert_one_row`** core extracted from `do_insert` (shared, no
tx/persist — so seed runs N rows in one tx), and the `Request::Seed` /
`Database::seed` / worker wiring.
- **`src/dsl/grammar/data.rs`** — `SEED` `CommandNode`, `build_seed`,
the `--seed` flag grammar (`Seq[Flag("seed"), NumberLit]`, the first
DSL flag with a value). `Command::Seed` in `command.rs`.
- **Runtime/render**`CommandOutcome::Seed`, `AppEvent::
DslSeedSucceeded`, `App::handle_dsl_seed_success`. Catalog keys
`ok.rows_seeded` / `seed.capped` / `seed.advisory_generic` /
`help.data.seed` / `parse.usage.seed`.
- **Tests**`tests/it/seed.rs` (25 integration tests),
`tests/typing_surface/mod.rs` (`seed_completion_and_validity`),
`tests/it/parse_error_pedagogy.rs` (bare-`seed` near-miss row),
`src/app.rs` (two render tests), `src/dsl/shortid.rs`
(`generate_with_rng`).
## §4. Process notes (the two `/runda` passes)
- **Pre-build `/runda`** (on the ADR) found six blockers — undo
integration (D15), replay semantics (D16), `set`-value quoting (D2),
CHECK handling (D17), an advisory phase-ordering bug (D13), auto-show
flooding (D18) — all folded into ADR-0048 before any code; the three
genuine forks re-escalated and user-resolved.
- **Post-implementation `/runda`** (on the whole implementation) found
**eight gaps**, all closed: FK-sampling determinism (→ `ORDER BY`),
**shortid not reproducible** (→ seeded RNG, fixed not documented — the
user chose the fix), and six **untested ADR decisions** (D5 advanced
mode, D15 undo, D16 replay, D17 complex-CHECK advisory, atomic
rollback, zero-count) — tests added for each.
## §5. Phase 2 (deferred — designed in ADR-0048, NOT built)
These are the only seed pieces left; both have full designs in
ADR-0048:
1. **The `set` override clause (D2)** — `seed t 20 set role in
('a','b'), status = 'x', work_addr as email, price between 10 and
100`. Value / pick-from-list / explicit-generator / range, **quoted
literals** (grammar-consistent). This is the SD2 "override hooks"
core. The `ColumnSpec.check_in_values``PickFrom` plumbing and the
`Generator` vocabulary already exist; this adds the grammar + a `set`
clause that overrides the per-column plan.
2. **Column-fill (`seed <table>.<column>`, D1 form 2)** — fill one
column across *existing* rows (an UPDATE). Refuses PK/autogen targets;
empty-table no-op.
`requirements.md`: **SD1 `[x]`**, **SD2 `[/]`** (core done; the two
above open), **A1 14/15** (only `hint`/**H2** unregistered).
## §6. How to take over
1. Read handoffs 63 → 64 → 65, `CLAUDE.md`, `docs/requirements.md`,
`docs/adr/0048-seed-fake-data-generation.md` (the whole thing — D1
D18 + the as-built status block).
2. **Seed is feature-complete for Phase 1; nothing pending.** Next
options (user's call): seed **Phase 2** (`set` clause + column-fill);
**H2 `hint`** (closes A1) — own ADR; **TT5 CI**; or the larger
**V4 journal** / **tutorial** ADRs.
3. Two minor, user-deferred observations (non-blocking): the uniqueness
retry cap (`MAX_ATTEMPTS=200`) can cap a *medium* unique domain
slightly below its true size (junction/small domains are exact);
`literal_to_value` doesn't type-check an IN-CHECK literal vs a numeric
column (a malformed `int IN ('a')` CHECK fails cleanly at bind).
+145
View File
@@ -0,0 +1,145 @@
# Session handoff — 2026-06-11 (66)
Sixty-sixth handover. Continues from handoff-65 (ADR-0048 `seed`
Phase 1). This session built **ADR-0048 Phase 2** end to end: the
**`set` override clause** (D2) and the **`<table>.<column>`
column-fill** form (D1 form 2) — the two surfaces Phase 1 deliberately
deferred. Designed-then-DA-vetted (a `/runda` pass that caught a real
ADR-vs-grammar conflict), then built test-first.
## §1. State at handoff
**Branch:** `main`. All Phase-2 work is in the working tree;
**commits are pending the user's approval** (see §6). Unpushed is the
normal working state.
**Tests: 2400 passing / 0 failing / 0 skipped / 1 ignored** (the
long-standing `friendly` doctest). **Clippy clean** (nursery, all
targets). +42 over handoff-65's 2358.
## §2. What landed (read ADR-0048 — Status + D1/D2/D9/D13)
`seed <T>[.<col>] [count] [set <overrides>] [--seed <n>]`.
- **`set` override clause (D2):** four forms, comma-separated —
`status = 'active'` (fixed), `role in ('a','b')` (pick-list),
`work_addr as email` (named generator), `price between 10 and 100`
(range; numeric **and quoted dates**). Type-aware; an override
**drops its column from the generic-fill advisory** (D13). Value
slots reuse `update`'s typed `current_column_value` (quoting
enforced structurally — a bare word is rejected).
- **Column-fill (D1 form 2):** `seed users.email [set …]` fills one
column across **existing** rows (an UPDATE). Refuses PK / autogen
(`serial`/`shortid`/`blob`) targets; **empty table → friendly
no-op**; FK target samples the parent; UNIQUE/identifier target gets
collision-free values; **one undo step**; `set` may only adjust the
filled column; a row count is refused.
- **Named-generator vocabulary (D9):** `src/seed/vocabulary.rs`
`KNOWN_GENERATORS` + `generator_for_name` + `is_known_generator_prefix`,
the single source of truth for completion, validity, and the executor.
- **Range generator:** `Generator::Range { low, high }` in
`src/seed/generators.rs`, interpreted per destination type;
`range_bounds_reason` validates compatibility before generation.
- **Ambient wiring:** completion (generator names after `as`, the
`set <col>` and `.col` column slots, the `set` keyword); highlight
(new `HighlightClass::Function` → existing `tok_function`); validity
(new `IdentSource::Generators` — unknown generator flagged `[ERR]`;
unknown column in `set`/`.col` flagged via the existing Columns
path); help (`help.data.seed`); parse-error pedagogy near-miss rows;
the D13 advisory's **Phase-2/3 wording** (points at `set` and the
column-fill repair). Both modes (D5).
## §3. The ADR amendment (a real DA find)
The pre-build `/runda` pass found that **ADR-0048 D2's "dates stay
unquoted" was impossible** — this DSL has **no date-literal token**
(`Value` is `Number`/`Text`; dates are quoted strings validated by
`bind_date`). Escalated to the user, who chose **quoted dates +
amend the ADR** (the grammar-consistent option). D2 now carries a
dated amendment; the range form uses `between '2023-01-01' and
'2024-12-31'`. This was the only divergence from the ADR text; numbers
remain unquoted.
## §4. Where the code lives
- **`src/dsl/command.rs`** — `Command::Seed` gains `target_column:
Option<String>` + `overrides: Vec<SeedOverride>`; new `SeedOverride`
/ `SeedOverrideKind`.
- **`src/dsl/grammar/data.rs`** — `SEED_SET_CLAUSE` + `SEED_DOT_COLUMN`
grammar; `SEED_GENERATOR` slot (`IdentSource::Generators`,
`HighlightClass::Function`); `build_seed` + the override fold
(`build_seed_overrides` / `parse_seed_override_tail`).
- **`src/dsl/grammar/mod.rs`** — `IdentSource::Generators` +
`HighlightClass::Function`.
- **`src/db.rs`** — `apply_seed_overrides` / `seed_override_plan` /
`seed_override_literal`; `do_seed_column_fill`; `do_seed` +
`Database::seed` + worker wiring threaded with the new params.
- **`src/seed/`** — `vocabulary.rs` (new); `generators.rs` (range
generator + `range_bounds_reason`); `mod.rs` (`Generator::Range`).
- **`src/completion.rs`** — generator candidates after `as`; generator
validity. **`src/input_render.rs`** — `"generator"` invalid-ident
kind. **`src/theme.rs`** — `Function → tok_function`.
- **Catalog**`help.data.seed`, `parse.usage.seed`,
`seed.advisory_generic` (Phase-2/3 wording) in `en-US.yaml`;
`keys.rs` placeholders updated.
- **Tests**`tests/it/seed.rs` (+~30: builder fold, executor
set/column-fill, undo, advanced mode), `src/seed/{vocabulary,
generators}.rs` (range + vocabulary units), `src/completion.rs`
(generator + column validity), `src/dsl/walker/highlight.rs`,
`tests/typing_surface/mod.rs` (completion slots),
`tests/it/parse_error_pedagogy.rs` (near-miss rows).
## §5. Two implementation refinements vs. the ADR (both met the contract)
- **Quoted dates** (the D2 amendment, §3).
- **Value slots reuse `current_column_value`** (the `update … set`
typed slot) rather than the raw ADR-0026 expression operand — no
spurious column-ref match, typed narrowing, consistent with
`update`. The user-facing contract (quoted literals, type-aware) is
fully met.
The `seed_take_value` / `seed_set_error` builder paths are
drift-guards (the typed slots only ever match value literals, so a bare
word is rejected at the grammar level) — they use the generic
`parse.error_wrapper`, mirroring `expr::build_expr`.
## §6. How to take over / next steps
1. Read handoffs 64 → 65 → 66, `CLAUDE.md`, `docs/requirements.md`,
`docs/adr/0048-…md` (Status block + D1/D2/D9/D13 + the amendment).
2. **Seed is feature-complete (SD1 + SD2).** `requirements.md`: **SD1
`[x]`, SD2 `[x]`**. The only open A1 gap is `hint`/**H2** (own ADR).
3. **Commits pending approval.** Suggested split:
- `feat(seed): set override clause + column-fill (ADR-0048 Phase 2)`
— all `src/` + `tests/` changes.
- `docs: ADR-0048 Phase 2 implemented + handoff 66` — ADR / README /
requirements / this file.
4. Next options (user's call): **H2 `hint`** (closes A1); **TT5 CI**;
the larger **V4 journal** / **tutorial** ADRs; or Tier-4 PTY (TT4).
5. Consider a `cargo sweep` at this milestone (`target/` grows).
## §7. Post-implementation `/runda` pass (done this session)
A DA pass over the completed code found **no correctness bugs and no
dropped requirements**; all D1D18 acceptance criteria verified met,
tests confirmed to catch regressions. One **design fork** was surfaced
and **resolved by the user**:
- **Bounded override × UNIQUE column** — a fixed value / too-short
pick-list on a single-column-UNIQUE target used to silently cap the
run (e.g. `seed users 100 set email = 'x'` → 1 row). Now a **friendly
error** up front (`seed_override_capacity_guard`, `src/db.rs`), for
both whole-row and column-fill; generators/ranges stay cap-based
(unbounded sources). ADR-0048 D2 documents it; two tests pin it.
Remaining **non-blocking** edges (noted, not bugs):
- Overriding an **FK column** with a literal: the override wins (D2); a
non-parent value fails safely through the FK-error layer.
- **Column-fill of one column of a *compound* FK** samples that column
independently → an invalid tuple fails safely (UPDATE rejected,
rollback), never corrupts. Single-column FKs / non-FK columns are
exact.
- The generator slot uses the **default candidate-ladder hint** (offers
the vocabulary), not a dedicated prose intro — discoverability is met
by completion; a prose intro is optional polish.
+119
View File
@@ -0,0 +1,119 @@
# Session handoff — 2026-06-12 (67)
Sixty-seventh handover. Continues directly from handoff-66 (ADR-0048
`seed` Phase 2, committed). This was a **manual-testing pass**: the user
exercised the app, found several rough edges, and we triaged each into
*fix now* vs *file an issue*. Net result: **three bug fixes committed**
and **three enhancement issues filed**.
## §1. State at handoff
**Branch:** `main`. Working tree **clean**; all work committed. Unpushed
(push is the user's step).
**Tests: 2407 passing / 0 failing / 0 skipped / 1 ignored** (the
long-standing `friendly` doctest). **Clippy clean** (nursery, all
targets). +7 over handoff-66's 2400.
**Commits since handoff-65:**
```
f7155ce fix(input): thread the `:` one-shot escape into live SQL feedback
4cacb82 fix(completion): don't flag a table alias used before its FROM clause
c3e0103 fix(completion): flag-aware partial so a dash completes flags, not keywords
30b2677 docs: ADR-0048 Phase 2 implemented + handoff 66
a12facc feat(seed): set override clause + column-fill (ADR-0048 Phase 2)
```
(`a12facc`/`30b2677` are the Phase-2 work documented in handoff-66.)
## §2. Bug fixes this session (all committed, all tested)
1. **`c3e0103` — flag completion ate the dash.** Typing a flag at a
flag position (`add 1:n relationship … -`) offered the `on` keyword
and, on accept, produced `-on` / `---create-fk`: the partial-token
walk stopped at `-`, so the dash was outside the replaced range.
Fix: flag-aware partial detection (a dash-prefixed token at a word
boundary is a flag-in-progress, **gated on a flag being expected** so
`where x = -5` stays a number) + a unified flag matcher
(`trim_start_matches('-')`). Affected **all** flags. 4 tests + 2
partial-flag snapshots updated (they'd captured the latent bug).
2. **`4cacb82` — table alias flagged as an unknown column.** In a
SELECT, the projection (`sum(ol.count*…)`) can reference an alias
whose `FROM … OrderLines ol` sits *after* the cursor. The candidate
engine recovers that via the §10.6 full-input lookahead (ADR-0032),
but `invalid_ident_at_cursor` only walked text *before* the cursor —
so `ol` matched no scope and got a red "ERR" overlay on an otherwise
valid query. Fix: give the validity check the same full-input
lookahead and bail when the partial prefix-matches a binding's alias
or table. 1 test.
3. **`f7155ce` — the `:` one-shot escape broke live SQL feedback.**
Submission strips the `:` (ADR-0003), but the *live* feedback kept it
in the buffer handed to the walker, which bailed at the `:`. Effect:
under `:`, Tab completed nothing and a valid query could flash `[ERR]`
— while the same line in full `mode advanced` worked. (The hint
already stripped it, hence "hint shows the name but Tab does
nothing".) Fix: one shared `App::feedback_view()` (the `:`-stripped
SQL + mapped cursor + stripped offset) routed through completion (with
a `replaced_range` offset shift), the validity verdict, and rendering
(new `render_input_runs_feedback` highlights/overlays the view shifted
by the offset; the `:` renders as plain text); the ambient hint was
consolidated onto it (removing the duplicate `strip_one_shot_prefix`).
3 tests + the 9 existing colon tests still green.
## §3. Investigated, **no code change** (working as designed)
- **Comma-`FROM` implicit join** (`select … from A, B, C`) is
**deliberately rejected** — ADR-0032 §11 / OOS-3: *"comma-FROM teaches
habits we do not want to encourage; `CROSS JOIN` covers the same shape
explicitly."* The explicit equivalent (`CROSS JOIN … WHERE …`) works.
- **`sum(…)` returning one row** with no `GROUP BY` is **correct SQL**
(the aggregate collapses the result to one row; SQLite/the playground
allow the non-aggregated columns where Postgres would error). The
user's query needed `group by o.id`. Verified (1 row).
## §4. Open issues filed this session — **next session's candidates**
All on `git.lazyeval.net/oli/rdbms-playground`, label `enhancement`:
- **#26`seed <table>` hint omits the optional count.** A complete
command's optional positional *number* has no Tab candidate, so it's
invisible. `IntroProse` doesn't fit (it only fires for incomplete
required slots; the completing Seq match clears the hint). Needs a way
to advertise optional positional non-keyword args. *(I attempted +
reverted this during Phase 2; see the analysis in the issue.)*
- **#27 — Bottom status line: keybindings-only, context- and
state-aware.** Per-nav-focus keybindings (Input vs sidebar), **include
transient states** (Tab-cycle, history) — user preference — and add
`mode advanced` to the empty-input hint. May warrant a small ADR.
- **#28 — Reconsider relationship prose in `add column` (incidental DDL)
confirmations.** Currently by design (ADR-0044 §1 keeps prose, not
diagrams, for incidental DDL). **User preference: do NOT show the
`References:` / `Referenced by:` block** in the add-column
confirmation at all — focus on the change just made. This revisits a
decided area → land as a **new ADR** superseding the relevant part of
ADR-0016 §5 / ADR-0044 §1; confirm scope (just `add column`, or all
incidental DDL).
## §5. Other open work (unchanged from handoff-66 §6)
`seed` is **feature-complete** (`requirements.md` SD1 `[x]`, SD2 `[x]`).
Remaining roadmap, user's call:
- **H2 `hint`** — the last A1 gap (its own ADR).
- **TT5 CI** — test infra exists; no CI workflow yet.
- **TT4 PTY (Tier-4)** — ADR-0008 specifies it; not wired.
- Larger: **V4 journal**, **tutorial/lesson system** (each needs an ADR).
A possible quick follow-up: a friendlier "use an explicit `JOIN`"
parse-error for comma-`FROM` (point 1) — not filed; mention if wanted.
## §6. How to take over
1. Read handoffs 65 → 66 → 67, `CLAUDE.md`, `docs/requirements.md`.
2. `seed` Phase 2 is done (ADR-0048 Status block is current). The
manual-testing fixes (§2) are committed and green.
3. Pick from §4 (filed issues #26/#27/#28) or §5 (roadmap). #28 is a
decision/ADR; #27 is UX (maybe ADR); #26 is a hint-system enhancement.
4. Consider a `cargo sweep` at this milestone (`target/` grows across
sessions).
+66 -26
View File
@@ -88,12 +88,16 @@ since ADR-0027.)
because relationships are cross-table rather than per-table, they
get their own sibling panel stacked below the tables list, not
nested items within it — user-confirmed 2026-06-10.)*
- [/] **S3** Output panel renders a visualization of the
currently selected item and supports multiple tabs.
*(Partial, verified 2026-06-07: single-element structure
visualisation renders (`output_render.rs:82-180`); **multiple
tabs are not implemented** — the output is one line buffer, no
tab abstraction. Same multi-tab gap as V2.)*
- [x] **S3** Output panel renders a visualization of the
currently selected item.
*(Satisfied: single-element structure visualisation renders
(`output_render.rs:82-180`) — select a table, see its columns /
types / keys. **Multi-tab clause withdrawn 2026-06-11** (user
decision): the original wording promised "and supports multiple
tabs", but the output model is settling on the single scrollable
**V4 journal** rather than switchable tabs, so the tab clause is
dropped from tracked scope. A future return to tabbed output would
be a fresh requirement, not this one. Same withdrawal as V2.)*
- [x] **S4** Hint area below the input field, showing hints about
the current input or last error.
*(Verified 2026-06-07: `ui.rs:1088-1110` `render_hint_panel` /
@@ -242,13 +246,12 @@ since ADR-0027.)
available in both modes: `save`, `save as`, `load`, `new`,
`rebuild`, `export`, `import`, `seed`, `replay`, `undo`,
`redo`, `mode`, `help`, `hint`, `quit`.
*(Partial, verified 2026-06-07: 13 of 15 implemented and
available in both modes — `quit`/`q`, `mode simple|advanced`,
`help`, `save`, `save as`, `load`, `new`, `rebuild`, `export`,
`import`, `replay`, `undo`, `redo` (REGISTRY in
`grammar/app.rs:249-333`). **Missing: `seed`** (tracked as SD1)
**and `hint`** (tracked as H2) — neither is registered. A1
closes when SD1 + H2 land.)*
*(Partial: **14 of 15** implemented and available in both modes —
`quit`/`q`, `mode simple|advanced`, `help`, `save`, `save as`,
`load`, `new`, `rebuild`, `export`, `import`, `replay`, `undo`,
`redo`, and now **`seed`** (ADR-0048 / SD1, done 2026-06-11).
**Only `hint`** (tracked as H2) remains unregistered. A1 closes
when H2 lands.)*
## DSL data commands
@@ -469,15 +472,18 @@ since ADR-0027.)
"relationship-relevant" reach). The §3 last-resort helper line was
considered and rejected. Two `/runda` passes (design + implementation).
Selection-nav and the broader journal direction remain in V4.)*
- [/] **V2** SQL query results render as a dynamic table view in
the output pane, with multiple result tabs supported.
*(Partial, verified 2026-06-07: the **table view** is done —
`output_render.rs:38-72` `render_data_table` renders a
box-drawing frame with aligned columns (numeric right, text
left) and NULL/control-char sanitisation, for `show data` and
after every write (ADR-0014). **Missing: multiple result tabs**
— the output is a single `VecDeque<OutputLine>` with no tab
abstraction (same gap as S3). Multi-tab sits in V4 territory.)*
- [x] **V2** SQL query results render as a dynamic table view in
the output pane.
*(Satisfied: the **table view** is done — `output_render.rs:38-72`
`render_data_table` renders a box-drawing frame with aligned
columns (numeric right, text left) and NULL/control-char
sanitisation, for `show data` and after every write (ADR-0014).
**Multi-tab clause withdrawn 2026-06-11** (user decision): the
original wording promised "with multiple result tabs supported";
retained multi-result output, if ever wanted, now belongs to the
single scrollable **V4 journal** direction rather than switchable
tabs, so the tab clause is dropped from tracked scope. A future
return would be a new requirement. Same withdrawal as S3.)*
- [~] **V3** Full ER-diagram export (whole-database graph, viewed
outside the TUI) — low priority; design and ADR pending.
- [~] **V4** Output panel as a *scrollable per-session log* with
@@ -492,7 +498,13 @@ since ADR-0027.)
*(Partial: PageUp / PageDown scrolling of the existing line
buffer is in, with new output snapping the view to the most
recent. The full V4 scope — smart structure rendering, log
styling, Markdown export, scroll indicator — remains pending.)*
styling, Markdown export, scroll indicator — remains pending.
**As of 2026-06-11 this journal model is the sole tracked
direction for evolving the output pane:** the competing multi-tab
output alternative (the trailing clauses of S3 and V2) was
withdrawn from scope by user decision, so retained / multi-result
output, if pursued, is folded into this journal rather than into
switchable tabs.)*
- [x] **V5** `show <kind> [<name>]` family of commands for
redisplaying schema info on demand.
*(Done 2026-06-07: `show table <name>` + `show data <Table>`
@@ -652,11 +664,39 @@ since ADR-0027.)
## Sample data / seeding
- [ ] **SD1** `seed <table> [count]` generates plausible fake
- [x] **SD1** `seed <table> [count]` generates plausible fake
data; junction tables are seeded with valid foreign-key
references drawn from existing parent rows.
- [~] **SD2** Detailed seeding rules (per-type generators,
locale, determinism, override hooks) — design and ADR pending.
*(Done 2026-06-11 via **ADR-0048** (commits `202e25a``fbd219b`).
Whole-row `seed <table> [count] [--seed <n>]` with realistic
name-aware generation (`fake` crate + a type-gated heuristic
catalogue, table-context name disambiguation, hand-rolled
`product` generator, bounded dates), identifier + constraint
uniqueness, **junction tables seeded with valid FK references
drawn from existing parent rows** (distinct combinations, capped;
empty-parent friendly error), `IN`-CHECK derivation, a
required-column block guard, undo as one step, replay as a data
write, a capped auto-show + enum/CHECK advisory, and an O(N)
single-transaction path. The `set` override clause and
`<table>.<column>` column-fill landed in SD2 Phase 2, below.)*
- [x] **SD2** Detailed seeding rules (per-type generators,
locale, determinism, override hooks).
*(Done 2026-06-11 via **ADR-0048** (Phase 1 + Phase 2). Phase 1:
type-gated name-aware per-type generators with a `fake`-backed
catalogue + table-context disambiguation, **`--seed` determinism**
(serial/FK/shortid all reproducible — D4 holds with no
exceptions), English-only locale (X2). **Phase 2 (the "override
hooks" core):** the `set` override clause — fixed value /
pick-from-list / `as <generator>` / `between` range (numeric and
**quoted** dates, type-aware; an override drops the column from
the generic-fill advisory) — and the `<table>.<column>`
column-fill form (an UPDATE over existing rows, refusing
PK/autogen targets, empty-table no-op, FK/unique-respecting, one
undo step). Adds the `KNOWN_GENERATORS` vocabulary (D9), a range
`Generator`, and full completion / highlight / validity / help /
parse-error-pedagogy wiring. Deferred SD2 increments:
user-defined custom generators, NULL injection, multi-locale,
recursive parent auto-seed.)*
## Query analysis