Commit Graph

124 Commits

Author SHA1 Message Date
claude@clouddev1 4a5fd1b5c1 feat(hint): H2 Phase B — per-form keying + the three exemplars (ADR-0053)
The first exemplar (`add 1:n relationship`) showed per-node keying is
too coarse for multi-form commands, so revise the mechanism to per-form.

- CommandNode `hint_id: Option<&str>` -> `hint_ids: &[&str]` (mirrors
  usage_ids); hint_key_for_input_in_mode reuses a factored-out
  pick_form_key (shared digit/m:n/suffix form disambiguation with
  usage_key_for_input_in_mode)
- wire INSERT + ADD (all four forms) with hint_ids
- author the three approved exemplars: hint.cmd.insert,
  hint.cmd.add_relationship, hint.err.foreign_key.child_side
  (what/example/concept) + keys.rs registration
- revise ADR-0053 D3 to per-form; record clause-concept hints as a
  deferred extension (issue #37); update README + plan
- +5 tests; 2488 pass / 1 ignored, clippy clean
2026-06-15 12:18:41 +00:00
claude@clouddev1 e16ad50aa7 docs(adr): ADR-0053 — contextual hint command + F1 keybinding (H2)
Settles the `hint` slot ADR-0003 left pending; closes the last open
piece of A1. Two surfaces (F1 → live-input hint; `hint` command →
last-error expansion), no topic arg, and a new tier-3 teaching corpus
keyed on a new CommandNode `hint_id` so advanced-SQL forms get distinct
mode-correct content. Comprehensive content for v1, authored
exemplars-first. Refines ADR-0003; references ADR-0019/0021/0022/0049/
0051. Files #36 for the parallel help-side gap.
2026-06-14 22:14:11 +00:00
claude@clouddev1 e8fa859ab9 refactor(db): unwind vestigial worker source plumbing (ADR-0052 follow-up)
ADR-0052 moved success journaling out of the worker to the dispatch
layer, leaving the `source` that handlers threaded purely for the
worker's old history.log write dead. Remove it:

- drop `_source` from finalize_persistence and do_rebuild_from_text
- inline + delete the three read-only *_request wrappers
- drop the now-unused `source` param from the ~30 forwarding worker
  handlers (leaf + composite), compiler-guided
- remove the `source` field from the DescribeTable/QueryData/RunSelect
  requests and their DatabaseHandle methods (call sites updated)

The only worker `source` left is the snapshot/undo label
(snapshot_then / stage_pre_mutation / begin_batch). Purely mechanical,
no behaviour change. 2471 pass / 0 fail / 1 ignored, clippy clean.
2026-06-14 13:47:49 +00:00
claude@clouddev1 4aeea55984 feat(history): mode-tagged history + top-of-chain journaling (#30)
Record the submission mode per history entry so advanced commands are
reusable in simple mode, and fix the bug where a ':'-one-shot command
lost its ':' across sessions (ADR-0052, closing #30).

Format: the history.log status token gains an optional ':adv' suffix
(ok / ok:adv / err / err:adv); 'source' stays last and canonical, so
replay is unaffected. The in-memory ring (still Vec<String>) stores
advanced entries ': '-prefixed; recall strips the ':' in advanced mode
and keeps it in simple; hydration reconstructs the prefix from the tag.

Journaling moved from the worker to the dispatch layer (spawn_dsl_-
dispatch / run_replay / app-command sites), where the mode is in scope
with no worker plumbing; finalize_persistence writes only yaml/csv
(commit-db-last still atomic for state). The journal write is now
best-effort (command already committed), consistent with the failure
path. App commands journal simple, so they recall bare. Journaling is
now uniform (every successful command, per ADR-0034) — closing a gap
where show tables/relationships/explain didn't journal.

Amends ADR-0034 (status tag + journaling location), ADR-0015 §6
(history.log out of the worker tx), ADR-0040 (journal-write best-effort).
15 worker-level journaling tests retired, re-covered at the new layer
(history.rs format, app.rs recall matrix, iteration6 cross-session
regression, replay). 2471 pass / 0 fail / 0 skip, clippy clean.
2026-06-14 11:20:55 +00:00
claude@clouddev1 eceedc19b7 feat(ui): context- and state-aware bottom keybinding strip (#27)
Per ADR-0051 (closing issue #27): the bottom status line is now a
keystrokes-only, state-selected strip. A pure status_bar_bindings()
picks the binding set by priority (first match wins):

  sidebar focus   → Ctrl-O next pane · ↑↓/PgUp/PgDn scroll · Esc input
  completion live → Tab/Shift-Tab cycle · Esc cancel · Enter run
  history nav     → ↑↓ browse · Esc clear · Enter run
  editing         → Esc clear · Ctrl-A/E home/end · Ctrl-W del word · Enter run
  default (empty) → Ctrl-O sidebar · Tab complete · ↑ history · Enter run

The editing state surfaces the #29 readline keys (ADR-0049's deferred
advertisement). Typed-command words (mode advanced/simple, the ':'
one-shot) and Ctrl-C quit leave the strip; simple mode's empty-input
hint gains a '`mode advanced` for SQL' pointer (advanced mode shows
none — a switcher knows the way back, and help covers it).

Mechanism: status_bar_bindings + a thin renderer (unit-testable);
App::is_browsing_history() exposes the private history_cursor; the mode
pointer lives in resolve_hint_lines. Catalog: 12 new shortcut labels +
panel.hint_mode_advanced (en-US.yaml + keys.rs, validator 1:1), 5 dead
strip strings removed.

Forks user-chosen: editing state shows #29 keys; quit omitted; no
width-drop machinery (longest strip ~65 cols fits; a width-budget test
keeps it lean). Modal-aware strip is OOS (pre-existing). Tests: 9
Tier-1 unit (per-state key sets, width budget, mode pointer), 1 Tier-3
rewritten, 15 full-panel snapshots re-accepted (reviewed). 2467 pass /
0 fail / 0 skip, clippy clean.
2026-06-13 12:18:37 +00:00
claude@clouddev1 8ac3537df0 feat(render): incidental-DDL confirmations show structure only, no relationships (#28)
Per ADR-0050 (closing issue #28): the confirmation echo after an
incidental structural edit — create table, add/drop/rename/change
column, add/drop index — now renders the structure only (header +
column box + indexes + constraints) and no longer appends the
References:/Referenced by: relationship block.

Rationale: a confirmation reports the change just made, not the
table's relationships, which the user didn't touch. Relationship info
is still one `show table <T>` away, and the relationship-subject
surfaces (show table, add/drop relationship) keep their ADR-0044
diagrams unchanged.

Scope is all incidental DDL (user-confirmed). Mechanism: drop the
relationship-block call from render_structure (all its callers are
incidental DDL); the handle_dsl_success diagram-vs-structure routing
is unchanged. The orphaned relationship_prose_lines + cols_disp
helpers are deleted (the prose format survives in ADR-0016 §5 + git
history for a future OOS-7 always-prose setting).

ADR-0050 supersedes ADR-0044 §1's incidental-DDL prose clause and the
relationship-block half of ADR-0016 §5 (both annotated). Tests: prose-
presence unit test + snapshot removed; new unit test locks structure-
only with inbound+outbound relationships present; the misnamed add-
column integration test inverted + renamed. 2458 pass / 0 fail / 0
skip, clippy clean.
2026-06-12 22:45:18 +00:00
claude@clouddev1 66c8bdaa65 feat(input): readline keymap — Esc-clear + Ctrl-A/E/W/K/U (#29)
Implement the deferred I1b readline shortcuts in the command input
field (ADR-0049, closing issue #29):

  Esc      clear a partly-typed command (only when no completion memo)
  Ctrl-A   cursor to line start (Home alias)
  Ctrl-E   cursor to line end (End alias)
  Ctrl-W   delete the previous word (readline-style, UTF-8 safe)
  Ctrl-K   kill to end of line
  Ctrl-U   kill to start of line

Esc precedence is preserved: a live Tab-completion memo still wins
(Esc undoes the completion first, ADR-0022); Esc clears only when no
memo is alive. While a sidebar panel is focused (Ctrl-O), Esc exits
navigation mode upstream and never clears the input draft. Cursor-only
keys leave history navigation intact like Home/End; buffer-mutating
keys end it like Backspace.

New helpers clear_input / delete_prev_word / kill_to_end /
kill_to_start in src/app.rs. 22 new Tier-1 tests (2458 pass / 0 fail
/ 0 skip, clippy clean). ADR-0049 amends ADR-0046's OOS list;
requirements.md I1b marked done.
2026-06-12 22:12:08 +00:00
claude@clouddev1 ee3ccd8d77 feat(hint): advertise the optional seed count in the hint panel (#26)
At `seed <table> ▮` the hint showed only the `set`/`--seed` chips and
never mentioned the optional row count — a bare positional number with no
candidate, on an already-complete command, so neither the candidate
ladder nor the resolver surfaced it. (A prior IntroProse attempt was
reverted: pending_hint_mode is cleared by the trailing optionals.)

Carry a skipped Optional's IntroProse hint: walk_optional stashes the
inner's key into a new WalkContext.surviving_intro_hint (key + position)
before the empty match clears pending_hint_mode; the snapshot keeps it
only when the skip position is the cursor (so it never leaks past a
later-consumed `set …` clause, nor once the count is given); the
resolver returns it ahead of the empty-expected short-circuit. The seed
count is wrapped Hinted{IntroProse("hint.seed_count")}; the prose names
the count (default 20), the `.column` column-fill form, and `set` /
`--seed`. Tab still cycles the keywords.

Only IntroProse is carried; ProseOnly/ForceProse and the CREATE-TABLE
element (a required Repeated) are untouched. No AmbientHint/renderer
change. Fires in both modes.

ADR-0022 Amendment 7; +3 tests.
2026-06-12 21:34:48 +00:00
claude@clouddev1 deb0948d6c feat(seed): year-as-int + conventional choice-set heuristics (#33, #34)
Two additive D7 catalogue rules, surfaced while writing the website seed
docs. No change to the type fallback, executor, or grammar.

#33 — year-like int columns. `published`/`birth_year` were just `int`, so
they fell to the unbounded int path and produced nonsense (`9419`). Add an
int-gated year rule (after the quantity rule, so `year_count` stays a
count): `year`/`*_year`/`published`/`founded` -> a bounded 1950-2025 year
(new `YearRecent`), or the dob-style birth window 1945-2007 for
`birth`/`born`/`dob` (new `YearBirth`). Plain int; not added to the D9
named-generator vocabulary.

#34 — conventional choice sets. A few enum-ish names have a near-canonical
small set that reads far better than lorem text. Add a type-gated PickFrom
lookup (reusing the existing generator): priority/prio, severity,
rating/stars. `status` is deliberately excluded (values too
domain-specific) and keeps the D12 advisory; a user IN-CHECK still wins.
`priority` leaves ENUM_TOKENS.

ADR-0048 Amendment 1; +8 tests (incl. a column-fill integration test that
also closes a pre-existing gap on that path).
2026-06-12 20:36:20 +00:00
claude@clouddev1 fde50ce3bf fix(ui): mark sidebar focus with an accent colour, not bold (#25)
The focused sidebar panel border (ADR-0046 DC3) was bright `fg` plus
`Modifier::BOLD`. Bold box-drawing glyphs render as broken/gapped
line-art in the asciinema cast player and are fragile in some terminals.

`panel_border_style` now marks focus with a non-bold accent colour
(`theme.mode_simple`, blue); the unfocused border stays muted. Bold is
untouched on text spans (titles, key hints) — the constraint is
specifically that box-drawing borders carry no bold attribute.

Pure style change: the Tier-2 snapshots are text-only so none needed
re-accepting; the Tier-1 assertion was updated and a render-level test
now checks the rendered border cells carry the accent and no bold.

ADR-0046 Amendment 1.
2026-06-12 15:01:26 +00:00
claude@clouddev1 3d4a0fd45e fix(render): trim IEEE-754 noise from displayed decimal arithmetic (#32)
`decimal` is stored as exact TEXT, but SQLite has no native decimal type,
so arithmetic/aggregation implicitly coerces it to an IEEE-754 double.
The computed result carries no playground type, so `sum(price * qty)`
rendered the double's full noise — `298.59999999999997` for `298.60` — a
confusing, off-topic float lesson for a teaching tool.

Add `format_real_display`: round REAL values to 15 significant figures
(a double's reliable precision) then take the shortest round-tripping
form, collapsing `298.59999999999997` to `298.6`. Wired into `format_cell`
(result-set / `show data` cells) only — the sole surface where the noise
appears, since it arises from arithmetic.

Every other f64->string path keeps full precision for semantic, not
cosmetic, reasons: CSV persistence stays byte-exact for round-trip;
`render_value` is a canonical identity key for the uniqueness dry-runs
(dry_run_unique, check_uniqueness_collisions), where rounding would
report collisions the exact-valued engine wouldn't; FK-key matching and
EXPLAIN-SQL literals likewise stay exact.

ADR-0005 Amendment 1; +7 tests.
2026-06-12 14:42:22 +00:00
claude@clouddev1 7e4bc122be fix(completion): treat a bare in-scope table alias as an alias, not an unknown column (#31)
A bare table alias typed where a column is expected — `… GROUP BY o`,
with `o` aliasing `FROM Orders o` — was a blind spot: completion offered
nothing for `o`, and the hint panel called the in-scope alias an unknown
column (`no such column o on table Orders, ...`).

Completion now offers each FROM source's qualifier (alias-if-present-else
table-name) at a bare sql_expr_ident slot, folded into the column
candidate list; on an exact-qualifier partial the alias source steps
aside so the diagnostic can surface. The bare-reference diagnostic arm
emits a targeted `alias_used_as_column` / `table_used_as_column` hint
("`o` is a table alias — write `o.<column>` ...") after the
projection-alias check, so ORDER-BY alias refs still win and a genuine
unknown column still reports `unknown_column`.

Two guards keep the qualified-form advice correct: SQL only (role
`sql_expr_ident`, so the DSL `expr_column` path keeps `unknown_column`
since the DSL has no `table.column` syntax) and effective-qualifier
match (alias-if-present-else-table, so an aliased source referenced by
its shadowed real name falls through rather than being advised as
`name.<column>`). The diagnostic is a drop-in replacement for
`unknown_column` at the same span/Error severity, so verdict/overlay/hint
paths are unchanged.

ADR-0032 Amendment 3; +10 tests.
2026-06-12 14:03:00 +00:00
claude@clouddev1 30b2677bf3 docs: ADR-0048 Phase 2 implemented + handoff 66
- ADR-0048: status → Phase 1 + Phase 2 implemented; D2 amendment
  (quoted dates, no date-literal token) and the override × UNIQUE
  capacity-guard decision; phasing/Status blocks marked done.
- README index: 0048 entry updated (Phase 2 shipped, 2400 tests).
- requirements.md: SD2 → [x] (the override-hooks core + column-fill).
- handoff 66: this session's Phase 2 build + the two /runda passes.
2026-06-12 09:44:36 +00:00
claude@clouddev1 78c38e8b33 docs: ADR-0048 Phase 1 accepted/implemented + handoff 65
- ADR-0048 status -> Accepted; Phase 1 implemented (commits
  202e25a..fbd219b), with the pre-build and post-implementation /runda
  passes and the 2358-test green state recorded; index entry updated.
- requirements.md: SD1 [x] (whole-row seed + FK/junction, both modes,
  --seed reproducibility with no exceptions), SD2 [/] (core generators /
  determinism done; the set override clause + column-fill are Phase 2),
  A1 14/15 (only hint/H2 remains unregistered).
- Handoff 65: the full seed Phase-1 build, the two /runda passes, where
  the code lives, and Phase-2 / next steps.
2026-06-11 21:49:06 +00:00
claude@clouddev1 202e25a94f feat(seed): fake-data generation library + fake dependency (ADR-0048 P1.1)
The pure generation half of `seed` — no command wiring yet:
- src/seed/: ColumnSpec + Generator model and a seeded StdRng; the
  type-gated name-heuristic catalogue (D7) with documented
  false-positive guards; table-context name disambiguation (D11);
  identifier (D10) and enum-ish (D12) detection; per-type + bounded-date
  generators (D8); the hand-rolled product generator (D9); and PickFrom
  for IN-CHECK / enum lists.
- Adds the `fake` crate (v5, default features). Verified: single rand
  0.10.1 (no duplication), determinism via one seeded StdRng driving
  both fake and the hand-rolled generators, security-clean across
  osv/grype/trivy.
- ADR-0048 D3 updated to record the dependency verification.

32 Tier-1 tests (exact-value via fixed --seed); 1673 lib tests pass,
clippy all-targets clean.
2026-06-11 15:35:17 +00:00
claude@clouddev1 0af7f56821 docs: ADR-0048 — seed fake-data generation command (SD1/SD2, A1)
Dedicated `seed` command: realistic, name-aware fake data via the
`fake` crate + a type-gated heuristic catalogue, table-context name
disambiguation, a hand-rolled product generator, bounded dates, an
identifier-uniqueness rule, a quoted `set` override clause (value /
list / generator / range), `--seed` reproducibility, FK sampling
(empty-parent error, junction distinct combinations), CHECK
derive-or-friendly-fail, undo as one batch step, replay as a
data-write, capped auto-show, and an enum/CHECK advisory.

Design settled across an extended user fork dialogue, then hardened
by a /runda DA pass that found six blockers (undo, replay, set
quoting, CHECK handling, advisory phasing, auto-show flood) — all
folded in, genuine forks re-escalated and user-resolved.

Takes up SD2 ([~]->[ ]); SD1 implementation in progress.
2026-06-11 13:48:19 +00:00
claude@clouddev1 f0afec3812 docs: session handoff 64 + ADR-0047 implemented (#22/#24)
Flip ADR-0047 Status -> implemented (commits f879d54..2d0f4b2, phased
A->B->C + flat-rectangle restyle); update the README index entry to
match (implemented, flat black-on-yellow rectangles, final 2290-green
tally, website-cast follow-up noted). Add session handoff 64 covering
#24 (vi load-picker nav) and #22 (ADR-0047 demo overlay layer).
2026-06-11 09:59:51 +00:00
claude@clouddev1 2d0f4b2958 feat(ui): flat filled rectangles for demo overlays (#22, ADR-0047 D4)
Render the keystroke badge and step caption as a solid yellow rectangle
with no border glyphs and a one-cell text margin, instead of a
rounded-border box — deliberately unlike the app's bordered panels so
the demo overlays read as a distinct, eye-catching callout. Shared
fill_overlay_rect helper (borderless Block fill + inset Paragraph).
Snapshots regenerated; ADR-0047 D4 wording updated.
2026-06-11 08:40:07 +00:00
claude@clouddev1 f879d54721 feat(cli): --demo demonstration mode flag + app plumbing (#22, ADR-0047 D1)
Add `--demo` (and the RDBMS_PLAYGROUND_DEMO env fallback) to enter
demonstration mode, threaded onto App.demo_mode through run_loop —
mirrors the --no-undo plumbing. Off by default, zero footprint when
off. The --help line advertises only the visible keystroke badges;
the Ctrl+] caption trigger is kept low-profile (ADR-0047 D6 updated).

Phase A of ADR-0047; behaviour (badges/captions) lands in B and C.
2026-06-10 22:22:12 +00:00
claude@clouddev1 e9eb1b177e docs: ADR-0047 — demonstration overlay layer for casts/teaching (#22)
Accepted decision record for the in-app demo overlay: a --demo mode
that shows automatic keystroke badges ([TAB], [ENTER], …) and a
stealth Ctrl+]-delimited step-caption buffer, both as floating
black-on-yellow boxes at the output panel's bottom-right. All forks
user-confirmed; a /runda pass contributed 10 tightening findings.
Indexed in docs/adr/README.md.
2026-06-10 22:16:44 +00:00
claude@clouddev1 18303784a0 docs: session handoff 63 + ADR-0046 marked implemented (#20/#21/#23)
ADR-0046 status -> Accepted + implemented (8 commits 9f5f76b..22bec61);
README index updated; the two draft-divergent decisions recorded inline
(App.relationships not SchemaCache; nav overlay partial-clear + gutter).
Handoff 63 covers the full UI build across Phases A/B/C; issues
#20/#21/#23 closed on Gitea.
2026-06-10 21:30:00 +00:00
claude@clouddev1 22bec61d11 feat(ui): scroll the focused sidebar panel + refine the nav overlay (#21, ADR-0046 DC3 + DC2)
DC3 — navigation-mode scroll: the focused Tables / Relationships panel
scrolls (Up/Down by a line, PageUp/PageDown by its visible-row count).
Per-panel offsets are clamped to content at render time, and the
renderer reports each panel's visible rows for paging — mirroring the
output panel's scroll. render_items_panel / render_relationships_panel
take &mut App, count their rows, and store+clamp the offset before
building the borrowing lines.

DC2 refinement: the expand-on-focus overlay now clears only the sidebar
strip plus a one-column gutter, leaving the base output/input/hint
visible (unchanged) to the right rather than blanking the whole area —
truer to "underneath keeps its layout", with the gutter keeping the
cut-off edge clean (chosen after eyeballing both variants). ADR DC2 and
the overlay snapshot updated to match.

Tests: line/page scroll move only the focused panel and clamp; the
render clamps a past-the-end offset so the last row stays visible.
2026-06-10 21:27:13 +00:00
claude@clouddev1 94825d0f36 feat(ui): relationships sidebar panel + schema data (#21, ADR-0046 DB2/DB4)
The left column now stacks a Tables panel over a Relationships panel.
Each relationship renders as three narrow lines — its name, then the
endpoints broken at the arrow (Customers.id -> / indented
Orders.customer_id) — ellipsized past the inner width. The panel is
content-sized within [5 rows ("(none)" when empty), half the column];
the Tables panel keeps the rest (>=3 rows). Phase C adds focus+scroll
for content beyond the cap (clipped for now).

Data path: a new worker Request::ReadAllRelationships +
Database::read_all_relationships returns full RelationshipSchema
records; the runtime posts them via a RelationshipsRefreshed event
alongside the schema-cache refresh, and the App holds them in a new
`relationships` field.

ADR deviation (recorded in ADR-0046 DB2 + index): DB2 specified this
data on SchemaCache; it lives on the App instead — SchemaCache is
walker/completion-facing and needs only relationship names (untouched),
while the full records are UI-only, so App is the cleaner home and it
avoids editing ~23 SchemaCache literals. No behavioural difference.

Tests: panel-height bounds, the three-line render, the empty "(none)"
case, a snapshot, read_all_relationships end-to-end (real DB via the
m:n junction), and the event->field handler.
2026-06-10 18:44:27 +00:00
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
claude@clouddev1 8bd43ccadf feat: create m:n relationship convenience command (C4, ADR-0045)
`create m:n relationship from <T1> to <T2> [as <name>]` generates 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`), both modes, compound-parent PKs
supported (ADR-0043). Self-referential m:n / PK-less parent / internal
junction name / name collision all refused.

Wired across every surface: grammar (separate CREATE_M2N node), worker
executor, runtime dispatch, completion ("m:n" composite), hints,
highlighting, help + usage catalog + disambiguator, and the advanced-mode
DSL->SQL teaching echo (render_create_m2n, round-trips as valid SQL).

Generalized/fixed framework assumptions the build + two /runda passes
surfaced (all behaviour-preserving for existing commands):
- simple-mode dispatch committed simple.first() unconditionally -> tries
  candidates, so `create table` no longer shadows `create m:n`.
- the completion continuation-merge was advanced-only -> runs in simple
  mode too when an entry word has >1 DSL form (gated simple_count>1).
- do_create_table now rejects internal `__rdbms_*` names (closes a
  pre-existing hole on the DSL create-table path too, not just m:n).
- usage disambiguator now recognizes the `m:n` opener.

Tests: 14 integration (tests/it/m2n.rs), 7 typing-surface matrix, echo /
highlight / usage / internal-name units. Closes C4.
2237 pass / 0 fail / 1 ignored. Clippy clean.
2026-06-10 14:26:33 +00:00
claude@clouddev1 e598008ecf docs: ADR-0045 m:n convenience command (C4); accepted
create m:n relationship from <T1> to <T2> [as <name>] generates a
junction table (compound PK over the two FK column sets, CASCADE FKs)
plus two 1:n relationships, in one do_create_table call = one undo
step. Forks user-confirmed; /runda DA pass verified the reuse against
code and the no-PK-tables-exist-in-advanced-mode fact (parent-PK guard
retained). Self-referential m:n refused; FK cols named {table}_{pkcol}.
2026-06-10 13:18:07 +00:00
claude@clouddev1 0a343036d8 feat: compound-FK bus routing + complete V1 relationship visualization (ADR-0044)
Completes requirement V1. A compound (multi-column) FK now routes a
bus connector — each paired endpoint's stub merges into a shared
vertical channel that splits to the other side — plus an explicit
"(a, b) ▶ P.(x, y)" pairing line; the bus generalises the single-column
jog (reproducing it exactly, so prior snapshots are unchanged).
Self-referential FKs render as two same-named boxes.

- output_render.rs: gutter_seg routes all endpoint pairs via a
  junction() bus; pairing line for compound FKs; compound, self-ref,
  and compound-from-data (build_diagram_table glue) tests + snapshots
- compound_fk.rs: worker test that show_relationship carries both
  paired column lists into the diagram payload
- db.rs: document do_show_one's now-app-superseded relationship prose
  branch (retained as a worker-API/text fallback; could back a future
  non-visual display option, cf. ADR-0044 OOS-7)

Second /runda pass over the implementation: confirmed ADR-compliance,
UTF-8/byte-range safety, and edge-case routing. The ADR §3 last-resort
helper line was considered and rejected (vertical fallback + ratatui
truncation cover all realistic cases). ADR-0044 marked implemented;
requirements.md V1 -> [x].

Full suite 2207 pass / 0 fail / 1 ignored; clippy nursery clean.
2026-06-10 10:17:09 +00:00
claude@clouddev1 bb02dfb752 docs: ADR-0044 relationship visualization (V1); accepted
Two-table connector diagrams (Style A) for relationships, resolving
ADR-0016 OOS-1 and the open half of requirements.md V1. Reach is
'relationship-relevant' (show relationship / show table / relationship
DDL echoes; incidental echoes keep prose). App-side rendering, width-
adaptive side-by-side vs vertical, compound-FK pair routing, bold box
title rows. Revised after a /runda DA pass corrected three inverted-
architecture claims (App-side rendering, untracked output width,
prose-in-worker show-relationship).

Index updated per ADR-0000.
2026-06-09 21:47:35 +00:00
claude@clouddev1 b17148b467 docs: scrub GitHub-specifics after Gitea migration; add tea issue conventions
- Cargo.toml: repository -> git.lazyeval.net/oli/rdbms-playground
- requirements.md: backlog now tracked as Gitea issues
- ADR-0001 Amendment 1: distribution channel reopened post-migration
  (Decision text preserved per supersede-don't-rewrite)
- CLAUDE.md: issue-tracking working method + Gitea/tea operational section
2026-06-09 20:07:40 +00:00
claude@clouddev1 4752ba29a0 feat: compound-PK foreign-key references — grammar + tests (ADR-0043)
Multi-column FK parsing on both surfaces: DSL from P.(a, b) to
C.(x, y) (parenthesized endpoint; single bare form unchanged) and
SQL FOREIGN KEY (a, b) REFERENCES P(x, y) incl. bare-reference
auto-expand. consume_fk_reference + the table-level/ALTER FK
parsers collect column lists; the from P. completion now offers
( (snapshots updated). 12 integration tests in
tests/it/compound_fk.rs cover parse (both surfaces), engine-enforced
FK, arity + partial-PK + per-pair-type-mismatch refusal,
--create-fk per-column, save->rebuild round-trip, undo (one step),
and single-column preservation. Mark T3 [x]; ADR-0043 implemented.
2026-06-09 18:44:37 +00:00
claude@clouddev1 b14f0199e9 refactor: relationship model to column lists for compound FK (ADR-0043)
Move the FK column fields String->Vec<String> through all six
layers (AddRelationship/SqlForeignKey AST, RelationshipSchema,
metadata, project.yaml, ReadForeignKey, RelationshipEnd). Metadata
stores comma-joined lists in the existing TEXT cells; project.yaml
endpoints now columns: [a, b] (house style). Executor logic is
multi-column ready: resolve_fk_parent_columns (full-PK F-A +
auto-expand F-D), per-pair type-compat, schema_to_ddl multi-column
emission, pragma FK read grouped by id, auto-name + --create-fk
per-column, multi-column teaching echo. Single-column behaviour
preserved (one-element vecs); all 2181 tests green. The grammar to
parse multi-column input lands next.
2026-06-09 18:25:40 +00:00
claude@clouddev1 b688592b4c docs: ADR-0043 implementation-readiness notes from /runda DA pass
DA pass found three change sites the first sketch missed
(teaching-echo renderers, --create-fk per-column creation, the
auto-name generator) and made explicit the rules the forks left
implicit: SQLite FK precondition (compound PK provides the unique
index), explicit parent cols must be the PK set (any order,
positional), arity/empty/inline-rejection wording, single-in-parens
accepted, --create-fk per-column typed to fk_target_type. Expanded
the test plan to cover enforcement, auto-expand, undo, round-trip.
Fixed a stale 'legacy yaml loads' test line (no back-compat).
2026-06-09 17:11:01 +00:00
claude@clouddev1 274e2b17b7 docs: ADR-0043 compound-PK foreign-key references (T3); accepted
Audit found single-column FK woven through ~15-20 sites; earns an
ADR. Decision: reference the parent's full compound PK, matched
positionally to an equal-length child list, per-pair type compat.
DSL `from P.(a,b) to C.(x,y)`; SQL `FOREIGN KEY (x,y) REFERENCES
P(a,b)` with bare-FK auto-expansion. Storage follows the existing
primary_key: [...] list convention (yaml columns: [a,b], uniform
JSON in unchanged metadata TEXT cols); back-compat not required,
so no migration. Also marks T3's verified scope.
2026-06-09 17:01:38 +00:00
claude@clouddev1 c305dc7282 docs: mark H1a done via the ADR-0042 systematic pass
requirements.md H1a → [x]: the per-command near-miss matrix (entry words,
missing clauses, committed multi-forms, both modes) plus the gap fixes
(G1 `1:n relationship`, G2 select projection gloss, G3 mode-aware usage
showing all valid forms, G4 `with` CTE template, CROSS JOIN ON teaching
message) close the systematic pass. The advanced-SQL items the survey
flagged (INSERT…SELECT count, RETURNING scope) were verified already
present. One low-priority residual is deferred by decision (submit-time
expression first-set at non-projection positions).

ADR-0042: record the pre-existing `SELECT *` arity caveat (INSERT…SELECT
with a star projection isn't expanded for pre-flight arity; engine
catches it at execution — adjacent to ADR-0019 §OOS-2).
2026-06-06 07:44:55 +00:00
claude@clouddev1 d6e229f0f5 feat: H1a CROSS JOIN ON teaching message; advanced-SQL gaps re-verified (ADR-0042)
Empirically re-checking ADR §3's advanced-SQL "gaps" reversed two of
three — the code survey that produced the list was wrong:
- INSERT…SELECT column-count: already handled (verdict=Error, "the
  column list names N column(s) but M value(s) are given";
  insert_select_arity_mismatch_fires).
- RETURNING scope: already handled (completion offers the table's
  columns; `returning <unknown>` → unknown_column diagnostic).

The one genuine residual is fixed: `select … cross join b on …`
rejected the ON with a bare "expected end of input". Add
parse.cross_join_no_on — "a CROSS JOIN has no ON clause — it pairs
every row; for a join condition use `JOIN … ON`, or filter with
`WHERE`" — rendered when the failing token is `on` and the most
recent consumed join is a CROSS join (a precise signature: every
other join requires `on`, so `on` is expected there, not a failure).
Render-only in format_walker_error; two misfire guards locked (plain
join still asks for ON; a stray `on` with no join does not fire).

ADR-0042 §3 corrected + Implementation-outcome records the advanced-SQL
re-check and the user-confirmed low-priority residual (submit-time
expression first-set at non-projection positions, where typing-time
completion already offers the right candidates).

Full suite green (lib 1578 / it 388 / typing_surface_matrix 192); clippy clean.
2026-06-05 19:02:11 +00:00
claude@clouddev1 1d4923b15b fix: H1a G3 advanced usage shows all valid forms; complete near-miss matrix (ADR-0042)
The /runda DA pass found G3 over-corrected: advanced-mode `create`/`drop`
showed SQL forms only, hiding the DSL fallback forms that are valid input
in advanced mode (verified: `create table Foo with pk`, `drop column …`
parse and dispatch). Per the user decision, the advanced usage block now
shows every form valid in the mode, SQL-primary first, then the DSL
fallback forms — a usage hint must never hide working input. Simple mode
unchanged (DSL forms only).

Matrix completion (closing the residual coverage tail):
- arg-less app commands (help/rebuild/new/load/undo/redo/export/import)
  audited + locked — all reject trailing junk with "expected end of
  input" + usage.
- committed multi-forms (add index/constraint/1:n relationship, drop
  index/constraint/relationship, show table, change column, create index,
  alter table add/drop) audited + locked in
  near_miss_matrix_committed_multiforms — each renders its own
  form-specific missing-keyword message + usage.

Also from the DA pass:
- G2 distinct+all detector empirically verified unique to projection
  start (no misfire at count( / union / union all / select distinct).
- stale `chumsky` comment removed (app.rs import handler).
- ADR-0042 Implementation-outcome section records G1–G4, the
  user-confirmed G3 decision, and the now-complete matrix coverage.

Full suite green (lib 1578 / it 387 / typing_surface_matrix 192); clippy clean.
2026-06-05 18:46:57 +00:00
claude@clouddev1 0e6f767848 docs: ADR-0042 — continue H1a parse-error pedagogy on the grammar tree
ADR-0020/0021 specified a chumsky-based H1a; ADR-0024 replaced chumsky
with the scannerless walker, leaving both obsolete. Mark them superseded
(kept as institutional memory) and add ADR-0042, which restates H1a
against the architecture as built.

ADR-0042 records that H1a is substantially shipped already — per-command
usage block, available-commands fallback, source-derived ident slot
labels, curated parse.custom.* near-miss messages, and schema-aware
[ERR] diagnostics — and defines the remaining work: a verified
per-command near-miss matrix (the definition of done), friendlier
literal expectation labels that add role context while keeping the
exact literal visible, and advanced-mode SQL parse parity (RETURNING
scope, CROSS JOIN ON, INSERT…SELECT count), kept distinct from
ADR-0019 §OOS-2 engine-error sanitisation.

- docs/adr/0020,0021: superseded notes + README entries
- docs/adr/0042: new ADR
- docs/adr/README.md: index upkeep (ADR-0000 rule)
2026-06-03 14:05:09 +00:00
claude@clouddev1 d0c8f9d5d2 feat: copy the output panel to the system clipboard (#11)
New app-level `copy` / `copy all` / `copy last` command (ADR-0041).
Delivery is OSC 52 *and* a best-effort native write (arboard), always
both — OSC 52 acceptance is undetectable, so a true fallback can't be
built. Payload is the panel's plain text exactly as rendered (tags,
✓/✗, box-drawing), drift-locked to render_output_line. arboard added
--no-default-features (X11-only; OSC 52 covers Wayland).

Amends ADR-0003's command registry; requirements V6.
2026-06-02 14:23:21 +00:00
claude@clouddev1 516848ff63 test: integration-test the mode persist-on-unload wiring (#14)
The post-/runda DA pass on 4cd574b found the persist-on-unload wiring
(quit + project switch calling Database::set_mode) had no integration
test — only the db-level set_mode behaviour was covered, not that the
runtime actually invokes it on unload.

Add runtime::switch_persists_the_outgoing_projects_mode, driving the
real handle_project_switch end-to-end and asserting the outgoing
project's project.yaml recorded the mode it was left in. Red-first
verified: with the set_mode call disabled it fails (None vs
Some(Advanced)). The quit unload site shares the same set_mode call;
Action::Quit emission is already covered in app tests.

Updates ADR-0015 Amendment 1 coverage note.
2026-06-02 08:06:48 +00:00
claude@clouddev1 4cd574b909 feat: persist & restore per-project input mode (#14)
The input mode always started in simple; a learner who quit in advanced
had to re-toggle every launch. Store the mode per-project in project.yaml
(project.mode:, optional, default simple) and restore it on every open.

Mode is live UI state, not schema: the worker stamps the current mode
into project.yaml on every write, so a later command rewrites the live
value rather than clobbering it — no db round-trip needed. The mode is
persisted on unload (quit + project switch) so the mode you leave a
project in is always what reopens; the `mode` command also persists
immediately. A switch saves the outgoing mode, then restores the
incoming project's stored mode.

New --mode simple|advanced CLI flag (precedence --mode > stored >
simple; combines with --resume). A teacher can ship a project that
opens in advanced mode and export it to students (the mode travels in
the zip).

ADR-0015 Amendment 1; ADR-0003 note; help banner; requirements L1b.
2026-06-02 06:47:34 +00:00
claude@clouddev1 ae57c6fc82 feat: colour output tags by status, not mode — readable error bodies (#10)
The output tag was tinted by submission mode for every line kind, so a
[system] line and an [error] line rendered with an identical leftmost
tag — distinguishable only by body colour. And flooding the whole error
body in red made long messages hard to read.

Colour the tag by message status instead (its OutputKind): [system] →
green, [error] → red; the echo tag keeps the mode tint (ADR-0037's
actual purpose — per-command success rides the ✓/✗ marker). Bodies go
neutral; the error body stays bold for weight (rustc-style: severity-
coloured label, readable bold message). Yields a status traffic-light
matching the ✓/✗ palette.

Narrows ADR-0037's mode side-channel to the echo line it was always for.
ADR-0037 Amendment 1; closes the tag-colour gap ADR-0040 flagged as OOS.
2026-05-31 22:02:12 +00:00
claude@clouddev1 6d8c9eea36 feat: curated SQL function list — Tab completion (#15) + typing-time typo hint (#16)
Add src/dsl/sql_functions.rs (KNOWN_SQL_FUNCTIONS) as the shared source
of truth at sql_expr_ident slots:

- #15: offer the functions as Tab candidates under a new
  CandidateKind::Function + ninth Theme colour tok_function (blue,
  distinct from keyword/identifier/type).
- #16: restore the column-typo flag the #6 fix had dropped wholesale —
  invalid_ident_at_cursor now bails only when the partial prefix-matches
  a known function, else falls through to the schema-column check.

A column named like a function (e.g. `count`) is deduped (column wins).
`cast` is excluded — CAST(x AS type) is not a plain-call shape.
The no-validation-allowlist posture stands: the list drives completion +
the typo hint only, never parse-time acceptance.

Docs: ADR-0022 Amendment 6, ADR-0031 status note, README index,
requirements I3/I4 + refreshed test baseline.
2026-05-31 11:49:10 +00:00
claude@clouddev1 8311de44a8 feat: replace the [ok] summary line with a ✓/✗ echo marker
An audit of the command surface found the `[ok] <verb> <subject>`
summary line duplicated the echo line above it everywhere; its only
unique signal was success-vs-error. Retire it: a command's echo line
now resolves from `running: <input>` to `<input> ✓` / `<input> ✗`
on completion, and the symmetric `"<verb> <subject>" failed:` prefix
is dropped (only the reason remains). Content lines (row counts,
structure, plan tree, teaching echo) are unchanged.

Echo lines carry an EchoStatus; executed commands push Pending and
resolve the oldest-pending echo on their result event (FIFO worker —
correct under interleaving). Parse-time and pre-flight rejections are
not executed and keep their running: + caret rendering. App-command
[ok] lines (rebuild/export/replay) are payload-bearing and untouched.
ADR-0040.
2026-05-30 21:38:48 +00:00
claude@clouddev1 f62cccec55 feat: support explain over advanced-mode SQL queries
explain now wraps the advanced SQL commands — select, with (CTE),
insert, update, delete — in addition to the DSL show data/update/
delete it already covered, rendering through the same plan tree
(ADR-0039, closing the ADR-0030 OOS-2 gap).

Implemented as a second Advanced `explain` CommandNode under the
shared entry word, reusing the established shared-word dispatch
(SQL-first, DSL-fallback) rather than new grammar machinery.
build_explain_sql slices the inner SQL off the source and reuses the
existing SQL builders; do_explain_plan runs EXPLAIN QUERY PLAN over
the carried text verbatim (never executes, so safe for destructive
verbs). Advanced explain update/delete now route through SQL with an
identical plan; DSL-explain tests pinned to simple mode. Help and
usage text now list the advanced explain forms.
2026-05-30 18:44:05 +00:00
claude@clouddev1 f7ca288fe1 fix: grow the hint panel for long prose hints
A long prose hint (insert field-value hints, the parse.usage.*
synopses) wrapped but was clipped by the fixed one-row Hint panel,
hiding the most useful tail. The candidate list already scrolled
horizontally, so only prose was affected.

Pre-wrap the prose body and size the Hint panel to the wrapped line
count: one row by default, growing to a 3-row cap and reclaiming the
space when short, with an ellipsis backstop on the last row. Also
shorten the 299-char create-table usage synopsis to a terse one-liner
(the full grammar remains under `help`). ADR-0022 Amendment 5.
2026-05-30 09:02:12 +00:00
claude@clouddev1 d20f765325 feat: give column data types a dedicated syntax-highlight colour
Both Node::Ident and Word carried a highlight_override field, and
both were dead — the walker driver discarded the Ident's and
walk_word hardcoded Keyword. So column types (int, serial, …)
rendered identically to table/column names.

Wire both overrides through, and add a dedicated HighlightClass::Type
with its own theme colour (tok_type), distinct from keyword-purple
and identifier-teal. The three type Ident slots opt in, so canonical
types and the advanced-mode single-word SQL aliases (float, varchar,
…) render as types; the two-word `double precision` alias opts in via
a new Word::type_keyword constructor. ADR-0022 Amendment 4.
2026-05-29 22:07:18 +00:00
claude@clouddev1 10e5197c19 feat: bring simple-mode insert arity diagnostics to parity with advanced
A wrong-count simple-mode insert now shows the friendly per-column arity
message at typing time (instead of a bare "expected `,`/`)`") and is
blocked from dispatch at submit — unifying simple and advanced mode onto
the one ADR-0027 model (structural parse + ERROR diagnostic), where they
had diverged.

Grammar: a simple-mode-only arity gate (dsl_insert_value_list) routes a
wrong-count DSL insert tuple to the type-blind fallback so it matches
structurally and the per-tuple arity diagnostic fires. The gate is gated
to simple mode, so advanced behaviour is unchanged. count_tuple_values
and the target-column selection (insert_target_columns) are now shared
by both grammars.

Diagnostic: dml_insert_arity_diagnostics is mode-aware — advanced Form B
expects all columns; simple Form B/C expects the user-fillable columns
(serial/shortid auto-fill). It counts the DSL Form A role and scans the
keyword-less Form C tuple. New catalog keys name the fillable/auto split
and the all-auto-table case.

Submit: a wrong-count DSL insert now parses Ok + carries the ERROR
diagnostic, so a unified Ok-arm pre-flight (dsl_insert_count_mismatch_notes)
blocks dispatch and teaches; the previous Err-arm note retires.
advanced_alternative_note's gate now reads the validity verdict so it
still fires for the parse-Ok-with-error shape.

Docs: ADR-0036 Amendment 2 (+ README index) and requirements.md H1a.
2026-05-29 20:45:21 +00:00
claude@clouddev1 fa5d0dc6da fix: insert VALUES between-values hint points at comma not close-paren
The ambient-hint fallback in ambient_hint_core_in_mode parsed
schemalessly, so the type-blind grammar closed an `insert … values
(…)` tuple after the first value and the "Next:" hint pointed at `)`.
With a schema available the walk knows the remaining columns and the
correct next token is `,`. Parse the fallback with the schema cache so
the expected-token prose matches the rest of the (already
schema-aware) hint ladder.

Also corrects wrong-arity closed tuples where the schemaless parse
accepted the input and the hint said "submit with Enter" for a command
the schema-aware parse rejects — the hint now surfaces the accurate
error. Three typing-surface snapshots updated to match.

Docs: ADR-0022 Amendment 3 (+ README index) records the schema-aware
fallback; requirements.md H1a cites the hint-accuracy improvement.
2026-05-29 10:22:57 +00:00
claude@clouddev1 c12ed1da9a fix: INSERT Form B value-count UX (ADR-0033 Amendment 5)
Three layered fixes for advanced/simple-mode positional INSERT
value-count mismatches (e.g. `insert into T values (...)` with the
wrong number of values for T's column count), plus ADR-0033
Amendment 5 recording the gate refinement.

Walker diagnostic (dml_insert_arity_diagnostics): the function's own
doc-comment recorded the no-column-list (Form B) case as deferred.
This commit closes that gap. Form B mismatches now emit a new
diagnostic.insert_arity_mismatch_form_b ERROR per offending tuple,
keyed off the target table's column count from the schema cache. The
[ERR] validity indicator (ADR-0027) lights up at typing time for the
reported scenario, no longer needing a submit.

Cross-mode pointer gate (advanced_alternative_note): refactored from
a hand-rolled Form B count check to a single input_verdict_in_mode(
input, schema, Mode::Advanced) call. The pointer fires only when the
verdict is None — the ADR-0027 sense of "valid". Any future static
check added to the verdict pipeline participates automatically; no
per-feature maintenance.

Teaching notes for the value-count cases users can hit before the
indicator turns red:
  - simple-mode submit: insert.form_b_extra_values_note covers under-,
    in-window, and over-supply against the Form B contract; suppressed
    when the cross-mode pointer fires (to avoid parallel advice).
  - advanced-mode dispatch pre-flight:
    insert.form_b_positional_count_mismatch_note catches a submitted
    mismatch with a teaching message before the engine produces its
    raw NOT-NULL / type error.

The advanced_mode.also_valid_sql pointer wording was reworked to
"trying to write SQL? switch with `mode advanced`, or prefix `:` to
run once". One insta snapshot regenerated.

ADR-0033 Amendment 5 records the gate change: Amendment 3's "would
parse in advanced mode" now reads as "valid in advanced mode" in the
explicit verdict-is-None sense, with the precise definition spelled
out so future readers can't drift back to the syntactic-only reading.
ADR-0000 index entry updated; docs/requirements.md H1a citation added
listing the three new pedagogical strings.

Tests added (8): four walker arity tests (under-supply, over-supply,
match, unknown-table); two app-level teaching-note tests for the
sibling cases (under-supply, over-supply beyond total); one pointer-
gate unit test pinning the bug-case suppression; one gate-precedence
test ensuring only one advice line per error. Existing
simple_mode_submit_of_sql_construct_appends_advanced_pointer updated
to use a known schema (the new validity gate requires it).

Full suite: 2031 passed, 0 failed, 0 unexpected skips. Clippy clean.
2026-05-28 16:38:33 +00:00
claude@clouddev1 63b2927f10 docs: session handoff 49 — ADR-0038 done, M4 + ADR statuses in sync 2026-05-28 12:24:07 +00:00