Files
rdbms-playground/docs/requirements.md
T
claude@clouddev1 93266b99c9 docs: ADR-0046 UI sidebar nav-mode + responsive input/hint (#20/#21/#23)
Accepted; implementation pending, phased A→B→C. Treats the three
coupled UI issues as one decision (shared width/height budget):

- #20 hint jumpiness: hint height becomes a function of terminal
  geometry, fixed between resizes, so it no longer shoves the
  input/output panels.
- #21 left column: kept but width-optional (hidden by default ≤90),
  with a new relationships sibling panel and a Ctrl-O navigation/focus
  mode (peek-reveal, expand-on-focus overlay, scroll).
- #23 long input: single-logical-line horizontal scroll plus a 2-row
  display when tall, preserving the ADR-0027 indicator reserve.

A pre-build /runda DA pass drove key corrections: Ctrl-B→Ctrl-O (Ctrl-B
is the tmux prefix), an additive SchemaCache.relationship_details field
(retyping would break completion), full nav-mode key disposition +
modal gate, and Tier-2 snapshot coverage. Reconciles requirements
S1 (evolved), S2 (overridden — separate relationships panel), and
S4 (corrected — the stale "keyboard-toggleable" hint claim is struck;
no toggle added).

Updates docs/adr/README.md index and docs/requirements.md S1/S2/S4.
2026-06-10 16:57:46 +00:00

52 KiB
Raw Blame History

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, now tracked as Gitea issues).

Status legend

  • [ ] — open, not yet started
  • [/] — partial / in progress: some of it is built and tested, but named gaps remain. The entry states what works and what is still missing. (Distinct from [ ], which is genuinely untouched, and from [~], which is deliberately deferred pending an ADR rather than half-built.)
  • [x] — satisfied (implemented + tested)
  • [~] — deferred, awaiting an ADR or further design before any implementation
  • [-] — explicitly out of scope (rationale at the bottom)

Reconciliation note (2026-06-07). A full audit of every [ ] item against the source found ~46 % of them mis-marked — overwhelmingly under-claimed (e.g. tab completion I3 and syntax highlighting I4 were shipped but marked "not yet implemented"). The binary legend was the root cause: a shipped feature, a half-built one, and an untouched one all wore the same [ ]. The [/] marker above was added to fix this, and the audited items were re-marked. When you implement against an item, move it [ ][/][x] rather than jumping straight to [x], and keep the gap note current.

Test baseline

