`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.
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}.
Dropping a PK column was already refused in both modes via the shared
do_drop_column guard; this adds end-to-end coverage on the advanced
ALTER surface (single-column + compound PK, asserting refusal for the
right reason) and documents the asymmetry that advanced-mode SQL can
create a PK-less table (SQLite's implicit rowid keys it) while simple
mode forbids it. See issue #19 comment for the full assessment.
ADR-0043 residual: a compound-FK violation's friendly error named only the
first child->parent column pair (the ADR-0019 facts model is single-column).
enrich_fk_violation now gathers all pairs of the matched relationship and
carries them comma-joined in the existing single-column facts slots, so the
headline reads e.g. "no parent row in `Region` has `country, code` = `7, 8`."
instead of naming just `country`.
Single-column behaviour is unchanged (a one-element join is the element
itself). No facts-model or catalog change -- the joined strings flow through
the existing `{parent_column}` / `{value}` placeholders.
Tests: enrichment facts (compound names every pair, single-column
regression) + translate rendering (headline names both columns). 2211 pass
/ 0 fail / 1 ignored. Clippy clean.
ADR-0043 D4 residual: an inline column-level FK (`<col> REFERENCES P(a,b)`)
is single-column by construction, so referencing a parent's compound PK
gave the generic arity error ("1 foreign-key column(s) on the child side,
but `P`'s key has 2..."). It now points the user at the table-level form:
"an inline column reference can only name one column ... Use the table-level
form instead: FOREIGN KEY (<columns>) REFERENCES P (a, b)".
- Adds `inline: bool` to SqlForeignKey, set by the grammar's single shared
builder consume_fk_reference (true for the inline path, false for the
table-level and ALTER paths).
- resolve_fk_parent_columns takes `inline` and tailors the arity-mismatch
message when an inline FK meets a compound key.
Tests: parse-layer (inline=true / table-level=false) + end-to-end worker
refusal wording. 2209 pass / 0 fail / 1 ignored. Clippy clean.
Completes the X1 full sweep started in a8ad0c6 (db.rs). Closes X1 -> [x].
- persistence/mod.rs: debug! on every yaml/CSV/history write -- the
silent-failure-prone disk paths (write_schema, write_table_data incl.
the empty->delete branch, append_history/_failure).
- runtime.rs: debug! on execute_command_typed dispatch (one per executed
command, complements the db.rs executor logs).
- app.rs: debug! on submit (route + submission mode), dispatch_app_command,
and the ADR-0044 diagram-vs-prose render-mode choice.
- dsl/parser.rs: trace! on parse begin/outcome at the parse_command_inner
choke point -- trace, not debug, because the live overlay/completion
re-parse per keystroke (hot path).
- logging.rs: documented level discipline (error/warn/info/debug/trace) so
the convention survives across sessions.
Levels verified end-to-end through the real worker thread + logging::init.
~75 -> 135 tracing sites total. Tests: 2207 pass / 0 fail / 1 ignored.
Clippy clean.
Instrument db.rs to the CLAUDE.md "log liberally" bar (X1). 26 -> 67
tracing sites:
- Entry-level debug! on all 34 do_* executors (DDL, DML, relationship,
index, read paths), matching the existing do_sql_delete/do_run_select
style -- so the route through delegating executors (e.g. add_column ->
add_constrained_column_via_rebuild) is visible in the log sequence.
- Decision-point logs: rebuild_table primitive (begin/commit; FK-check
failure and foreign_keys re-enable failure as warn), do_insert autofill
summary, do_delete cascade summary, do_create_table FK resolution.
- Worker lifecycle (start/exit) raised debug! -> info! so it shows at the
default level.
Levels per the X1 discipline: debug for per-command detail (off by
default, opt-in via RDBMS_PLAYGROUND_LOG=debug), info for lifecycle, warn
for fallbacks. Loops log summary counts, never per-row.
Tests: 2207 pass / 0 fail / 1 ignored (unchanged). Clippy clean.
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.
show table <T> and add/drop relationship echoes now render the focal
structure box plus a Relationships section of compact stacked
connector diagrams (child-left/parent-right, n…1, actions); incidental
DDL echoes keep the prose References:/Referenced by: form. Selected by
command in handle_dsl_success via the "relationship-relevant" reach.
- output_render.rs: render_structure refactored into section helpers
(box/prose/index/constraint), byte-identical output; new
render_structure_with_diagrams + compact-box rendering
- app.rs: handle_dsl_success routes ShowTable/Add/DropRelationship to
the diagram path, others to prose
- fixes: eager widths[1] index on compact (1-col) boxes; body-cell
padding under title-widening (name wider than columns)
Tests: unit + snapshot + integration; add-relationship echo test
updated to the diagram form. Full suite 2203 pass / 0 fail / 1 ignored;
clippy clean. V1 still [/] (compound routing + self-ref remain).
The first wired slice of relationship visualization (V1). `show
relationship <name>` now renders the relationship as two full
structure boxes joined by a width-jogging connector (child-left /
parent-right, n…1 cardinality, on delete/update actions), styled
App-side, with a vertical-stack fallback for narrow terminals.
- db.rs: RelationshipDiagramData + show_relationship worker path
(structured data: the relationship + both endpoint TableDescriptions)
- runtime.rs: named relationships route to the structured outcome
(boxed); other show <kind> forms stay prose
- app.rs/event.rs/ui.rs: DslShowRelationshipSucceeded rendered App-side;
new diagram OutputStyleClass variants; App::last_output_width from ui.rs
- output_render.rs: styled Seg layout engine (boxes, connector routing,
side-by-side + vertical), composing the ADR-0016 box primitives
Tests: 4 unit + 4 integration; full suite 2201 pass / 0 fail / 1 ignored;
clippy nursery clean. requirements.md V1 stays [/] (show table diagrams,
compound routing, DDL-echo wiring remain).
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.
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.
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).
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.
Fold the singular per-item forms into Command::ShowList { kind,
name: Option<String> } (name: Some = one item). Two grammar
branches reuse the relationship/index completion sources; worker
do_show_one renders a labelled detail block or a friendly
"No ... named X." line, reusing the V5 render path. Help +
parse-usage entries, two ADR-0042 near-miss rows, 5 integration
tests. Mark V5a [x] — V5's [<name>] clause now complete.
HELP node takes an optional single-word topic (BarePath);
AppCommand::Help { topic }. note_help_topic renders the help
block(s) of every command sharing that entry word (so `help
create` covers both create forms), plus `help types` and a
friendly "no help for X" pointer for unknown topics. Full help
gains a detail-hint footer. Catalogued help.detail_hint /
help.unknown_topic; parse-error matrix updated (help now takes a
topic, so the near-miss is the multi-word case). 9 integration
tests in tests/it/help_command.rs. Mark H3 [x].
Add the list-all show family as one Command::ShowList { kind }
variant. A read-only worker show_list formats count-headed lists
(reusing do_list_tables / read_all_relationships /
read_table_indexes, so it never drifts from the items panel);
internal __rdbms_* tables excluded. Help + parse-usage entries
added; 10 integration tests in tests/it/show_list.rs.
Mark V5 [x]. Split the singular show relationship/index <name>
detail forms (the [<name>] half) into a new tracked V5a [ ] item
rather than leaving them as an untracked footnote.
Audit of all 35 [ ] items vs source found ~46% mis-marked. Add a
[/] partial marker; promote 7 shipped-but-open items to [x]
(S1/S4/S5/I1a/I3/I4/C1) and 9 substantially-built ones to [/]
with gap notes (S3/A1/V1/V2/V5/T3/H3/DOC1/X1). Fix CLAUDE.md's
false "Tier 4 is wired" claim — no PTY tests exist.
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).
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.
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.
Close the three remaining ADR-0042 triage gaps, each test-first, and
lock the advanced-mode near-miss matrix.
G2 — bare `select` dumped the 14-item expression first-set. Collapse
it to "a projection: `*`, a column, or an expression" in the error
message only (parser::format_walker_error), detected by the joint
`distinct`+`all` quantifier signature unique to a projection start.
Render-only: completion/hints still expand the full set (typing-surface
matrix unchanged).
G3 — the usage block was mode-blind: advanced `create table` showed the
DSL `create table … with pk …` template. usage_key(s)_for_input gain
mode-aware `_in_mode` variants selecting candidates by CommandCategory;
render_usage_block and the typing-time ambient usage thread the
submission mode. Advanced `create` now shows both SQL forms. A fallback
covers shared SQL nodes (insert/update/delete) that declare no
usage_ids of their own — without it they regressed to the
available-commands fallback (caught by the new advanced matrix).
G4 — `with` borrowed `select`'s usage template; give it its own
parse.usage.with CTE template.
Tests: new near_miss_matrix_advanced_mode (12 SQL-surface cases incl.
the available-commands regression guard) + per-gap tests; removed the
temporary baseline_dump. Full suite green (lib 1578 / it 386 /
typing_surface_matrix 192); clippy clean.
Start the ADR-0042 §1 near-miss matrix: a table-driven
near_miss_matrix_simple_mode locking 26 simple-mode parse-error
renderings (every DSL entry word's bare/missing-clause cases plus the
mode arg-errors and the "this is SQL" rail), and an #[ignore]
baseline_dump capturing the full rendering for ongoing triage.
G1 fix: the bare `1` cardinality literal that opens `add 1:n
relationship …` rendered cryptically in expected-sets. Render it as
`1:n relationship` in error wording only (format_expectation) —
completion/hints still read the raw Expectation::Literal("1"), so the
candidate surface is unchanged. Updated the one anchor test.
Full suite green (lib 1578 / it 382 / typing_surface_matrix 192).
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)
Captures the post-handoff-56 arc: H1 (ADR-0019) verified already shipped
(not "partial"); build-hygiene fixes (incremental off, line-tables debug,
cargo-sweep) cutting target/ from 38 GB to ~1.6 GB; and consolidating 25
integration crates into one `it` binary (disk win, not the predicted
speed win). Flags the new test layout (add tests under tests/it/ + a mod
line) and points the next session at H1a (ADR-0021) as the queued job.
Each top-level tests/*.rs was its own crate → its own binary, each
statically linking the bundled engine + every dep. 26 of them, so an
edit to the lib relinked all 26. Moved the 25 standalone files into
tests/it/ under one tests/it/main.rs (the pattern typing_surface
already uses); cargo auto-detects it as the `it` target. End state: 2
integration-test binaries instead of 26.
Result: target/debug/deps 1.5 GB → 629 MB (-58%). Build time barely
moved (clean 22.9s→22.4s, lib-edit relink 13.3s→12.4s) — wall-clock is
dominated by compiling, not linking, so this is a disk win, not a speed
win (see docs/plans/20260602-test-consolidation.md). Tests unchanged at
2151/0/1; clippy clean; no fixups needed. typing_surface_matrix stays
its own already-consolidated binary.
Tradeoff: the 25 files now share one crate (a compile error fails the
whole `it` binary; module-scoped namespaces, no clashes) — negligible
for a solo project.
target/ had reached 38 GB, dominated by a 16 GB incremental cache
(~28 compilation units × every historical config, never evicted) and
~25 separate integration-test binaries each statically embedding the
bundled engine + full debug info.
[profile.dev] (inherited by the test profile):
- incremental = false: the cache earns little in a full-suite workflow
and never self-evicts; off, target/ stays bounded.
- debug = "line-tables-only": keeps file:line in panics/backtraces (we
debug via tracing logs) at a fraction of debug=2's size.
Net: a clean full build dropped from 38 GB to ~1.6 GB. Document the
rationale plus the cargo-sweep workflow (the GC cargo lacks) under a
new CLAUDE.md "Build hygiene" section; also drop the now-shipped H1
from CLAUDE's deferred list (H1a remains).
Verification found H1 (ADR-0019) fully implemented and tested: the
friendly::translate_error chokepoint is wired on the live failure path
(runtime + app + DbError::friendly_message), covers all five error
categories (UNIQUE, FOREIGN KEY both sides, NOT NULL, CHECK,
type-mismatch) with operation×kind×verbosity wording, the messages
verbosity command, and §6 row-pinpointing via runtime-resolved facts —
backed by 44 friendly unit tests + 12 full-stack friendly_enrichment
integration tests. The "partial / FK-only" notes were stale.
Mark requirements H1 done; fix the obsolete "diagnostic_table is
always None" comment in translate.rs (pinpointing landed in 431645a).
Remaining ADR-0019 scope (§9 i18n sweep, §OOS-2 advanced-SQL
sanitization, §OOS-3 messages persistence) stays deferred.
serde_yml (RUSTSEC-2025-0068) and its libyml backend
(RUSTSEC-2025-0067) are archived, unsound, and unmaintained with no
patched version. Swap to serde_norway, the maintained serde_yaml fork
on unsafe-libyaml-norway — a drop-in for our from_str / to_string /
Value usage across persistence, undo, and the catalog parser.
Clears both advisories (cargo audit / osv-scanner / grype all clean;
serde_yml + libyml gone from the tree). No behaviour change; full
suite 2151/0/1.
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.
#10 (output [error]/[system] tag-colour collision) and #14 (per-project
input-mode persist & restore) closed. Two ADR amendments authored.
Records the two /runda saves on #14 (persist semantics re-decided by the
user; missing integration test added red-first) and a detailed pickup
brief for #11 (clipboard — new dependency, security review + command
surface to escalate).
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.
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.
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.
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.
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.
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.
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.
The undo/redo confirmation dialog capped at 60 columns and wrapped
even a short insert on wide terminals, showed a lowercase "snapshot
taken", a raw ISO-8601 timestamp, and lowercase yes/no labels.
Grow the dialog to fit its longest line (bounded 34–100), capitalise
Snapshot/Yes/No, and render the snapshot timestamp in local time,
human-formatted (24 May 2026, 11:00) via a new chrono dependency
(clock feature only; English month names). Yes/No capitalisation also
applies to the rebuild-confirm dialog.
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.