5cb105b74b
Surfaces from a Devil's-Advocate audit of the DSL → SQL teaching echo (ADR-0038) after Phases 1-3 landed: three doc-drift bugs introduced by the earlier handoff-47 / ADR-promotion commits — requirements.md M4 and both ADR-0038 README index entry + Status block still said "Phase 2 / Phase 3 remain," but `275c726` and `e6ad1ae` shipped them. Updated to reflect actual state: Buckets A + B complete plus the category-3 prose; only the §4 styled-runs polish remains. ADR-0037's README entry also touched to note all four shipping commits of its consumer. Plus a missing test slice the DA flagged: explicit no-echo coverage for the Bucket C cases that flow through command_to_sql's catch-all (show table, explain, replay, every Command::App variant). The contract — ADR-0030 §10 / ADR-0038 §7 Bucket C — forbids echoes for these; a future renderer arm added at the wrong place could silently leak one. The new bucket_c_no_echo_commands_all_return_none pins that. Tests: 2015 passed / 0 failed / 1 ignored (pre-existing); clippy clean. Nothing to escalate.
708 lines
35 KiB
Markdown
708 lines
35 KiB
Markdown
# RDBMS Playground — Requirements (Phase 1)
|
||
|
||
This document is the **consolidated Phase 1 requirements
|
||
checklist** for RDBMS Playground. It captures everything the
|
||
project has committed to so far, derived from the design
|
||
conversation and the ADRs in `docs/adr/`.
|
||
|
||
**Purpose.** Phase 5 verification at every milestone measures
|
||
delivered work against this checklist. An item not on this list
|
||
was not promised; an item silently dropped without confirmation
|
||
is a process failure.
|
||
|
||
**Scope.** The list is intentionally coarse — each item is a
|
||
unit of "satisfied / not satisfied" judgement. When an item is
|
||
taken up for implementation, it is decomposed further in a
|
||
backlog (initially in this repo, later in GitHub issues once the
|
||
repo is pushed).
|
||
|
||
## Status legend
|
||
|
||
- `[ ]` — open, not yet implemented
|
||
- `[x]` — satisfied (implemented + tested)
|
||
- `[~]` — deferred, awaiting an ADR or further design before any
|
||
implementation
|
||
- `[-]` — explicitly out of scope (rationale at the bottom)
|
||
|
||
## Test baseline
|
||
|
||
After the ADR-0027 highlight / hint follow-up (precise WARNING
|
||
spans, the diagnostic overlay + hint wiring, the
|
||
`LIKE`-on-numeric WARNING, the debounce state machine) plus
|
||
two manual-testing bug fixes (optional trailing-flag
|
||
completion; the `--resume` temp-project pointer):
|
||
**1131 passing, 0 failing, 1 ignored** (`cargo test` — the one
|
||
ignored test is a long-standing `` ```ignore `` doc-test in
|
||
`src/friendly/mod.rs`). Clippy clean with the nursery lint
|
||
group enabled. (Earlier reference points: 1100 after ADR-0027's
|
||
initial ship; 1079 after ADR-0026 (complex WHERE expressions);
|
||
1039 after ADR-0025 (indexes); 1006 after ADR-0024 + the
|
||
handoff-14 cleanup; 449 after B2/C2.)
|
||
|
||
---
|
||
|
||
## Distribution and install
|
||
|
||
- [ ] **D1** Cross-platform binaries: Linux, macOS, Windows on
|
||
x86_64 and aarch64.
|
||
- [ ] **D2** Single static binary, no runtime dependencies.
|
||
- [ ] **D3** Released via prebuilt binaries plus Homebrew, Scoop,
|
||
`winget`, and `cargo binstall`.
|
||
|
||
## TUI shell
|
||
|
||
- [ ] **S1** Three-region layout: items list (left), output
|
||
panel (right), input field (bottom).
|
||
- [x] **S2** Items list shows tables and per-table indexes;
|
||
designed to extend to additional element kinds (relations,
|
||
views, etc.) without restructuring.
|
||
*(ADR-0025: the items panel renders a nested list — each
|
||
table with its index names indented beneath it. The nested
|
||
model is the extension point for future element kinds.)*
|
||
- [ ] **S3** Output panel renders a visualization of the
|
||
currently selected item and supports multiple tabs.
|
||
- [ ] **S4** Hint area below the input field; keyboard-toggleable
|
||
for inspecting hints about the current input or last error.
|
||
- [ ] **S5** Mode label and distinct border style on the input
|
||
field communicate the current input mode at all times.
|
||
- [x] **S6** Input-field validity indicator: a debounced
|
||
`[ERR]` / `[WRN]` marker at the right edge of the input row,
|
||
summarising — before submit — whether the current command
|
||
would run. Backed by a walker diagnostics-severity model
|
||
(ERROR / WARNING). Advisory only — never blocks submission.
|
||
*(ADR-0027: `Severity` / `Diagnostic` on `WalkResult`;
|
||
`input_verdict` combines the parse outcome, schema-existence
|
||
ERRORs — unknown table / column — and the ADR-0026 §7
|
||
expression WARNINGs — type mismatch, `= NULL`. The runtime
|
||
debounces the indicator's display ~1 s; the rightmost six
|
||
columns of the input row are reserved unconditionally. New
|
||
`warning` theme colour. A follow-up pass completed §2's
|
||
highlight + hint wiring — diagnostics overlaid on the input
|
||
field and surfaced in the hint panel, with precise
|
||
per-literal WARNING spans — and Amendment 1 adds a
|
||
`LIKE`-on-numeric-column WARNING.)*
|
||
|
||
## Input field
|
||
|
||
- [ ] **I1** Multi-line entry that auto-expands; Ctrl-Enter (or
|
||
equivalent) submits, plain Enter inserts a newline.
|
||
- [ ] **I1a** In-line cursor editing in the input field: Left /
|
||
Right arrows move the cursor by character (UTF-8 boundaries
|
||
honoured), Home / End jump to the extremes, Delete removes the
|
||
character at the cursor, Backspace removes the character
|
||
before. Insertion happens at the cursor position. *(Implemented;
|
||
multi-line editing per I1 still pending.)*
|
||
- [ ] **I1b** Readline-style cursor shortcuts: Ctrl-A / Ctrl-E
|
||
as aliases for Home / End for users on keyboards without those
|
||
keys (and for ergonomics in command-driven workflows). Likely
|
||
followed by Ctrl-W (delete previous word), Ctrl-K (delete to
|
||
end), Ctrl-U (delete to start). Pending.
|
||
- [x] **I2** Persistent navigable input history (project-scoped).
|
||
*(Implemented across Iterations 2 + 6: per-command append to
|
||
`history.log` (Iter 2); on project open, the in-memory
|
||
navigable history is hydrated from the tail of
|
||
`history.log` up to the same in-memory cap (Iter 6). Global
|
||
rolling history is out of scope per OOS-6 / N4.)*
|
||
- [ ] **I3** Tab completion for app commands, DSL keywords, table
|
||
names, column names, and SQL keywords.
|
||
- [ ] **I4** Syntax highlighting for both the DSL and SQL.
|
||
- [ ] **I5** In-flight query/command cancellation (Ctrl-C in the
|
||
output area or input field).
|
||
|
||
## Input modes (per ADR-0003)
|
||
|
||
- [x] **M1** Simple mode is the default. It accepts DSL data
|
||
commands and the canonical app-level commands; raw SQL is
|
||
rejected with a friendly hint.
|
||
- [x] **M2** Advanced mode accepts SQL plus the canonical
|
||
app-level commands without any sigil.
|
||
- [x] **M3** Prefixing a single line with `:` in simple mode is a
|
||
one-shot advanced escape (with the prompt label updated). The
|
||
`mode simple` / `mode advanced` command switches modes
|
||
persistently.
|
||
- [x] **M4** Execution-time mode side-channel — **implemented** via
|
||
ADR-0037 (the channel) and its motivating consumer ADR-0038 (the
|
||
DSL → SQL teaching echo). Every command knows, at execution time,
|
||
which of three modes it ran under — the three-way
|
||
`EffectiveMode` { `Simple`, `AdvancedPersistent`,
|
||
`AdvancedOneShot` } resolves at submit time and threads through
|
||
`Action::ExecuteDsl` → runtime; the persistent `Mode` enum stays
|
||
two-way (the transient one-shot `:` lives on the channel where it
|
||
belongs, not in persistent state). The runtime gates the
|
||
ADR-0030 §10 teaching echo on it: a DSL-form command run in
|
||
advanced/one-shot mode renders the equivalent advanced-mode SQL
|
||
beneath `[ok]`; simple-mode and SQL-entered submissions stay
|
||
silent. Echo coverage: **Buckets A and B complete, plus
|
||
category-3 prose** — every catalogue row in ADR-0038 §7
|
||
round-trips per line through the advanced walker (the §1
|
||
copy-paste contract; §6 category 2 holds it per line). Shipped
|
||
across three commits: **Phase 1** Bucket A — single-statement DDL
|
||
+ `show data` + `--all-rows` fall-throughs (`90479cb`); **Phase 2**
|
||
Bucket B — resolved-name + multi-line echoes (`add index` auto- and
|
||
user-named, positional `drop index`, `add`/`drop relationship` in
|
||
both selector forms, `drop column --cascade`, `add relationship
|
||
--create-fk`) (`275c726`); **Phase 3** the category-3 prose —
|
||
`shortid` generation and type-conversion transforms via the
|
||
pre-existing `client_side.auto_fill_*` / `client_side.transformed*`
|
||
notes, plus the new `change column --dont-convert` caveat
|
||
(`e6ad1ae`). The only remaining ADR-0038 item is the §4
|
||
de-emphasised **styled-runs rendering polish** (ADR-0028) — the
|
||
echo + caveat currently surface as plain `[system]` lines.
|
||
|
||
## App-level commands (per ADR-0003)
|
||
|
||
- [ ] **A1** All canonical app-level commands implemented and
|
||
available in both modes: `save`, `save as`, `load`, `new`,
|
||
`rebuild`, `export`, `import`, `seed`, `replay`, `undo`,
|
||
`redo`, `mode`, `help`, `hint`, `quit`.
|
||
*(Progress: `quit`/`q`, `mode simple|advanced`, `help`,
|
||
`save`, `save as`, `load`, `new`, `rebuild`, `export`,
|
||
`import`, `replay` all implemented (Iterations 4 + 5;
|
||
`replay` via ADR-0024 Phase E — see U4). `seed` in the
|
||
seeding iteration; `undo` / `redo` in the U-series; `hint`
|
||
with H2.)*
|
||
|
||
## DSL data commands
|
||
|
||
- [ ] **C1** Table operations: create / drop / rename.
|
||
*(Progress: create + drop done; **rename done on the advanced
|
||
surface** — `ALTER TABLE … RENAME TO`, ADR-0035 §6 / 4h. A simple-mode
|
||
rename-table verb is deliberately not provided — table rename is
|
||
advanced-mode only.)*
|
||
- [x] **C2** Column operations: add / drop / rename / change
|
||
type. `drop column` and `rename column` use SQLite native
|
||
ALTER TABLE (3.35+ / 3.25+); `change column` routes through
|
||
the rebuild-table primitive since ALTER doesn't support
|
||
type changes. PK and relationship-involved columns are
|
||
refused with friendly messages (drop the relationship
|
||
first); SQLite STRICT enforces type compatibility on the
|
||
data copy during a type change.
|
||
- [x] **C3** Schema constraints: primary key (single and
|
||
compound), foreign key with `ON DELETE` / `ON UPDATE` referential
|
||
actions, indexes, `NOT NULL`, `UNIQUE`, `CHECK`, `DEFAULT`.
|
||
*(PK including compound done at create-table time;
|
||
FK with `ON DELETE` / `ON UPDATE` actions done (ADR-0013) —
|
||
declared via `add 1:n relationship`; symmetric outbound +
|
||
inbound view in the structure renderer; type compatibility
|
||
validated at declaration via `Type::fk_target_type()`.
|
||
Indexes done (ADR-0025) — `add index` / `drop index`,
|
||
rebuild-preserving, persisted in `project.yaml`; UNIQUE indexes
|
||
added on the advanced-mode SQL surface (`CREATE UNIQUE INDEX`,
|
||
ADR-0035 §4d / ADR-0025 Amendment 1; simple-mode `add unique index`
|
||
deferred).
|
||
`NOT NULL` / `UNIQUE` / `CHECK` / `DEFAULT` done (ADR-0029) —
|
||
a constraint suffix on `create table` / `add column`, plus
|
||
`add constraint` / `drop constraint` on existing columns;
|
||
populated-column additions are guarded by a pre-flight
|
||
dry-run that refuses with a table of offending rows.)*
|
||
- [~] **C3a** Modify relationship: `modify relationship <name>
|
||
[on delete <action>] [on update <action>]`. Users can achieve
|
||
the same via drop + add today; one-step modify is a small
|
||
follow-up using the existing rebuild-table machinery. ADR
|
||
pending.
|
||
- [ ] **C4** Convenience: `create m:n relationship from <T1> to
|
||
<T2>` produces an auto-named junction table the user can rename;
|
||
pulls primary keys and FK definitions automatically.
|
||
- [x] **C5** Data operations: insert / update / delete via DSL.
|
||
*(ADR-0014. INSERT short and long forms, UPDATE/DELETE with
|
||
required WHERE plus `--all-rows` opt-in, `show data <T>`,
|
||
per-column-type value-literal validation, FK enforcement
|
||
with metadata-driven error enrichment, auto-show after
|
||
writes. Bulk insert, complex WHERE expressions, and SELECT
|
||
in advanced mode are explicitly tracked separately — see
|
||
C5a below.)*
|
||
- [x] **C5a** Complex WHERE expressions (AND/OR, comparison
|
||
operators, LIKE, IS NULL, IN, BETWEEN) for UPDATE/DELETE/
|
||
show-data filtering; `show data` also gains `where` and
|
||
`limit`.
|
||
*(ADR-0026 steps 1–4: the stratified expression grammar
|
||
reached through a new `Subgrammar` node, the recursive
|
||
`Expr` AST + `build_expr`, wiring into update / delete /
|
||
show data, and `Expr` → parameterised SQL with an implicit
|
||
primary-key `ORDER BY` for `limit`. Type-mismatched WHERE
|
||
comparisons are permissive — they run rather than being
|
||
rejected (§7). The §7 advisory **flagging** of type
|
||
mismatches / `= NULL` is the seam with ADR-0027's
|
||
diagnostics-severity model and is tracked there — see
|
||
ADR-0026 "As-built notes".)*
|
||
|
||
## SQL handling
|
||
|
||
- [x] **Q1** SQL parsed via `sqlparser-rs`; supported subset is
|
||
defined (specifics deferred to a future ADR).
|
||
*(Progress: the advanced-mode SQL surface is authored as grammar
|
||
within the unified grammar tree (ADR-0030 / ADR-0024) and parsed by
|
||
the existing walker — **not** a separate batch parser — so SQL gets
|
||
the same completion / highlighting / hints as the DSL (ADR-0001's
|
||
`sqlparser-rs` reservation is superseded). Implemented so far: full
|
||
`SELECT` (ADR-0032), `INSERT` / `UPDATE` / `DELETE` (ADR-0033), and
|
||
`CREATE TABLE` (ADR-0035, 2026-05-25 — executed structurally: columns
|
||
+ types + `NOT NULL`/`UNIQUE`/`PRIMARY KEY` + `IF NOT EXISTS` (4a),
|
||
then per-column `DEFAULT`/`CHECK` (raw `sql_expr` text) and composite
|
||
`UNIQUE(a,b)` (4a.2), then table-level/multi-column `CHECK` (4a.3 —
|
||
round-trips via the new `__rdbms_playground_table_checks` metadata
|
||
table, since the engine reports no CHECK constraints), then foreign
|
||
keys (4b — inline `REFERENCES` + table-level `FOREIGN KEY` → ADR-0013
|
||
named relationships in the create transaction; self-references and
|
||
bare `REFERENCES <parent>` supported), then `DROP TABLE [IF EXISTS]`
|
||
(4c — reuses `do_drop_table`; `IF EXISTS` is a no-op-with-note), then
|
||
`CREATE [UNIQUE] INDEX` / `DROP INDEX [IF EXISTS]` (4d — reuse
|
||
`do_add_index`/`do_drop_index`; `CREATE UNIQUE INDEX` admitted in
|
||
advanced mode via the `IndexSchema.unique` flag, ADR-0025 Amendment 1),
|
||
then `ALTER TABLE` add/drop/rename column (4e — `alter` is advanced-only,
|
||
runtime-decomposed to the existing column executors; ADD COLUMN reaches
|
||
CREATE-TABLE constraint parity; drop/rename refuse a table-CHECK-
|
||
referenced column), then `ALTER TABLE … ALTER COLUMN TYPE` (4f —
|
||
runtime-decomposed to `change_column_type` with `ForceConversion`, the
|
||
§7 advanced policy: lossy converts with a note, incompatible + static
|
||
refusals (`↔ blob`, non-`int → serial`) refuse, `int → serial` allowed;
|
||
the internal-`__rdbms_*` guard folded into `do_change_column_type`),
|
||
then `ALTER TABLE` add/drop constraint + add FK (4g — `ADD [CONSTRAINT
|
||
<name>] (CHECK | UNIQUE | FOREIGN KEY)` + `DROP CONSTRAINT <name>`;
|
||
ADD = CHECK + composite UNIQUE + FK (PRIMARY KEY + named UNIQUE
|
||
refused); table-CHECK/UNIQUE rebuild with a dry-run guard, FK reuses
|
||
`add_relationship`; named table-CHECKs round-trip via a rebuild-only
|
||
`name` column on `__rdbms_playground_table_checks` + a `project.yaml`
|
||
`check_constraints` `{expr, name}` extension; the internal-table guard
|
||
completed across `do_add_constraint`/`do_add_relationship`)).
|
||
then `ALTER TABLE … RENAME TO` (4h — the one genuinely new low-level
|
||
op, `do_rename_table`: native rename + one-transaction reconciliation of
|
||
the CSV file and every metadata row naming the table, incl. **rewriting
|
||
CHECK text that qualifies a column with the old table name** so a fresh
|
||
rebuild round-trips; refuses same-name / existing-target / `__rdbms_*` /
|
||
non-existent; auto-named indexes + relationships kept stale per §6
|
||
scope; one undo step), then the **4i verification sweep** (shared-entry-
|
||
word completion merge + simple/advanced completion colour; `describe` of
|
||
table-level constraints; self-ref FK pre-submit indicator; CREATE-TABLE
|
||
help/usage refresh). **ADR-0035 Phase 4 (4a–4i) is complete.**)*
|
||
- [x] **Q2** Non-standard syntax rejected with a clear message
|
||
pointing at the supported subset.
|
||
*(Design done — ADR-0030 §8: out-of-subset statements are
|
||
refused with an engine-neutral message naming the construct.
|
||
Implementation pending.)*
|
||
- [x] **Q3** User-facing simplified types map transparently to
|
||
SQLite STRICT types in generated DDL. *(All ten types implemented
|
||
and tested.)*
|
||
- [x] **Q4** Supported SQL subset specification — **ADR-0030**.
|
||
Advanced mode is a standard-SQL surface, engine-neutral; the
|
||
supported surface — `SELECT` (full query surface),
|
||
`INSERT` / `UPDATE` / `DELETE`, `CREATE` / `DROP` / `ALTER
|
||
TABLE`, `CREATE` / `DROP INDEX` — is authored as grammar in
|
||
the unified tree. DDL routes through the typed `Command`
|
||
executor (metadata + the playground type vocabulary
|
||
preserved); DML and `SELECT` execute as validated SQL. Q1's
|
||
implementation is now unblocked.
|
||
|
||
## Database backend (per ADR-0002)
|
||
|
||
- [x] **B1** SQLite via `rusqlite`; all tables created `STRICT`;
|
||
`PRAGMA foreign_keys = ON` per connection. *(Database accessed
|
||
through a dedicated worker thread per ADR-0010.)*
|
||
- [x] **B2** Schema evolution uses the rebuild-table technique
|
||
internally where SQLite `ALTER TABLE` cannot — currently
|
||
the change-column-type code path. Add-column, drop-column,
|
||
and rename-column take the simpler ALTER TABLE route since
|
||
modern SQLite supports them natively; metadata sync into
|
||
`__rdbms_playground_columns` and
|
||
`__rdbms_playground_relationships` happens in the same
|
||
transaction either way.
|
||
- [ ] **B3** Query timeout and cancellation supported (no
|
||
cartesian-join-of-doom can hang the app).
|
||
*(Progress: the worker-thread architecture is in place; the
|
||
cancellation/timeout protocol on top of it is pending.)*
|
||
|
||
## Type system (per ADR-0005)
|
||
|
||
- [x] **T1** All ten user-facing types implemented: `text`,
|
||
`int`, `real`, `decimal`, `bool`, `date`, `datetime`, `blob`,
|
||
`serial`, `shortid`. *(Mapping to SQLite STRICT covered by
|
||
ADR-0005; FK target type rule by ADR-0011.)*
|
||
- [x] **T2** `shortid` generation: base58, 10–12 characters,
|
||
omits ambiguous characters; generated client-side at insert.
|
||
*(Implemented per ADR-0014; auto-fills omitted shortid
|
||
columns and validates user-supplied values against the same
|
||
alphabet and length range.)*
|
||
- [ ] **T3** Compound primary keys handled end-to-end (DSL,
|
||
storage, display, FK reference).
|
||
*(Progress: DSL grammar (`with pk a(int),b(int)`), storage, and
|
||
table-info description are all present; the FK iteration
|
||
references single-column PKs only — compound-key FK references
|
||
remain pending.)*
|
||
|
||
## Visualizations
|
||
|
||
- [ ] **V1** Single-element views render in the output pane: a
|
||
selected table as its structure (columns, types, keys,
|
||
constraints); a selected relationship as two tables joined by
|
||
a line.
|
||
*(Progress: a basic structure view (column rows with SQLite
|
||
type names) is rendered after each successful DDL; pretty
|
||
rendering, selection nav, and relationship line-art pending —
|
||
see V4 for the broader direction.)*
|
||
- [ ] **V2** SQL query results render as a dynamic table view in
|
||
the output pane, with multiple result tabs supported.
|
||
*(Progress: a basic aligned-column data view is rendered for
|
||
`show data` and after every write (ADR-0014). Pretty
|
||
box-drawing tables with truncation/scroll handling, plus
|
||
multi-tab support, remain in V4 territory.)*
|
||
- [~] **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
|
||
inline rich rendering. Direction agreed in conversation: the
|
||
output area is a chronological journal of operations and
|
||
selections (e.g. a "selected table X" entry with the rendered
|
||
structure underneath); structure renderings choose between a
|
||
compact ASCII-table form and a vertical line-per-column form
|
||
based on dimensions; the log is exportable to Markdown so
|
||
learners can keep a record of their session. Design and ADR
|
||
pending before any implementation.
|
||
*(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.)*
|
||
- [ ] **V5** `show <kind> [<name>]` family of commands for
|
||
redisplaying schema info on demand. *(Progress: `show table
|
||
<name>` and `show data <Table>` implemented;
|
||
`show tables`, `show relationships`, etc. pending.)*
|
||
|
||
## Project lifecycle (per ADR-0004)
|
||
|
||
- [x] **P1** Auto-named temp project on startup under
|
||
`<data-root>/projects/`. OS-standard data root via
|
||
`directories` crate; `--data-dir` overrides (Iteration 1).
|
||
- [x] **P2** `save` / `save as` elevate / copy + switch
|
||
(Iteration 4b). `save` on a named project reports
|
||
"already auto-saved".
|
||
- [x] **P3** Auto-save: per-command write-through to YAML +
|
||
CSV + `history.log` inside the SQLite tx with
|
||
commit-db-last ordering (Iteration 2). No dirty state.
|
||
- [x] **P4** `load` opens an in-TUI picker, sorted newest
|
||
first, with `[TEMP]` markers and a `b`-to-browse path-entry
|
||
sub-mode (Iteration 4b).
|
||
- [x] **P5** Existence-only load + explicit `rebuild`
|
||
command with confirmation modal (Iterations 3 + 4a).
|
||
- [x] **P-NAME-1** Temp project directory naming pattern:
|
||
`<YYYYMMDD>-[temp]-<word>-<word>-<word>` from a 161-word
|
||
built-in list (Iterations 1 + 4b). Bracketed `[temp]`
|
||
marker is unambiguous against user-named projects because
|
||
`validate_user_name` rejects brackets.
|
||
- [x] **P-NAME-2** Display-name prettifier strips
|
||
`YYYYMMDD-` AND `[temp]-`; splits kebab / snake / camel;
|
||
title-cases each word.
|
||
- [x] **P-NAME-3** Status bar shows
|
||
`Project: [TEMP] <name>` for temp projects,
|
||
`Project: <name>` for named.
|
||
- [x] **P-CLEAN-1** Unmodified empty temp projects are
|
||
auto-deleted on switch and quit, gated by
|
||
`safely_delete_temp_project`'s stacked guards
|
||
(containment, symlink rejection, `[temp]` marker, contents
|
||
allowlist).
|
||
|
||
## Project file format (per ADR-0004)
|
||
|
||
- [x] **F1** `project.yaml` with `version: 1` field carries
|
||
schema (ordered tables + columns), relationships, and
|
||
`created_at`. `data/<table>.csv` carries table data (UTF-8,
|
||
header row, RFC 4180; NULL distinct from empty string)
|
||
(Iteration 2). Empty tables produce no CSV.
|
||
- [x] **F2** `.gitignore` template (`/playground.db`,
|
||
`/.rdbms-playground.lock`, `/project.yaml.v*.bak`) created
|
||
in each new project (Iteration 1). Per ADR-0007 amendment
|
||
1, `history.log` is NOT in the template — user decides
|
||
whether to commit it.
|
||
- [x] **F3** Migration framework scaffold (Iteration 6).
|
||
`MigratorRegistry` + `migrate_to_latest` +
|
||
`ensure_project_yaml_migrated` are wired into every project
|
||
open; no migrators registered in v1 (the production
|
||
registry is empty). The framework is exercised by tests
|
||
that inject a fake v1→v2 migrator: registry plumbing,
|
||
`.v<N>.bak` backup, version-bump sanity check, and
|
||
newer-than-supported / malformed-version errors are all
|
||
covered. The first real migrator (when v2 ships) is a
|
||
one-file change.
|
||
|
||
## Undo and replay (per ADR-0006)
|
||
|
||
- [x] **U1** Auto-snapshot before **every** data/schema mutation
|
||
(DSL + SQL) into a persisted ring buffer (size N=50, tunable),
|
||
per ADR-0006 Amendment 1 (single-step undo, superseding the
|
||
original destructive-only model). Snapshot is a hybrid
|
||
whole-project copy (database via online backup API + `project.yaml`
|
||
/ `data/*.csv` as files); staged before the mutation's transaction,
|
||
finalised after the db commit (preserves ADR-0015 §6). A batch
|
||
command (`replay` / future batch ops) records one boundary
|
||
snapshot; `import` takes none. A `--no-undo` CLI flag disables
|
||
snapshotting. *(Implemented 2026-05-24, plan
|
||
`docs/plans/20260524-adr-0006-undo-snapshots.md`: `src/undo.rs`
|
||
ring + worker dispatch hook in `src/db.rs`; snapshots gated on a
|
||
user command `source` so internal ops like open-time rebuild are
|
||
not recorded; snapshot-bookkeeping failures are non-fatal, restore
|
||
failures surface.)*
|
||
- [x] **U2** `undo` restores the most recent snapshot (database +
|
||
text, directly); `redo` re-applies (redo stack discarded on new
|
||
work); both prompt for confirmation naming the command being
|
||
undone / re-applied (`Y` confirms). *(Implemented 2026-05-24:
|
||
`undo` / `redo` app commands, `Modal::UndoConfirm`, runtime
|
||
prepare→confirm→restore→refresh; `--no-undo` reports undo is off,
|
||
empty stacks report "nothing to undo/redo".)*
|
||
- [x] **U3** `history.log` records every submitted command in
|
||
append-only form, tagged with its outcome (Iteration 2;
|
||
broadened by ADR-0034). Format: `<ISO-8601 Z>|<status>|<source>`
|
||
per ADR-0015 §5 / ADR-0034 §1 — `status` is `ok` for a
|
||
successful command and `err` for one that failed to parse or
|
||
execute. Hydration (cross-session recall) reads all records;
|
||
replay reads `ok` only.
|
||
- [x] **U4** `replay` runs commands from a `history.log` or
|
||
`.commands` file. *(Implemented via ADR-0024 Phase E:
|
||
`runtime::run_replay` parses each non-blank, non-`#`-comment
|
||
line in advanced mode and dispatches it through the normal
|
||
pipeline; stops at the first genuine error, no rollback.
|
||
ADR-0034 §3: replay reads journal records (`<ts>|<status>|
|
||
<source>`), running `ok` records and skipping non-`ok`, while
|
||
still accepting bare-command scripts. ADR-0034 Amendment 1:
|
||
replay re-applies only schema/data write commands and **skips**
|
||
every app-lifecycle command + nested `replay` — all skips
|
||
continue (a nested `replay` is now skipped, not refused), with a
|
||
`[skip]` warning on `import` / nested-`replay`. Covered by
|
||
`tests/replay_command.rs`.)*
|
||
|
||
## Sharing and export (per ADR-0007)
|
||
|
||
- [x] **E1** `export` produces a zip excluding `playground.db`
|
||
AND `history.log` (per ADR-0007 amendment 1); default
|
||
filename `YYYYMMDD-<projectname>-export-NN.zip` with a
|
||
non-clobbering two-digit sequence under the active data root
|
||
(Iteration 5). The zip preserves the project's directory
|
||
name as a single top-level folder. `import <zip> [as <t>]`
|
||
is the inverse: derive target name from the zip's top
|
||
folder, auto-suffix `-NN` on collision (ADR-0015 §11
|
||
amendment), rebuild from text on open.
|
||
- [ ] **E2** User documentation includes sharing recipes for
|
||
git, email, and direct file transfer.
|
||
|
||
## Sample data / seeding
|
||
|
||
- [ ] **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.
|
||
|
||
## Query analysis
|
||
|
||
- [x] **QA1** `EXPLAIN QUERY PLAN` is run on demand for queries;
|
||
output is rendered as an annotated tree highlighting full
|
||
scans, index use, and join order.
|
||
*(Implemented per ADR-0028: the `explain` prefix over
|
||
`show data` / `update` / `delete`, with a span-styled plan
|
||
tree. `EXPLAIN QUERY PLAN` never executes, so explaining a
|
||
destructive `update` / `delete` is safe.)*
|
||
- [x] **QA2** Plan rendering specifics — tree layout, annotation
|
||
taxonomy, colour scheme. Implemented per ADR-0028 (§3–§6):
|
||
a box-drawing tree, the substring-pattern taxonomy, and the
|
||
`OutputLine` styled-runs mechanism.
|
||
|
||
## Hints, help, errors
|
||
|
||
- [ ] **H1** Friendly error-rewriting layer translates SQLite
|
||
error messages into learner-friendly equivalents.
|
||
*(Progress: foreign-key constraint failures are enriched
|
||
with both inbound and outbound relationship listings (so
|
||
RESTRICT errors point at the children that still reference
|
||
this table); full SQL → English translation pending.)*
|
||
- [ ] **H1a** Strong syntax-help in parse errors. When the user
|
||
types something near-correct (e.g. `insert into T ('Oli')` —
|
||
forgotten `values`; or `update T set x=1` — missing WHERE),
|
||
the error should *name the missing keyword or clause* rather
|
||
than just point at the unexpected character. This is a
|
||
separate effort from H1 (which targets database errors); it
|
||
targets parser errors. Pending — multiple targeted fixes
|
||
shipping piecemeal so far (e.g. `values` becoming optional in
|
||
INSERT removes one such case; ADR-0024's typed value slots
|
||
give per-column-type rejection wording; `insert into T (col)`
|
||
with no `values` clause now flags "looks like Form A — add
|
||
`values (...)`"). A systematic pass is still pending.
|
||
- [ ] **H2** `hint` provides contextual help for the current
|
||
input or the most recent error.
|
||
- [ ] **H3** `help` provides general reference and per-command
|
||
help.
|
||
*(Progress: the `help` command lists currently-supported
|
||
commands + DSL grammar reference + types. As of ADR-0024
|
||
§help_id it is assembled by iterating the command REGISTRY
|
||
and translating each `CommandNode.help_id`, so a new command
|
||
appears automatically. A general reference and `help
|
||
<command>`-style detailed per-command help are still the
|
||
missing pieces.)*
|
||
|
||
## CLI
|
||
|
||
- [x] **L1** Load a project via a positional CLI argument
|
||
(Iteration 1). Plus `--data-dir` to override the data root
|
||
and `--help` / `-h` for the usage banner.
|
||
- [x] **L1a** `--resume` CLI flag opens the most recently used
|
||
project (path tracked in `<data-root>/last_project`).
|
||
Iteration 6: errors cleanly with a stderr banner above the
|
||
shell prompt if no previous project is recorded or the
|
||
recorded path is gone — no silent fallback; mutually
|
||
exclusive with a positional path argument (ADR-0015 §7).
|
||
`last_project` is rewritten on every successful project
|
||
open (startup, load, new, save as, import).
|
||
- [~] **L2** Submit a command alongside project load — deferred,
|
||
not v1.
|
||
|
||
## Tutorials and lessons
|
||
|
||
- [~] **TU1** Tutorial / lesson system — design and ADR pending
|
||
before any implementation. Out of v1 unless an ADR is written.
|
||
|
||
## Documentation
|
||
|
||
- [ ] **DOC1** User- and student-facing reference
|
||
documentation under `docs/`: the DSL command surface,
|
||
the type system, and the boundaries of simple mode.
|
||
`docs/simple-mode-limitations.md` is the first piece —
|
||
it doubles as student explanation and as detailed
|
||
reference. Distinct from in-app `help` (`H3`), the
|
||
interactive tutorial system (`TU1`), and the sharing
|
||
recipes under `E2`.
|
||
|
||
## Testing (per ADR-0008)
|
||
|
||
- [x] **TT1** Tier 1: `cargo test` + `proptest` covering
|
||
pure-logic modules (parser, dispatcher, type mapping, project
|
||
I/O, snapshot ring buffer, replay log).
|
||
- [x] **TT2** Tier 2: Ratatui `TestBackend` + `insta` snapshots
|
||
for representative views.
|
||
- [x] **TT3** Tier 3: synthetic event-loop integration tests
|
||
covering the user-facing flows in this checklist.
|
||
- [~] **TT4** Tier 4: PTY-based end-to-end for the four critical
|
||
flows named in ADR-0008 (cold launch → DDL → quit; save →
|
||
reopen; export → import → rebuild; undo after DROP).
|
||
- [ ] **TT5** CI runs all tiers on Linux, macOS, and Windows on
|
||
stable Rust.
|
||
|
||
## Cross-cutting
|
||
|
||
- [ ] **X1** Comprehensive logging via the project's logging
|
||
infrastructure per `CLAUDE.md` (decision points, parameter
|
||
values, fallback paths).
|
||
- [~] **X2** Language: English-only for v1; multi-language is an
|
||
open question to revisit later.
|
||
- [~] **X3** Accessibility: TUI screen-reader support is
|
||
best-effort and not a v1 commitment; revisit if user need
|
||
emerges.
|
||
- [x] **X4** Auto-fill semantics differ between simple and advanced
|
||
mode — **resolved 2026-05-27** (raised 2026-05-26). Was: simple-mode
|
||
`do_insert` auto-fills an omitted **non-PK `serial`** column with
|
||
`MAX(col)+1`, but the advanced-mode SQL insert auto-filled **only
|
||
`shortid`**, leaving an omitted non-PK serial **silently NULL** —
|
||
violating ADR-0018 §1's "auto-generated on every path" contract (the
|
||
column is `INTEGER UNIQUE`, not `NOT NULL`, so SQLite permits the
|
||
NULL). Confirmed by characterization, escalated, and fixed (decision:
|
||
advanced mode matches simple mode): the advanced-mode auto-fill
|
||
reconstruction (`db.rs`, renamed `plan_shortid_autofill` →
|
||
**`plan_autogen_autofill`**) now also fills an omitted non-PK serial
|
||
with `MAX+1` per row (single- and multi-row), mirroring `do_insert`
|
||
and the existing shortid fill. PK serial is excluded (rowid alias);
|
||
Form B (no column list) still supplies every column. Covered by
|
||
`tests/sql_insert.rs::sql_insert_autofills_omitted_nonpk_serial`.
|
||
Honours ADR-0018 §1/§5; no ADR amendment needed (the contract already
|
||
said "every path"). ADR-0036 was correct that it did not touch this.
|
||
- [~] **X5** Framework cohesion / restructuring — **strategic,
|
||
revisit later** (raised 2026-05-26). The grammar/execution
|
||
framework (lexer → walker `Node`s → unified grammar tree → typed
|
||
`Command`s → executors; ADR-0023/0024 and everything layered on
|
||
since) grew organically from the original grammar outline and now
|
||
reuses + mixes elements across many levels without a *cohesive
|
||
written specification* of what the framework comprises, which
|
||
elements are meant to be reusable, and where the boundaries sit for
|
||
the recurring "reuse vs create new" decision. A concrete symptom:
|
||
commands are coupled tightly enough to their execution that reusing
|
||
an *execution behaviour* tempts reusing the whole *command* (see the
|
||
ADR-0036 discussion — the temptation to emit `Command::Insert` from
|
||
the advanced path just to reuse `do_insert`). Desired end state
|
||
(user-stated): **unique commands for every unique case, with a clean,
|
||
documented structure for reusable mechanics** (so execution helpers
|
||
are shared as library functions, not by collapsing command
|
||
identity). Consider a dedicated specification + restructuring run
|
||
(its own ADR) to map the framework and set the reuse-boundary rules,
|
||
easing future maintenance and extension. Not scheduled.
|
||
|
||
---
|
||
|
||
## Non-functional requirements
|
||
|
||
NFRs are quality bars rather than discrete features. Where a
|
||
target is measurable, it is stated numerically; where it is
|
||
necessarily qualitative, the criterion is named and the bar is
|
||
"reviewer judgement against the criterion."
|
||
|
||
- [ ] **NFR-1 Performance — startup.** Cold launch to first
|
||
rendered frame under 500ms on commodity hardware (developer
|
||
laptop, mid-range desktop). Measured in CI on the Linux runner
|
||
as a regression gate.
|
||
- [ ] **NFR-2 Performance — input latency.** Keystroke-to-render
|
||
latency under 16ms during normal editing; long-running queries
|
||
must execute off the UI thread so the interface remains
|
||
responsive (typing, scrolling, mode switching) while a query is
|
||
running.
|
||
- [ ] **NFR-3 Performance — resource footprint.** Idle memory
|
||
under 50MB on the smallest target platform; no busy-loops; CPU
|
||
near zero when waiting for input.
|
||
- [ ] **NFR-4 Visual quality — distinctive design.** Colour
|
||
palette and typography are deliberate and consistent across
|
||
views; layout uses Unicode box-drawing and symbols where they
|
||
add clarity; rendering avoids the generic flat-default look
|
||
that ships with most TUI frameworks. Criterion: a reviewer can
|
||
identify the app from a screenshot of any view.
|
||
- [ ] **NFR-5 Visual quality — colour use.** Colour conveys
|
||
information rather than decoration: mode indication, query
|
||
result types (numeric vs text vs null), error severity,
|
||
syntax highlighting categories. Foreground/background
|
||
combinations meet WCAG-AA contrast (4.5:1 for normal text)
|
||
even though we have not committed to broader accessibility.
|
||
- [ ] **NFR-6 Cross-platform parity.** Behaviour and visual
|
||
quality are equivalent across Linux, macOS, and Windows on
|
||
crossterm-supported terminals. Platform-specific divergence
|
||
(e.g. font fallbacks) is documented, not silently tolerated.
|
||
- [ ] **NFR-7 Light and dark background support.** The colour
|
||
scheme remains legible and visually coherent on both light and
|
||
dark terminal backgrounds. The mechanism (auto-detect via
|
||
terminal query, explicit user setting, or both) is an
|
||
implementation choice, but the outcome is non-negotiable: no
|
||
dark-on-dark or light-on-light readability failures on either
|
||
background.
|
||
|
||
---
|
||
|
||
## Explicitly out of scope
|
||
|
||
- [-] **N1** Hosted publishing platform — per ADR-0007. Sharing
|
||
is local-artifact based.
|
||
- [-] **N2** Real UUID column type — per ADR-0005. The `shortid`
|
||
type covers the pedagogical need at TUI-friendly width.
|
||
- [-] **N3** Cross-emulator visual regression coverage — per
|
||
ADR-0008. Crossterm abstracts terminals adequately; we revisit
|
||
only if a real regression surfaces.
|
||
- [~] **N4** Global rolling input history (cross-session,
|
||
cross-project). Mentioned in I2's wording; deferred per
|
||
ADR-0015 §12 — project-scoped history (via `history.log`) is
|
||
the v1 surface. Revisit if real demand emerges.
|
||
|
||
---
|
||
|
||
## Maintenance
|
||
|
||
This document is updated whenever:
|
||
|
||
- A new requirement is committed to (added as a new item with the
|
||
next free ID in its section).
|
||
- A deferred item is taken up (status moves from `[~]` to `[ ]`).
|
||
- An item is satisfied (status moves to `[x]`, with a reference
|
||
to the commit, PR, or test that demonstrates it).
|
||
- An item moves out of scope (status moves to `[-]` with a
|
||
rationale and a link to the decision).
|
||
|
||
IDs are stable: once assigned, they are not reused. Removing a
|
||
requirement leaves a "withdrawn" entry referencing the decision.
|