After ADR-0022 Amendment 6 (the curated SQL function-name list — issues #15 tab-completion + #16 typing-time typo hint): 1538 lib unit tests passing, 0 failing, 1 ignored (cargo test --lib; the full cargo test across every binary is 2107 passing, 0 failing, 1 ignored — the one ignored is a long-standing ```ignore doc-test in src/friendly/mod.rs). Clippy clean with the nursery lint group enabled. (Earlier reference points — lib counts: 1131 after the ADR-0027 highlight / hint follow-up + the optional-trailing-flag / --resume manual-testing fixes; 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. Note the intervening issue fixes #8/#13/#12/#7/#9 landed tests without a baseline bump; this is the first refresh since ADR-0027.)


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). (Verified 2026-06-07: ui.rs:26-58 lays out a horizontal split — items panel left, right column subdivided into output panel / input field / hint panel; rendered every frame. ADR-0046 evolves this: the left items region becomes width-optional — hidden by default at ≤ 90 columns, peek-revealed via Ctrl-O navigation mode — so the three-region layout is the wide-terminal default, not an invariant.)
  • 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. ADR-0046 overrides the nesting approach for relationships: 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.)
  • 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 / resolve_hint_lines — a dynamic 1MAX_HINT_ROWS panel below the input showing ambient hints, candidates, or the last error. Correction (2026-06-10, ADR-0046): the original wording said the area was "keyboard-toggleable"; that was never implemented and is deliberately dropped — the panel became indispensable once completion moved into it (ADR-0022), so it is always on. ADR-0046 replaces its content-driven height with a geometry-driven one to stop the resize jump (#20); no toggle is added.)
  • S5 Mode label and distinct border style on the input field communicate the current input mode at all times. (Verified 2026-06-07: ui.rs:896-934 render_input_panel — a coloured, bold mode label plus a mode-distinct border colour (border vs border_advanced), tracking the three-way EffectiveMode incl. the one-shot : state.)
  • 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. (Verified 2026-06-07: app.rs:973-1005 cursor_left / cursor_right (with is_char_boundary checks), Home/End, Delete, Backspace, insert_at_cursor. This is single-line cursor editing and is complete on its own terms; the separate multi-line entry goal is tracked under I1, which is genuinely not started.)
  • 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.
  • 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. (Verified 2026-06-07 — this was mis-marked [ ] despite being shipped: src/completion.rs is 2852 lines with ~100 tests; app.rs:898 binds Tab → completion_tab_forward (and BackTab → _backward) with forward/backward cycling through a candidate memo and a colour-coded candidate line in the hint panel (ui.rs:1125 render_candidate_line). All five candidate categories work — app commands (via REGISTRY), DSL keywords (walker Expectation), table names + column names (SchemaCache), and SQL keywords/functions in advanced mode. Refinement 2026-05-30, issue #15: SQL expression slots (sql_expr_ident) also offer a curated set of SQL function names — KNOWN_SQL_FUNCTIONS in src/dsl/sql_functions.rs, surfaced as CandidateKind::Function (ADR-0022 Amendment 6). The original "broad tab-completion" goal is met; any further polish is incremental, not a missing core.)
  • I4 Syntax highlighting for both the DSL and SQL. (Verified 2026-06-07 — mis-marked [ ] despite being shipped: input_render.rs:64-113 lexes the input to styled byte-range runs (lex_to_runs_in_mode) and renders them per-mode (DSL in simple, SQL in advanced), with nine token classes in theme.rs (tok_keyword, tok_identifier, tok_string, tok_punct, tok_flag, tok_error, tok_function, tok_type) and diagnostics overlaid (error/warning spans). Both surfaces are highlighted; the core goal is met. Refinement 2026-05-29, issue #8: column data types now carry a dedicated HighlightClass::Type / tok_type colour, distinct from identifiers and clause keywords — ADR-0022 Amendment 4; a further refinement 2026-05-30, issue #15: SQL function-name candidates carry a dedicated tok_function colour (the ninth Theme token colour, ADR-0022 Amendment 6). The broad highlighting goal stays open.)
  • I5 In-flight query/command cancellation (Ctrl-C in the output area or input field).

Input modes (per ADR-0003)

  • 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.
  • M2 Advanced mode accepts SQL plus the canonical app-level commands without any sigil.
  • 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.
  • 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 the command's echo line (the [ok] summary it once sat under was retired by ADR-0040, issue #9 — the echo line now carries a ✓/✗ completion marker instead); simple-mode and SQL-entered submissions stay silent. Echo coverage: ADR-0038 is feature-complete — every catalogue row in §7 round-trips per line through the advanced walker (the §1 copy-paste contract; §6 category 2 holds it per line), every §6 category-3 line surfaces, and the §4 de-emphasised styled-runs rendering polish (ADR-0028) is wired. Shipped across four feature 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 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); Phase 4 styled-runs polishOutputKind::TeachingEcho custom rendering branch (dimmed Executing SQL: prefix + the SQL re-lexed via input_render::lex_to_runs_in_mode(Advanced) for token highlighting, same as the input echo), OutputStyleClass::Hint for every cat-3 prose line (caveat + the existing illuminating notes — broader scope, visually consistent) (2aab457).

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. (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.)

DSL data commands

  • C1 Table operations: create / drop / rename. (Verified 2026-06-07: create + drop done; rename done on the advanced surfaceALTER TABLE … RENAME TO, ADR-0035 §6 / 4h (do_rename_table, db.rs:4821). A simple-mode rename-table verb is deliberately not provided — table rename is advanced-mode only — so the requirement is satisfied as designed, not partial.)
  • 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.
  • 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. (Done 2026-06-10 via ADR-0045. create m:n relationship from <T1> to <T2> [as <name>] builds a junction table with one FK column per parent PK column ({table}_{pkcol}, typed via fk_target_type), a compound PK over them, and two CASCADE 1:n relationships — all in one do_create_table call = one undo step. Auto-named {T1}_{T2} (optional as), available in both modes, compound-parent PKs supported (ADR-0043). Self-referential m:n refused; PK-less parent refused. Wired across every surface — completion (m:n composite), hints, highlighting, help/usage, and the advanced-mode DSL→SQL teaching echo (the generated CREATE TABLE … FOREIGN KEY …). 9 integration + 7 typing-surface + echo/parse unit tests. The build surfaced — and fixed — two latent simple-mode dispatch/completion assumptions ("≤1 DSL form per entry word"), now generalized behaviour-preservingly.)
  • 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.)
  • 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 14: 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

  • 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 (4a4i) is complete.)*
  • 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.)
  • Q3 User-facing simplified types map transparently to SQLite STRICT types in generated DDL. (All ten types implemented and tested.)
  • 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)

  • B1 SQLite via rusqlite; all tables created STRICT; PRAGMA foreign_keys = ON per connection. (Database accessed through a dedicated worker thread per ADR-0010.)
  • 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)

  • 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.)
  • T2 shortid generation: base58, 1012 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). (Done 2026-06-09 via ADR-0043: the FK-reference leg now works on both surfaces — DSL add 1:n relationship from P.(a, b) to C.(x, y) and SQL FOREIGN KEY (a, b) REFERENCES P(x, y) (bare REFERENCES P auto-expands to the full PK). References the parent's full compound PK matched positionally, per-pair type-compat (ADR-0011); the FK is engine-enforced, persisted (columns: [a, b] in project.yaml, comma-joined in metadata), shown symmetrically by describe, and --create-fk creates one child column per parent PK column. The relationship model went list-based through all six layers, single-column behaviour preserved (commit b14f019); 12 integration tests in tests/it/compound_fk.rs plus the existing single-column suite as the regression net. The earlier-noted (2026-06-07) breakdown: compound-PK declaration (with pk a(int),b(int)), storage (primary_key: Vec<String>), and display were already present and tested. The FK-reference leg — once an ADR-first ~1520-site change across the relationship model — is what ADR-0043 delivered (back-compat dropped by user decision, so no migration was needed). Subset / non-PK (UNIQUE-target) FK references stay out of scope.)

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. (Done 2026-06-10 — ADR-0044. The table-structure half shipped earlier; the relationship-as-line-art half (ADR-0016 OOS-1) now renders as Style-A two-table connector diagrams wherever a relationship is the subject: show relationship <name> (full structure boxes), show table <T> and add/drop relationship echoes (focal box + compact stacked diagrams). Child-left / parent-right, n…1 cardinality, referential actions, bold title rows; rendered App-side and width-aware (side-by-side ↔ vertical fallback). Compound FKs route a shared bus + an explicit (a, b) ▶ P.(x, y) pairing line; self-referential FKs draw two same-named boxes. Incidental DDL echoes keep the prose form (the "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.)
  • [~] 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. (Done 2026-06-07: show table <name> + show data <Table> (single-item) plus the list-all family show tables / show relationships / show indexes — the latter three landed as Command::ShowList { kind } (one variant, grammar/data.rs), a read-only worker show_list formatting count-headed lists from the same helpers the items panel uses, with help + parse-usage entries and 10 integration tests (tests/it/show_list.rs). The one remaining member of the [<name>] clause — singular per-item detail for relationships/indexes — is split out as V5a below so it has a tracked home rather than living as a footnote here.)
  • V5a Singular per-item detail views show relationship <name> / show index <name> — the [<name>] half of V5 for the relationship and index kinds (the table kind already has show table <name>). (Done 2026-06-07: folded into Command::ShowList { kind, name: Option<String> }name: Some(_) is the singular form. Two grammar branches (relationship <name> / index <name>, reusing the Relationships/Indexes completion sources for the name slot), a worker do_show_one rendering a labelled detail block (endpoints + ON DELETE/UPDATE for a relationship; table, columns, uniqueness for an index) or a friendly "No relationship/ index named X." line, reusing the V5 ShowList render path. Help + parse-usage entries + two ADR-0042 near-miss matrix rows; 5 added integration tests. V5's [<name>] clause is now complete across all three kinds.)
  • V6 Copy the output panel to the system clipboard (issue #11, ADR-0041). copy / copy all copy the whole panel; copy last copies the most recent command's output. Delivery is OSC 52 (SSH-friendly, no native dep) plus a best-effort native write (arboard), always both; the payload is the panel's plain text exactly as rendered. Removes the terminal-select-and-fight-wrapping friction of filing bug reports. (Complements V4's planned Markdown export — a different "get the session out" path.)

Project lifecycle (per ADR-0004)

  • P1 Auto-named temp project on startup under <data-root>/projects/. OS-standard data root via directories crate; --data-dir overrides (Iteration 1).
  • P2 save / save as elevate / copy + switch (Iteration 4b). save on a named project reports "already auto-saved".
  • 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.
  • P4 load opens an in-TUI picker, sorted newest first, with [TEMP] markers and a b-to-browse path-entry sub-mode (Iteration 4b).
  • P5 Existence-only load + explicit rebuild command with confirmation modal (Iterations 3 + 4a).
  • 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.
  • P-NAME-2 Display-name prettifier strips YYYYMMDD- AND [temp]-; splits kebab / snake / camel; title-cases each word.
  • P-NAME-3 Status bar shows Project: [TEMP] <name> for temp projects, Project: <name> for named.
  • 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)

  • 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.
  • 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.
  • 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)

  • 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.)
  • 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". UX polish 2026-05-29, issue #13: the confirm dialog grows to fit its summary on one row, capitalises Snapshot / Yes / No, and renders the snapshot timestamp in local time, human-formatted (24 May 2026, 11:00) via the new chrono dependency.)
  • 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.
  • 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)

  • 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

  • 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. Extended 2026-05-30, issue #7 / ADR-0039: explain now also wraps advanced-mode SQL — select / with / insert / update / delete — via a second Advanced explain CommandNode on the shared entry word, reusing the same plan-tree renderer.)
  • 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 engine error messages into learner-friendly equivalents (ADR-0019). (Done: the friendly::translate_error chokepoint is wired on the live failure path (runtime + app + DbError::friendly_message) and covers all five ADR-0019 §3 categories — UNIQUE, FOREIGN KEY (parent- and child-side), NOT NULL, CHECK, and type-mismatch — with operation×kind×verbosity catalog wording, the messages short|verbose verbosity command, and §6 row-pinpointing via runtime-resolved facts rendered through the bordered diagnostic table. Covered by 44 friendly unit tests + 12 full-stack friendly_enrichment integration tests. Remaining ADR-0019 scope is deferred and separately tracked: the §9 i18n migration sweep of all other user-facing strings, advanced-mode SQL-error sanitization (§OOS-2), and messages persistence (§OOS-3, awaits the settings ADR).)
  • 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. (Done via the ADR-0042 systematic pass, 2026-06-06.) Built piecemeal first (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 (...)"; issue #1 / ADR-0033 Amendment 5, 2026-05-28 added two pedagogical lines that teach the INSERT Form B positional-VALUES contract — a simple-mode submit-time teaching note covering under-supply, over-supply, and extra values (insert.form_b_extra_values_note) and an advanced-mode dispatch-time pre-flight note for the same value-count class (insert.form_b_positional_count_mismatch_note), plus a walker-level diagnostic.insert_arity_mismatch_form_b ERROR that lights the [ERR] validity indicator at typing time; issue #2 / ADR-0022 Amendment 3, 2026-05-29 made the ambient-hint fallback rung schema-aware so the "Next:" prose names the schema-correct next token (, between values, ) after the last) instead of the type-blind close-paren, and so a wrong-arity closed tuple surfaces the real parse error rather than a misleading "submit with Enter"; issue #17 / ADR-0036 Amendment 2, 2026-05-29 then brought the §8.1 arity diagnostic to simple mode at parity with advanced — a wrong-count DSL insert (Form A/B/C) now fires the friendly "N value(s) for col…" message at typing time, counted against the user-fillable columns, with serial/shortid auto-fill named; new keys diagnostic.insert_arity_mismatch_form_b_simple / diagnostic.insert_arity_mismatch_all_auto). The ADR-0042 systematic pass then closed it: a per-command near-miss matrix (tests/it/parse_error_pedagogy.rs) locks every entry word's bare / missing-clause / wrong-token cases plus the committed multi-forms, in both modes; friendlier labels landed (add1:n relationship; bare select → "a projection: …"); the usage block became mode-aware (advanced shows the SQL forms plus the still-valid DSL fallback forms, SQL-first); with got its own CTE template; and cross join … on now teaches that a CROSS JOIN takes no ON clause. The advanced-SQL diagnostics that the survey thought missing (INSERT…SELECT count, RETURNING column scope) were verified already present. One low-priority residual is deferred by decision: at submit time a non-projection expression position (bare where , returning ) still shows the raw expression first-set — typing-time completion already offers the right candidates there, so the payoff is small.
  • H2 hint provides contextual help for the current input or the most recent error.
  • H3 help provides general reference and per-command help. (Done 2026-06-07: the general reference is help (no arg) — intro + the full command list (REGISTRY × help_id, so new commands appear automatically) + the type reference + a footer pointing at the focused form. Per-command help is help <command> (H3's new piece): the HELP node took an optional single-word topic (BarePath), AppCommand::Help { topic }, and note_help_topic renders the block(s) of every command sharing that entry word — so help create covers both create forms — plus help types for the type reference and a friendly "no help for X" pointer for an unknown topic. Help/usage strings catalogued + key-registered; 9 integration tests (tests/it/help_command.rs). A richer narrative overview (modes, the : escape, syntax conventions) is reference-docs scope, tracked under DOC1 — not part of H3.)

CLI

  • 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.
  • 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).
  • L1b Per-project input-mode restore (issue #14, ADR-0015 Amendment 1). The input mode is stored in project.yaml (project.mode:), restored on every open, and persisted on a mode change and on unload (quit / project switch) — so the mode you leave a project in is what reopens. A teacher can ship a project that opens in advanced mode; a learner's last-used mode is restored per project. New --mode simple|advanced CLI flag (precedence --mode > stored > simple; combines with --resume). Independent of the history.log input-history hydration piece of Iteration 6.
  • [~] 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. (Partial, verified 2026-06-07: docs/simple-mode-limitations.md exists (~55 lines, covers the WHERE-expression and table-creation boundaries). Missing: a DSL command-surface reference and a standalone type-system reference under docs/.)

Testing (per ADR-0008)

  • TT1 Tier 1: cargo test + proptest covering pure-logic modules (parser, dispatcher, type mapping, project I/O, snapshot ring buffer, replay log).
  • TT2 Tier 2: Ratatui TestBackend + insta snapshots for representative views.
  • 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). (Verified 2026-06-07: nothing is wired — no portable-pty / expectrl / vt100 dependencies, no PTY test files; ADR-0008 §Tier-4 is a specification only. The Tier-3 tests/it/*_e2e.rs files are synthetic event-loop tests, not PTY. Correcting a stale CLAUDE.md line that read "Tier 4 is wired only for the listed critical flows" — it was not wired at all. Genuinely deferred.)
  • 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). (Done 2026-06-10 via a full-sweep instrumentation pass. The prior state (verified 2026-06-07) was a wired harness (src/logging.rs) but sparse instrumentation — failure-path heavy, nothing in db.rs/parser/executors. The sweep brought every layer to the "log liberally" bar under a documented level discipline (see the logging.rs module doc): db.rs gained entry-level debug! on all 34 do_* executors plus decision-point logs (rebuild-table primitive, insert auto-fill, delete cascade, FK resolution) — so the route through delegating executors is visible in the log sequence; persistence logs every yaml/CSV/history write (the silent-failure paths); runtime logs execute_command_typed dispatch; app.rs logs submit / app-command dispatch / render-mode choice; the parser logs parse begin/outcome at trace (it is a per-keystroke hot path). Levels: debug for per-command detail (off by default, RDBMS_PLAYGROUND_LOG=debug), info for lifecycle, warn for fallbacks, trace for hot paths. Emission verified end-to-end through the real worker thread + logging::init. ~75 → ~135 sites.)
  • [~] 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.
  • 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_autofillplan_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 Nodes → unified grammar tree → typed Commands → 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.