40de389bcb
Brings website up to date with main (18 commits): H1a parse-error pedagogy, V5/H3/V5a show+help commands, ADR-0043 compound-PK FK, handoffs 58-59, and the GitHub->Gitea doc scrub (Cargo.toml repository, CLAUDE.md, ADR-0001 amendment, requirements). Conflict: docs/adr/README.md. main and website had each created an ADR-0042 (main: H1a parse-error pedagogy; website: public website & docs site). Renumbered the website ADR to 0044 (next free after main's 0042/0043) and updated all references (ADR file, plan file, STYLE.md, astro.config.mjs, README index). Website build verified green.
60 KiB
60 KiB
Architecture Decision Records
This directory contains the project's ADRs, recorded per ADR-0000.
Index
- ADR-0000 — Record architecture decisions
- ADR-0001 — Language and TUI framework — Amendment 1 (2026-06-09): after the GitHub→Gitea migration (
git.lazyeval.net), the prebuilt-binary distribution channel named in the Decision ("GitHub releases") is reopened as an undecided choice, to be settled by a future distribution ADR; package-manager channels unaffected - ADR-0002 — Database engine
- ADR-0003 — Input modes and command dispatch — the persistent
Simple/Advancedmode and the:one-shot escape. The startup mode is no longer alwayssimple: it is restored from the project's stored mode and overridable with--mode(see ADR-0015 Amendment 1, issue #14). The app-command registry gainscopy(ADR-0041, issue #11) - ADR-0004 — Project file format
- ADR-0005 — Column type vocabulary
- ADR-0006 — Undo snapshots and replay log — Accepted. The replay/journal half (U3/U4) shipped via ADR-0034; the undo/snapshot half (U1/U2) is settled by Amendment 1 (2026-05-24) and implemented 2026-05-24 (plan:
docs/plans/20260524-adr-0006-undo-snapshots.md; ring insrc/undo.rs, worker hook insrc/db.rs). Amendment 1 supersedes the original "snapshots only before destructive operations" model: a snapshot is taken before every data/schema mutation (DSL + SQL) for familiar single-step (Ctrl-Z) undo — so the confirmation collapses to naming the one command being undone (no db-diff). Snapshot is a hybrid whole-project copy — database via the online backup API plusproject.yaml/data/*.csvas files — reconciling this ADR with ADR-0015's "text is authoritative, db is derived"; undo restores all three directly. Staged before the mutation's transaction, finalised after the db commit (preserves ADR-0015 §6 commit-db-last); rolled-back ops leave no snapshot. Persisted ring under.snapshots/, N = 50 (raised from 10), git-ignored + export-excluded + temp-cleanup-aware.redosupported, redo stack discarded on new work. Batch ops record one undo step (replay+ future batch via a Begin/EndBatch worker primitive);importis outside undo (it switches projects per ADR-0015 §11, leaving the current project untouched). A--no-undoCLI flag disables snapshotting (hardware escape hatch). Adds thebackupfeature torusqlite - ADR-0007 — Sharing and export
- ADR-0008 — Testing approach
- ADR-0009 — DSL command syntax conventions
- ADR-0010 — Database access via a dedicated worker thread
- ADR-0011 — Foreign-key column type compatibility
- ADR-0012 — Internal metadata for user-facing column types
- ADR-0013 — Relationships, naming, and the rebuild-table strategy
- ADR-0014 — Data operations, value literals, and the auto-show pattern
- ADR-0015 — Project storage runtime — Amendment 1 (2026-05-31, issue #14): the input mode is per-project state in
project.yaml(a new optionalmode:key underproject:, alongsidecreated_at), restored on every open and persisted as it changes — so a teacher can ship a project that opens in advanced mode, and a learner's last-used mode is restored per project. Mode is live UI state, not schema (rebuildignores it) and not stored in the database: the persistence handle carries the current mode and the worker stamps it intoproject.yamlon every write, so a later command rewrites the live value rather than clobbering it (no preserve-the-old-value dance needed). Backward-compatible optional field (pre-#14 files default tosimple, no migration). New CLI flag--mode simple|advanced, precedence--mode> stored >simple; combines with--resume. Mid-sessionmodechanges persist viaAction::PersistMode→Database::set_mode(immediate, crash-safe), and the mode is persisted on unload (quit + project switch) so the mode you leave a project in is always restored — deterministic, not selectively dependent on whether you ran a DDL (rejected as confusing) nor rewritingproject.yamlon every read command (rejected for load-picker mtime churn). Switches save the outgoing project's mode, then restore the incoming project's stored mode via theProjectSwitchedevent - ADR-0016 — Pretty table rendering for data and structure views
- ADR-0017 — Column type-change compatibility
- ADR-0018 — Auto-fill contracts for
serialandshortidcolumns - ADR-0019 — Friendly error layer (H1) and i18n message catalog
- ADR-0020 — Tokenization layer for the DSL parser — Superseded by ADR-0024 (never implemented). Specified a
chumsky-over-tokens architecture (separate lexer,define_keywords!,&[Token]grammar). ADR-0024 adopted a scannerless hand-rolled walker and removedchumskyentirely; the lexer/keyword/token model here does not exist. Kept as institutional memory of the path not taken. - ADR-0021 — Parser-as-source-of-truth for H1a (per-command usage in parse errors) — Mechanism superseded by ADR-0024; H1a scope continued in ADR-0042. The intent (show the command's grammar at the point of error) shipped —
usage_idson eachCommandNode, theparse.usage.*templates, and theavailable_commandsfallback all exist — but via grammar nodes, not thechumskyUsageEntryregistry /parse.token.*keys described here (which were never built). - ADR-0022 — Ambient typing assistance: colour, hint panel, completion (I3 + I4) — Amendment 1 supersedes §12's simple-mode-only carve-out: the unified mode-aware walker (ADR-0030/0031/0032) now speaks SQL, so advanced-mode ambient assistance is re-enabled.
ambient_hint_in_mode+hint_resolution_at_input_in_mode+expected_for_hint_snapshotthreadMode;render_hint_panelcalls ambient for all modes (no more advanced-modeNone); the one-shot:sigil is stripped before the ambient walk. Fixes a live bug where advanced-mode SQL hinting/completion-preview were dead despite Phase 2 marking them green (validated at the engine layer, not the UI). Simple-mode gating, highlighting, and the §13 performance posture are unchanged; covered by an app-level render test plus ambient-layer regression locks; Amendment 2 reverses the handoff-14 keywords-first candidate ordering — schema identifiers (table/column/relationship names) now sort before keywords so a name the user would have to look up stays visible in the single-row, window-scrolled candidate line (keywords are learned over time; thetok_identifier/tok_keywordcolour split marks the boundary); shipped with awalk_repeatedfix that surfaces a list item's trailing optionals at a clean boundary (order by Name→asc/desc,select Name→as,create table … Code(text)→not/unique/default/check; the,separator deliberately not surfaced); records a deferred two-line hint box for growing lists; Amendment 3 makes the ambient-hint fallback rung schema-aware — Amendment 1's bottom-rungparse_command_in_modewas schemaless while every earlier rung was not, so between-values insert hints pointed at)(type-blind close) instead of,and wrong-arity closed tuples read "submit with Enter" for an input the schema-aware parse rejects (issue #2); now usesparse_command_with_schema_in_mode, no extra walk, with the friendly arity diagnostic still winning at its higher rung; Amendment 4 gives column types a dedicated highlight class — bothNode::Ident.highlight_overrideand theWord.highlight_overridefield were dead (driver destructured the former to_,walk_wordhardcodedKeyword); now both wired through, with a newHighlightClass::Type+ eighthThemefieldtok_type(a pink/deep-magenta distinct from both keyword purple and identifier teal) so types no longer render identically to identifiers (issue #8); the threeIdentSource::Typesslots opt in viaSome(Type)(advanced-mode single-word SQL aliases —float,varchar, … per ADR-0035 §3 — ride along for free), and the two-worddouble precisionalias opts in via the newWord::type_keywordconstructor so it matches its synonyms; Amendment 5 lets the hint panel grow for long prose hints — a fixed one-row panel clipped long field-value/usage hints past the first line (issue #12);resolve_hint_linesnow pre-wraps prose andrender_right_columnsizes the panel to the line count (1 row default, up toMAX_HINT_ROWS=3, reclaimed when short) with aclamp_wrappedellipsis backstop; the candidate list still scrolls horizontally on one row (Amendment 2's deferred two-line candidate box stays deferred); also shortens the 299-charparse.usage.sql_create_tablesynopsis to a terse one-liner (full grammar remains inhelp.ddl.sql_create_table); Amendment 6 adds a curated SQL function-name list (src/dsl/sql_functions.rs,KNOWN_SQL_FUNCTIONS— aggregates + common + broader scalars;castdeliberately excluded as itsCAST(x AS type)syntax isn't a plain-call shape) as the single source of truth shared by two consumers at thesql_expr_identslot (ADR-0031 §1): issue #15 offers the functions as Tab candidates under a newCandidateKind::Function+ ninthThemecolourtok_function(a blue distinct from keyword/identifier/type, parallel to Amendment 4'stok_type) so a learner discoverssum/upper/…; issue #16 restores the typing-time column-typo flag the issue-#6 fix had dropped wholesale at this slot —invalid_ident_at_cursornow bails only when the partial prefix-matches a known function, else falls through to the schema-column check, soselect Agxwarns again at typing time whileselect sumdoes not (the issue-#6 lockdown tests + the submit-timeunknown_columndiagnostic path are untouched, and the no-validation-allowlist posture stands); see ADR-0031's status note for the grammar-side anchor - ADR-0023 — Unified declarative grammar tree — direction (superseded for execution detail by ADR-0024)
- ADR-0024 — Unified grammar tree: execution plan — Accepted, the executable spec — implemented (Phases A–F; Phase F shipped "minimal",
parser.rsretained as the router — see the ADR's Phase F implementation note) - ADR-0025 — Indexes — Accepted (Amendment 1, 2026-05-25: UNIQUE indexes admitted on the advanced-mode surface via
CREATE UNIQUE INDEX— ADR-0035 §4d; theIndexSchema.uniqueflag round-trips throughproject.yamlwith no new metadata table since the engine reports uniqueness natively; simple-modeadd unique indexstays deferred),add index/drop index, persistence, rebuild-table preservation, and items-list display (C3index portion +S2) - ADR-0026 — Complex WHERE expressions — Accepted, stratified recursive expression grammar (
AND/OR/NOT, comparisons,LIKE,IS NULL,IN,BETWEEN) forupdate/delete/show datafilters;show datagainswhere+limit; adds theSubgrammarnode and a recursiveExprAST (C5a) - ADR-0027 — Input-field validity indicator — Accepted, a debounced
[ERR]/[WRN]marker at the input row's right edge, backed by a walker diagnostics-severity model (parse-outcome + schema-existence); advisory, never blocks submission (S6); Amendment 1 adds aLIKE-on-numeric-column WARNING - ADR-0028 — Query plans (
EXPLAIN QUERY PLAN) — Accepted, anexplainprefix command overshow data/update/delete; an annotated, span-styled plan tree; introduces theOutputLinestyled-runs mechanism (ADR-0016's deferred per-span styling) (QA1/QA2) - ADR-0029 — Column constraints (NOT NULL / UNIQUE / CHECK / DEFAULT) — Accepted, the four column-level constraints declared in the column-spec suffix (
create table/add column) and modified on existing columns viaadd constraint …/drop constraint …; a pre-flight dry-run guards populated columns;CHECKreuses the ADR-0026 expression grammar viaSubgrammar(C3) - ADR-0030 — Advanced mode: the standard-SQL surface — Accepted, SQL added as grammar within the unified grammar tree (ADR-0024), not a separate batch parser — so SQL gets the same completion / highlighting / hints / parse-errors as the DSL; mode gates the SQL forms; DDL routes through the typed
Commandexecutor (metadata + type vocabulary preserved), DML andSELECTexecute as validated SQL; engine-neutral posture, the DSL→SQL teaching echo; supersedes ADR-0001'ssqlparser-rsreservation; phased plan (Q1/Q2/Q4); §13 OOS-2 (EXPLAIN of advanced SQL) superseded by ADR-0039 - ADR-0031 — The SQL expression grammar — Accepted, the stratified SQL expression grammar fragment commissioned by ADR-0030 §3: a single precedence ladder (
OR/AND/NOT, the comparison/LIKE/IN/BETWEEN/IS NULLpredicate set, arithmetic incl.||, function calls,CASE) — the superset of ADR-0026's DSLWHEREgrammar, authored as a parallel fragment so simple mode is untouched; pure validation, builds no AST (consumers run/store SQL as text per ADR-0030 §4/§6); reuses ADR-0026'sSubgrammarrecursion + depth cap unchanged; subquery expressions and qualified column refs deferred to ADR-0030 Phase 2; status note (2026-05-30) records that ADR-0022 Amendment 6 layers a curated known-function list on thesql_expr_identslot (§1) for completion + the typing-time typo hint (issues #15/#16) — the grammar itself is unchanged, and the no-validation-allowlist posture stands - ADR-0032 — The full SQL
SELECTgrammar — Accepted, the Phase-2 grammar commissioned by ADR-0030 §3: fullSELECTwithINNER/LEFT/RIGHT/FULL OUTER/CROSSjoins,GROUP BY/HAVING, all four set ops (UNION/UNION ALL/INTERSECT/EXCEPT),WITHandWITH RECURSIVECTEs,LIMIT … OFFSET,DISTINCT,t.*, and bare-alias projection (lifting Phase-1 §4.2); additive extensions to ADR-0031'ssql_exprfor scalar subqueries,IN (SELECT …),[NOT] EXISTS, and qualified column refs (redeeming ADR-0031 §7 OOS-1/OOS-2); grammar-recursion viaSubgrammar(&SQL_SELECT_COMPOUND)reuses ADR-0026'sMAX_SUBGRAMMAR_DEPTH = 64cap unchanged; softens ADR-0030 §8's "ambient assistance comes for free" claim: completion scope needs newWalkContextaccumulators (afrom_scope_stackofScopeFrames holdingfrom_scope/cte_bindings/projection_aliases), a new walker node variantNode::ScopedSubgrammar(&Node)as the push/pop trigger (existingNode::Subgrammarunchanged so DSLExprandsql_exprrecursion are unaffected), qualified-prefix completion narrowing, body-projection-derived CTE column resolution (soSELECT *and explicit-projection CTE bodies both yield real column completion pastcte_alias.|), and a post-walk fixup pass that re-resolves projection-list identifier highlighting/validity onceFROMis parsed (the projection-before-FROM problem); classifies every Phase-2 validation case against ADR-0027's ERROR/WARNING guideline (§11): five newdiagnostic.*keys for parse-time-detectable cases (unknown qualifier, ambiguous column, projection-alias misplaced, CTE/compound arity mismatch) plus eightengine.*translation keys; a MatchedPath-walking predicate-warnings variant that closes the Phase-1 gap where SQLWHEREexpressions emitted noLIKE-on-numeric /= NULL/ type-mismatch warnings (ADR-0027 Amendment 1 finally extends to the SQL surface); adds a worker-side post-prepare type-resolution pass via engine column-origin metadata so bare column refs recover their playground type (partially lifting Phase-1 §4.5, the bool→0/1 case) —Cargo.tomlgainscolumn_metadatato rusqlite features (verified against pinned 0.39.0);__rdbms_*rejection extended to every new table-source slot; Amendment 1 narrows §12's resolution rule from a grammar-side structural classification to "trust the engine's column-origin metadata verbatim" after an empirical probe showed origin metadata follows through non-recursive CTEs, scalar subqueries, derived tables, set ops, and joins — the one structural exception is recursive CTE result columns, which return None and stay typeless; Amendment 2 records that §10.6's "rewrite the highlight class" prescription is realised via the two-pass schema-existence diagnostic + the renderer's diagnostic-overlay path (no separate per-byte rewrite step needed; no new HighlightClass variant), and that the projection-before-FROM completion narrowing has been improved by ansrc/completion.rslook-ahead probe when the leading walk'sfrom_scopeis empty but the full input parses - ADR-0033 — The full SQL DML grammar (
INSERT/UPDATE/DELETE) — Accepted (implemented + verified through sub-phase 3k, 2026-05-23; phase-exit reportdocs/handoff/20260523-phase-3-verification.md), the Phase-3 grammar commissioned by ADR-0030 §3: single- and multi-rowINSERT(incl.INSERT … SELECTrecursing through ADR-0032'sSQL_SELECT_COMPOUND),UPDATEwithSETassignment list,DELETE, all three optionally followed byRETURNING projection_list, plus fullON CONFLICT … DO NOTHING / DO UPDATEUPSERT on INSERT; fixes the DSL-vs-SQL dispatch architecture for shared entry words (insert/update/delete): SQL-first / DSL-fallback in Advanced mode via aChoice(SQL_shape, DSL_shape)per shape, gated by a new walker capabilityNode::Guard(fn)— a zero-byte-consumption gating node that fails the enclosing Seq with aValidationError; carriesCommand::SqlInsert/SqlUpdate/SqlDeletevariants anddo_sql_*worker handlers each of which knows the target table (for re-persistence) and thereturning: boolflag (for DataResult routing);shortidauto-fill mirrors the DSLdo_insertmechanism via worker post-fill; SQL DELETE produces the same per-relationship cascade summary the DSL DELETE does (ADR-0014 parity); three new walker diagnostics (insert_arity_mismatchERROR,auto_column_overriddenWARNING,not_null_missingWARNING) with positive + negative tests each; OOS list explicitly carves outDEFAULT VALUES(the project's planned seed feature), SQLite-specificOR REPLACE/OR IGNORE/OR ABORT/OR FAIL/OR ROLLBACKprefixes,UPDATE FROMmulti-table updates, and WITH-prefixed DML; theexcludedkeyword insideON CONFLICT DO UPDATEis a deliberate carve-out from ADR-0030 §7's engine-neutral posture (no standard-SQL UPSERT spelling exists that SQLite and PostgreSQL share); eleven phased sub-phases each with explicit exit gates + written DA gate, opening with the dispatch mechanism before any DML grammar lands; initial DA review recorded seven critiques that were resolved before status moved to Proposed; Amendment 1 supersedes §2's dispatch mechanism: the originally-chosenNode::Guard(fn)+Choice(SQL_shape, DSL_shape)was found during 3a to be unworkable as framed (any guard-in-Choicemechanism forces awalk_choicechange —walk_choiceonly falls through onNoMatch, so Simple-mode valid-DSL would wrongly surface "this is SQL", andwalk_seqtreats aNoMatchpastidx 0as a hardFailed, breaking Advanced-mode DSL fall-through); replaced by category-grouped, mode-aware dispatch inwalker::walk(eachREGISTRYentry taggedCommandCategory::{Simple, Advanced}, generalising the existing whole-commandis_advanced_onlygate), shared entry words carrying a node in both groups, noNode::Guardand nowalk_choice/walk_seqchange, advanced-mode completion SQL-first with DSL as a full-line fallback; Amendment 2 (sub-phase 3f) supersedes §7's cascade mechanism: the WHERE-injected per-child pre-count rested on a premise that was factually wrong about the DSL handler (which detects cascades by before/after row-count diffing inside a transaction, not byExpr-derived pre-count subqueries) and would have broken the §2 parity promise by reportingSET NULLthe DSL path doesn't; replaced by mirroringdo_delete's count-diff exactly (verbatim DELETE executes, child-count diff observes the cascade —ON DELETE CASCADErow removals only, SET NULL deferred for both paths to preserve parity), which shares the render-layer formatter for free viaCommandOutcome::Deleteand withdraws risk R2 (no WHERE-byte extraction, no N+1 subquery); Amendment 3 (sub-phase 3j) records the command-identity model and defers the execution-mode side-channel: a command is the typed outcome of a mode-rooted grammar path and its identity is intrinsic (Advanced mode tries SQL first, falls back to the Simple DSL command when no SQL branch matches a token, e.g.delete … --all-rows; noteupdate … --all-rowsdoes not fall back — the SQLSETexpression eats--all-rows, harmless since the engine treats it as a comment); Simple mode commits the DSL candidate for shared words so the real DSL error surfaces, and when that line would also run in advanced mode the rendering layer combines them — DSL error plus anadvanced_mode.also_valid_sqlpointer ("… (valid as SQL in advanced mode)") — keeping the actionable DSL fix while pointing at advanced mode; bare "this is SQL" is reserved for entry words with no DSL form (select/with); a fully-overlapping input (insert … values …) legitimately yields two distinct commands (Command::Inserttyped-AST vsCommand::SqlInsertvalidated-text) that do the same thing but execute differently (ADR-0030 §4), so each is tested in the mode that produces it; corrects the plan's 3j exit-gate premise that the DSL DML tests run in Simple mode (they callparse_command, which defaults to Advanced) — the real invariant is "Simple-mode behaviour unchanged, Advanced mode SQL-first, DSL grammar tested in Simple mode, both variants tested in their producing mode", with §6/§7 parity keeping the paths observably equivalent; and defers to its own future ADR the execution-time mode side-channel (three-wayMode: simple/advanced/advanced-one-shot threaded throughAction→worker, for mode-dependent output like echoing generated SQL) — today only the rendering side-channelOutputLine.mode_at_submissionexists, and the three-way distinction is not required for Phase 3 dispatch correctness; Amendment 4, 2026-05-27 (design agreed, pending impl): reverses Amendment 3'supdate … --all-rowscounter-example as a bug — surfaced by the ADR-0038 echo design. The walker has no--comment support (it lexes two minus operators) while the engine treats--as a comment, soupdate T set x=42 --all-rowswas silently parsed as the expression42 - -all - rowsover non-existent columnsall/rows(an ADR-0027 "flag-if-it-will-fail" case) and matchedSqlUpdate. Decision: the--all-rowssequence makes the SQLUPDATEshape fail, so dispatch falls back to the DSLUpdate { AllRows }— symmetry withdelete … --all-rows; no--comment feature introduced (trailing comments stay unsupported). Invertssql_dml_e2e.rs::e2e_update_all_rows_in_advanced_does_not_fall_back_to_dsl; mechanism settled test-first in the build; folded into the ADR-0038 effort (makesupdate … --all-rowsechoable); Amendment 5, 2026-05-28 (implemented + verified, user-confirmed):advanced_mode.also_valid_sql(the cross-mode pointer from Amendment 3) fires on validity, not just parse — "valid" meaninginput_verdict_in_mode(input, schema, Mode::Advanced) == Nonein the ADR-0027 sense (parse succeeds and no Warning/Error diagnostic from any pass). Surfaced by issue #1: a positionalINSERT INTO T VALUES (…)(no column list) with a value count that didn't match the target's column count parsed in advanced but failed at the engine, so the syntactic-only Amendment-3 gate promised a mode switch that wouldn't help. Closes the gap by (a) extendingdml_insert_arity_diagnostics(§8.1, previously Form A only — its own doc-comment deferred Form B) to also check the no-column-list form against the schema's column count, emitting a newdiagnostic.insert_arity_mismatch_form_bERROR per offending tuple, and (b) refactoringadvanced_alternative_noteto read the validity verdict instead of running its own bespoke check — any static diagnostic added to the pipeline in the future automatically participates in the pointer gate. Side benefit: the[ERR]validity indicator now lights up at typing time for the reported scenario, no longer needing a submit to learn the line is wrong. Tests pinned:insert_form_b_arity_mismatch_under_supply_fires/_over_supply_fires/_match_is_silent/_unknown_table_is_silent(walker);ambient_hint_omits_advanced_pointer_when_form_b_value_count_wouldnt_match(gate);simple_mode_submit_of_sql_construct_appends_advanced_pointer(pointer still fires for genuine SQL-only constructs against a known schema). Amendment 3's "would parse in advanced mode" should henceforth be read as a synonym for "valid in advanced mode" in this stricter sense; the user-confirmed behavioural change is exactly the issue #1 bug case (no other input flips its pointer state) - ADR-0034 —
history.logas a complete command journal; replay reads success-only — Accepted, resolves a three-way tension inhistory.log's roles found while implementing ADR-0033 3f: (1) the persistent log is success-only while the in-memory Up/Down recall ring records every submission (success or failure, "so users can recall and edit typo'd commands"), and the ring is re-seeded from the log on project open — so failed commands are recallable within a session but silently lost across sessions; (2) replay wants the state-building (successful) commands while recall wants everything typed, which one success-only file cannot serve; (3)replay history.lognever actually worked —run_replayparses each whole line through the DSL parser with no understanding of the<ts>|<status>|<source>record shape, so a real log fails on line 1, and no test ever fed the pipe format to replay (thereplay_history_log_records_subcommands_onlytest only checks what replay writes, never replays the log as input). Decision:history.logbecomes a complete journal — every submission recorded, taggedok/errvia the status field the format already reserved (ADR-0015 §5) — and each consumer filters: hydration reads all records (cross-session recall matches in-session), replay readsokonly (and learns the journal format, while still accepting bare-command.commandsscripts; detection by the leading timestamp+status prefix so a|inside a bare command isn't misread). Successful commands stay journalled transactionally by the worker; failed commands are journallederrbest-effort from the runtime/app error path (a parse failure never reaches the worker). Amends ADR-0006's "successfully executed" wording and ADR-0015 §5 ("status alwaysok") / §12 (hydration). Code deferred to two tracked test-first sub-tasks (journal-failures+filtering; replay-parses-journal-format); existing all-oklogs need no migration; implemented 2026-05-24 (plandocs/plans/20260524-adr-0034-history-journal.md); Amendment 1 (2026-05-24): replay filters out app-lifecycle commands — a workingreplay history.log(the §3 fix) exposed that the journal also recordssave as/load/new/export/import/rebuild/mode(which would panic the worker dispatch or abort the replay), so replay now re-applies only schema/data write commands and skips everyCommand::App+ nestedCommand::Replay; all skips continue (never abort — reversing the prior nested-replayrefusal, so a journal containing a once-runreplayneeds no hand-editing, and the infinite-loop footgun is closed by construction), with a[skip]warning onimportand nested-replayskips (their omission can leave replayed state incomplete) and silent skips for the rest;replay.error_nestedremoved,replay.skipped_import/replay.skipped_replayadded,ReplayCompletedcarrieswarnings - ADR-0035 — Advanced-mode SQL DDL — Accepted (design agreed 2026-05-24; validated end-to-end by sub-phases 4a/4a.2/4a.3/4b
CREATE TABLE(incl. foreign keys) + 4cDROP TABLE [IF EXISTS]+ 4dCREATE [UNIQUE] INDEX/DROP INDEX [IF EXISTS]+ 4eALTER TABLEadd/drop/rename column + 4fALTER TABLE … ALTER COLUMN TYPE+ 4gALTER TABLEadd/drop constraint + add FK + 4hALTER TABLE … RENAME TO+ 4i verification sweep (completion merge + simple/advanced completion colour + describe of table-level constraints + self-ref FK indicator + CREATE-TABLE help/usage), implemented 2026-05-25/26 — Phase 4 complete; Amendment 1, 2026-05-26: drop a composite UNIQUE via a derived, engine-neutralunique_<cols>name that reuses the existingDROP CONSTRAINT <name>grammar — no new syntax, no metadata, §4g anonymity intact;describeshows the name; dropping a UNIQUE-covered column now refuses with that name + the drop command), Phase 4 of the ADR-0030 roadmap (peer of 0031/0032/0033) and clarifies ADR-0030 §4. Advanced-modeCREATE/DROP/ALTER TABLE+CREATE/DROP INDEXget their own per-statement commands (SqlCreateTable/SqlAlterTable/SqlDropTable/SqlCreateIndex/SqlDropIndex), like DML'sSql*set — but unlike DML they execute structurally, not verbatim (raw execution would lose the playground's types, named relationships, andSTRICT; "verbatim" was a DML convenience, not a rule). Handlers reuse the low-level schema/metadata helpers where the operation matches simple mode and stand alone where the SQL surface is richer (clarity over forced refactoring); simple mode is untouched (additive). Dispatch:create/dropreuse ADR-0033 Amendment 1's category-grouped mode-aware dispatch (SQL-first, simple fallback);alteris a new advanced-only entry word. Full surface (no pre-emptive cuts,Q4):CREATE TABLEwith column + table constraints, single/compoundPRIMARY KEY, inline + table-levelFOREIGN KEY→ named relationships (one statement = one command = one undo step, ADR-0006);ALTER TABLEadd/drop/rename column,ALTER COLUMN TYPE, add/drop constraint, add FK,RENAME TO(advanced-only table rename — new low-level op renaming the table + its CSV + the relationship and table-CHECK metadata, closing the rename half ofC1);CREATE [UNIQUE] INDEX/DROP INDEX. Type slot accepts the ten playground keywords and standard-SQL aliases (integer→int,varchar→text,timestamp→datetime, …; length args accepted-and-ignored; no engine type names in/out — ADR-0030 §5).CHECK/DEFAULTreuse ADR-0031sql_expr. Pre-implementation/rundarefinements (2026-05-24, user-confirmed):CREATE TABLE/DROP TABLEadmitIF [NOT] EXISTS(no-op-that-succeeds-with-a-note — a near-universal cross-vendor idiom, reclassified into scope, not engine-specific);INTEGER PRIMARY KEYmaps to a plainintPK, not auto-increment (serialstays the sole auto-increment type). Column-type-conversion is unified (ADR-0017 engine, mode-appropriate policy): clean auto-converts and incompatible/own-type-static cases refuse in both modes, but a lossy change refuses-by-default in simple mode (--force-conversionopts in) while advanced mode performs it with a loss note and relies onundoas the safety net — no force flag, no dropping to simple mode (a payoff of shipping ADR-0006 first). OOS: views/triggers/txn-control/PRAGMA/etc. (ADR-0030 §3), the PostgresUSINGclause, and the DSL→SQL teaching echo (ADR-0030 Phase 5). Sub-phases 4a–4i, plus 4a.2 (per-columnCHECK/DEFAULTvia rawsql_exprtext —sql_expris validate-only, noExprAST — + compositeUNIQUE(a,b); no new internal table) and 4a.3 (table-level/multi-columnCHECK, landed via the new__rdbms_playground_table_checksmetadata table because SQLite has no PRAGMA for CHECK; the builder tells a table-level CHECK from a column-level one by element position) and 4b (foreign keys — inlineREFERENCES+ table-levelFOREIGN KEY→ ADR-0013 named relationships in the create transaction, one undo step; self-references + bareREFERENCES <parent>supported, user-confirmed) and 4c (DROP TABLE [IF EXISTS]→SqlDropTable, reusingdo_drop_table;IF EXISTSis a no-op-with-note viaDropOutcome::Skipped) and 4d (CREATE [UNIQUE] INDEX [IF NOT EXISTS] [<name>] ON <T> (cols)→SqlCreateIndexandDROP INDEX [IF EXISTS] <name>→SqlDropIndex, reusingdo_add_index/do_drop_index;CREATE UNIQUE INDEXadmitted — ADR-0025 Amendment 1 — via an additiveIndexSchema.uniqueflag that round-trips throughproject.yamland rebuild, with[unique]markers in the structure view + items panel, while simple-modeadd unique indexstays deferred;IF [NOT] EXISTSreuses the 4c skip path;create/dropeach gain a second advanced node, exercising the all-candidates dispatch) and 4e (ALTER TABLEadd/drop/rename column →SqlAlterTable;alteris a new advanced-only entry word, runtime-decomposed to the existingdo_add_column/do_drop_column/do_rename_column— no new worker layer;do_add_columnextended to consume rawdefault_sql/check_sqlso ADD COLUMN reaches CREATE-TABLE constraint parity; drop/rename refuse a column any CHECK references (table-level AND column-level, incl. a column's own self-check on rename) — the 4a.3 deferral, via a raw-CHECK-text tokenizer in the shared executors, so it guards both surfaces and fixes a latent rename-drift bug; SQL DROP COLUMN refuses an index-covered column with no--cascadespelling; the column executors +do_add_indexgained an internal-__rdbms_*-table guard — all user-confirmed) and 4f (ALTER TABLE … ALTER COLUMN TYPE→ a fourthAlterTableAction, runtime-decomposed to the existingchange_column_typewithChangeColumnMode::ForceConversion— which is the §7 advanced policy: lossy converts with a note (no force flag), incompatible + ADR-0017 static refusals (↔ blob, same-type,date ↔ datetime, non-int → serial) still refuse, whileint → serialis allowed (auto-fills nulls + UNIQUE, ADR-0018 §8 — the §7 "→serial refused" summary is looser than the code); the builder discriminates the fourth branch by thetypekeyword (unique — ADD COLUMN's type is an ident), the type slot reusesSQL_TYPE; the internal-__rdbms_*guard was folded intodo_change_column_type, closing the simplechange columnexposure too — user-confirmed) and 4g (ALTER TABLE … ADD [CONSTRAINT <name>] (CHECK | UNIQUE | FOREIGN KEY)+DROP CONSTRAINT <name>; ADD = CHECK + composite UNIQUE + FK, withADD PRIMARY KEYand a named UNIQUE refused — composite UNIQUE is anonymous in our model; each ADD reuses a low-level path (table-CHECK/UNIQUE rebuild with a dry-run guard; FK →add_relationship, bareREFERENCES <P>→ parent single-PK), DROP CONSTRAINT resolves the name to a table-CHECK then a child-side FK; named table-CHECKs round-trip via a nullablenamecolumn on__rdbms_playground_table_checks(rebuild-only arrival — pre-4g projects gain it onrebuild, a named add on an un-upgraded project is refused with a friendly "rebuild first" message) and aproject.yamlcheck_constraintsextension to an{expr, name}mapping (the bare-string form still reads); the internal-__rdbms_*guard was folded intodo_add_constraint/do_add_relationship, completing that guard class — all user-confirmed) and 4h (ALTER TABLE … RENAME TO— the one genuinely new low-level op,do_rename_table: a native engine rename plus one-transaction reconciliation of every metadata row naming the table (__rdbms_playground_columns, both ends of__rdbms_playground_relationships,__rdbms_playground_table_checks), the CSV file (the existing rewrite+delete path — no new persistence method), and CHECK text that qualifies a column with the old table name (T.age→U.age, a planning-/rundafinding — the engine rewrites the live CHECK but the stored text would drift and break a fresh rebuild;rewrite_check_table_qualifierkeeps them in step); grammar splits therenameverb into one branch with an inner Choice on a distinct second keyword (columnvsto), the new-name slot mirroring theCREATE TABLEname slot; refuses same-name / existing-target /__rdbms_*/ non-existent, with case-insensitive collision checks behind an engine-neutral pre-check (a finished-slice/rundafinding — the engine matches names case-insensitively); auto-named indexes and relationships keep their stale names (only table-name columns update — §6 scope); one undo step; advanced-only, closing the rename half ofC1— all user-confirmed) and 4i (the verification sweep that completes Phase 4: the shared-entry-word completion merge + the simple-vs-advanced completion colour-when-mixed with Both→Advanced→Simple block ordering;describeof table-level composite UNIQUE + table CHECK; the self-ref FK pre-submit indicator fix; and the CREATE-TABLE help/usage skeleton refresh). All of Phase 4 (4a–4i) is shipped. Each sub-phase has exit + DA gates; Amendment 2, 2026-05-27 (design agreed, pending impl): a standard-first dialect stance (refines ADR-0030's "standard SQL" posture — ISO spelling is canonical + echoed where one exists; a vendor shorthand may be accepted but isn't canonical; where ISO offers none, one documented vendor spelling is a deliberate extension) + anALTER COLUMNconstraint gap-fill surfaced by the ADR-0038 echo design: makes ISOALTER COLUMN … SET DATA TYPEthe canonical type-change verb withTYPEretained as a synonym (reverses §4f's "noSET DATA TYPE"), and addsSET/DROP DEFAULT(ISO) +SET/DROP NOT NULL(the one documented extension — ISO has no in-place NOT-NULL verb; PostgreSQL's chosen for being type-independent), all rebuild-backed via the existing ADR-0029do_add_constraint/do_drop_constraintexecutors (dry-run + internal-table guards free, no new worker layer), reaching simple↔advanced constraint-mod parity for NOT NULL + DEFAULT; the rebuild stays hidden (Category-1 engine detail, ADR-0038). Residual gap left open + flagged: dropping a column-level (anonymous) UNIQUE/CHECK (no portable name — same class as Am1's parallel gap), which ADR-0038's catalogue marks "no headline echo" - ADR-0036 — Value validation for advanced-mode DML — Accepted (design agreed +
/runda'd 2026-05-26; mechanism then deliberately narrowed from "bind literals via the DSL path" to surgical "validate-and-retain, execute verbatim" after the user resisted consolidating the modes and a concrete auto-fill difference confirmed even the single-row literal case isn't identical across modes; Phases 1–2 implemented 2026-05-26 —INSERT … VALUESandUPDATE … SETliteral validation + offending-value retention, capture-at-parse with no grammar change; Phase 3a implemented 2026-05-26 — live typed-slot hints + numeric-shape highlighting forUPDATE/UPSERTSET col = <literal>via a boundary-aware lookahead (Amendment 1 corrects this ADR's naive-Choicesketch); Phase 3b implemented 2026-05-27 — per-position typed slots forINSERT … VALUES(single/multi-row, Form A/B) via a new zero-widthNode::SetColumnprimitive + an arity-gating tuple lookahead that preserves the §8.1 arity diagnostic; fully implemented). Augments — does NOT supersede — ADR-0030 §4 / ADR-0033 §10: execution stays verbatim, ADR-0033 Amendment 3's two-command identity (InsertvsSqlInsert) stands. The problem (investigated 2026-05-26; characterization testsql_insert.rs::sql_dml_skips_app_level_value_validation_that_the_dsl_enforcesproves it): advanced-mode SQL DML gets none of the DSL's value feedback — a malformeddatelike2025/01/15is silently written, and the offending value is missing from constraint errors — because literal values are spliced into text and discarded (onlySTRICTstorage types check them). Fix (surgical): validate each literal value against its column type before the verbatim insert, and retain it for error reporting — sharing only the per-type validators (Value::bind_for_column/validate_date/shortid::validate), nothing else. No binding, no statement reconstruction, no auto-fill change, no command-identity collapse — because the two gaps are closed by validation + retention alone, and executing the user's own text is already safe. The literal set =NULL/boolean/string/signed-numeric; arithmetic/functions/subqueries/column-refs are expressions (skipped — the engine evaluates them).WHEREnot validated (it's an expression in general; motivation met byVALUES/SET);SELECT/INSERT … SELECT/RETURNING/ON CONFLICTneed no special handling since execution is untouched. Phased: Phase 1 capture-at-parse + validate + retain forINSERT … VALUES(no grammar change, no reparse — closes both proven gaps); Phase 2UPDATE … SETliterals; Phase 3 completion hinting/highlighting (the only part needing a grammar change — a typed-literal slot vssql_exprreusing the DSLTypedValueSlots atdata.rs:141/189/269, discriminated by a boundary-aware lookahead not a naiveChoiceper Amendment 1; split into 3aSET(done) and 3bVALUES(pending); supersedes only Phase 1/2's literal detection, not the validation/enrichment on top). Non-goals: binding/reconstruction, collapsing command identity (Am3 stands), changingserial/shortidauto-fill (requirements.mdX4, a separate possible-bug), a structuralSELECT, a full SQL-expression AST. Embodiesrequirements.mdX5 (share a mechanic, not a command); the neutral "that value" safety net (ADR-0035 Amendment 1) stays correct for genuinely-computed values; Amendment 2 (issue #17, 2026-05-29) brings the §8.1 arity diagnostic to simple mode at parity with advanced: atuple_value_list-style gate (dsl_insert_value_list, simple-mode-gated so advanced is byte-for-byte unchanged) routes a wrong-count DSL insert tuple to the type-blind fallback so it matches and the friendly arity diagnostic fires (instead of a bareexpected,/)``);dml_insert_arity_diagnosticsis now mode-aware (advanced Form B = all columns, simple Form B/C = user-fillable since serial/shortid auto-fill, ADR-0018 §3), counts the DSL Form A role (insert_first_item) and the keyword-less Form C tuple, with new keysinsert_arity_mismatch_form_b_simple/_all_auto; a wrong-count DSL insert now parsesOk+ carries the ERROR diagnostic (the[ERR]verdict), with a unified Ok-arm submit pre-flight (dsl_insert_count_mismatch_notes) blocking dispatch + teaching (the issue #1 Err-arm note retires). Arity-UX parity only — no consolidation of value-handling/execution/auto-fill; the deliberate mode-distinctness stands - ADR-0037 — Execution-time mode side-channel (the three-way submission mode) — Accepted (design agreed 2026-05-27; channel implemented + verified end-to-end by its motivating consumer — ADR-0038's fully-shipped DSL → SQL teaching echo — across handoff-46
04c8e42(channel + first echo slice), handoff-4790479cb(full Bucket A),275c726(Bucket B resolved-name + multi-statement renderers),e6ad1ae(the category-3--dont-convertcaveat — gated on this channel too), and2aab457(the §4 styled-runs rendering polish)), redeems the follow-up deferred by ADR-0033 Amendment 3 (which named this ADR and its motivating consumer). Establishes the channel that lets a command know, at execution time, the effective mode it ran under — so execution can adjust output without touching identity (the motivating case: a DSL-formcreate tableechoing the equivalent SQL when run in advanced mode, silent in simple — ADR-0030 §10, realised by ADR-0038). Introduces a new per-submission enumSubmissionMode{ Simple, Advanced, AdvancedOneShot } — refining Amendment 3's "widenMode" sketch: the persistent inputModestays two-way (mode.rskeeps the one-shot:out of persistent state), and the three-way distinction lives on the per-submission channel where the transient:belongs. Resolved at submit time (Simple+:→AdvancedOneShot; Advanced:is a no-op), threaded throughAction::ExecuteDsl→ worker, output-only (no executor branches its effect on it — Amendment 3 forbids behavioural mode dependence). The worker builds the teaching echo (+ category-3 expansion data — ADR-0038) for DSL-form commands in advanced/one-shot mode and returns it; the App renders it beneath[ok]. Co-located with execution because the echo's harder forms (resolved auto-names, generatedshortids, conversion counts) are worker-computed facts, and gating on mode means the work happens only when shown. Alternatives weighed + rejected: wideningMode(conflates transient/persistent state); App-side gating with the worker always emitting echo data (computes unconditionally, doesn't generalise, re-opens the render-side framing ruled against). Scope: channel + resolution rule only — the renderer/catalogue/Value → SQL-literalare ADR-0038, theALTER COLUMNgap-fill is the ADR-0035 amendment. Amendment 1 (2026-05-31, issue #10): the output tag is colour-coded by message status (itsOutputKind), not the mode — narrowing the side-channel to its stated purpose (the mode tint lives on the echo tag alone).[system]tag → green,[error]tag → red,Echotag → mode tint (the sole exception); bodies go neutral (theme.fg), the error body bold (rustc-style: severity-coloured label, readable bold message — a wall of red prose is harder to read). Yields a status traffic-light (green = ok, red = error) matching the ADR-0040 ✓/✗ markers; supersedes issue #10's three sketched options and closes the tag-colour gap ADR-0040 had flagged as orthogonal - ADR-0038 — The DSL → SQL teaching echo — Accepted (design agreed 2026-05-27; fully implemented + verified — every catalogue row in §7 Buckets A + B and the §6 category-3 prose round-trips per line through the advanced walker per §1, and the §4 de-emphasised styled-runs polish is wired: handoff-46
04c8e42shipped the channel + create-table slice, handoff-4790479cbthe full Bucket A expansion + a skeleton contract-gap fix (dropped per-columnDEFAULT/CHECK),275c726the Bucket B resolved-name + multi-statement renderers (auto- and user-namedadd index, positionaldrop index,add/drop relationshipin both selector forms,drop column --cascade,add relationship --create-fk),e6ad1aethe last category-3 line — thechange column --dont-convertcaveat (shortid + transform notes were already surfaced via pre-existingclient_side.*keys), and2aab457the §4 styled-runs polish: a newOutputKind::TeachingEchocustom rendering branch (dimmedExecuting SQL:prefix + the SQL re-lexed in advanced mode for token-class colouring, same as the input echo) plus a newOutputStyleClass::Hintfor every cat-3 prose line — caveat and the existing illuminating notes, user-confirmed broader scope), realises ADR-0030 §10 (the teaching bridge) — the Phase-5 echo ADR-0035 §12 forward-referenced — building on ADR-0037 (theSubmissionModegate) and ADR-0035 Amendment 2 (standard-first dialect +ALTER COLUMNgap-fill). When a DSL-form command runs in advanced/one-shot mode, the worker emits the equivalent SQL beneath[ok]as a de-emphasised styledOutputLine(ADR-0028); the App renders it. Defining invariant — the copy-paste contract: every echoed line is runnable advanced-mode SQL (round-trip-tested: parse the echo → same-effect command; a planned "copy the echo" affordance depends on it). Type vocabulary = the playground's own keywords (serial/shortid/…, accepted byfrom_sql_name, decision (a)); statement shape = the standard-first dialect (Am2). DML uses substituted literals, not?(per-typeValue → SQL-literal, round-trip-safe;blobmoot — no literal syntax exists; auto-gen columns omitted to matchdo_insert+ X4). Firing reality — a DDL +show datafeature: in advanced modeinsert/update/delete … whereare SQL-first (Sql*= already SQL = nothing to echo per §10); only DSL-only spellings echo (DDL +show data+ thedelete/update … --all-rowsfall-throughs — the latter via ADR-0033 Amendment 4, a bug-fix folded in here that reverses Amendment 3'supdate … --all-rowsmisparse). Three-category framework for "what happens beyond the literal SQL": (1) engine-implementation-hiding (the rebuild, rowid PK, non-PKserialMAX+1) — never surfaced; (2) decomposable into advanced SQL (drop column --cascade,--create-fkrelationship) — shown as the runnable multi-line sequence, one statement per line; (3) playground type-behaviour with no SQL-expressible form (shortidgeneration — noshortid(); type-conversion transforms — noUSING) — de-emphasised prose expansion from the worker'sclient_side.*notes. Carries the full catalogue (Buckets A single-statement / B resolved-name + multi-line / C no-echo) mapping every DSL-form command to its echo. OOS: reverse SQL→DSL echo (§13 OOS-5), app commands /show table/explain/replay, ablobliteral, the column-level UNIQUE/CHECK drop residual (Bucket C until Am2's gap closes), and surfacing any category-1 engine internal - ADR-0039 — EXPLAIN over advanced-mode SQL queries — Accepted (2026-05-27), implemented 2026-05-30 (issue #7), supersedes ADR-0030 §13 OOS-2. Lets
explainwrap the advanced SQL commands (Select/SqlInsert/SqlUpdate/SqlDelete, pluswith/CTE which builds aSelect) in addition to the DSLShowData/Update/Deleteit already covers (ADR-0028), runningEXPLAIN QUERY PLANover the validated SQL text through the existing ADR-0028 span-styled plan tree (advanced mode only; DSLexplainunchanged in both modes). Implemented via a secondAdvancedexplainCommandNode (EXPLAIN_SQL) registered under the sharedexplainentry word — reusing the establishedinsert/update/deleteshared-word dispatch (decide: SQL-first / DSL-fallback), soexplain show data …and DSL-only--all-rowsstill reach the DSL node; rejected aDynamicSubgrammarmode-gate (its resolution cache key omitsmode).build_explain_sqlslices the inner SQL off the source (excludesexplain) and reuses the existing SQL builders;do_explain_planruns the carried text verbatim, no params. Advancedexplain update/deletenow route through SQL (identical plan, full SQL syntax); DSL-explain tests pinned to simple mode. Reframed OOS-2 as a deferred exclusion (per ADR-0000's out-of-scope discipline), not a rejection. OOS (deferred): EXPLAIN of DDL (no query plan exists) - ADR-0040 — A per-command completion marker (✓/✗) replaces the
[ok]summary line — Accepted 2026-05-30 (issue #9), amends ADR-0014 / ADR-0028 / ADR-0019 output conventions, builds on ADR-0037's mode-tagged echo. An audit of the whole command surface found the[ok] <verb> <subject>summary line duplicates the echo line above it (verb+subject) everywhere; its only unique contribution is the success-vs-error signal (andexplain selecteven rendered[ok] explainwith an empty subject post-ADR-0039). Decision: drop the[ok]line and the symmetric"…" failed:prefix; the echo line gains a trailing inline ✓ (green, success) / ✗ (red, failure) —running:becomes a pending state that resolves to<input> ✓/✗on completion (status set via the existingrfind(Echo)lookup). Content (row counts, structure, data, plan tree, teaching echo) unchanged. Scoped to the DSL/data/SQL family that has the redundant echo+[ok]pair; app-command[ok]lines (rebuild/export/now editing) are payload-bearing, have no echo to mark, and stay as-is.ok.summaryretired;dsl.failedreduced to the rendered reason. Broad but mechanical snapshot churn. OOS: app-command[ok]lines, the[WRN]validity indicator, and the tag colours (issue #10) - ADR-0041 — Copy the output panel to the system clipboard — Accepted 2026-06-02 (issue #11), amends ADR-0003's app-command registry (adds
copy/copy all/copy last). The friction it removes: filing a bug report meant terminal-selecting the output panel and fighting wrapping/borders. New app-level command (sigil-free, both modes):copy/copy allcopy the whole panel;copy lastcopies from the most recent echo line to the end. Mechanism — OSC 52 and native (arboard), always both, because OSC 52 acceptance is undetectable (no terminal ack), so a true "fall back when unsupported" can't be built: emit the OSC 52 escape (no new dep —base64+crossterm; works over SSH; tmux-passthrough-wrapped via$TMUX), then a best-effort native write whose failure is ignored (headless host — OSC 52 carried it); the two carry identical content. Format — plain text verbatim as rendered (tags,✓/✗, box-drawing) joined by\n, without viewport padding/wrapping; a drift-lock test pinsOutputLine::plain_texttorender_output_line.arboardadded--no-default-features(drops theimagecrate; X11-only on Linux —wayland-data-controldeliberately omitted as it ~doubles the dep tree and OSC 52 covers native-Wayland). Security: write-only, scans clean for arboard's tree (cargo audit / osv-scanner / grype), 1Password-maintained, minimal surface. OOS: Markdown export, selection/range, a keybinding, OSC 52 read,screenpassthrough - ADR-0042 — H1a parse-error pedagogy in the grammar-tree era — Accepted 2026-06-03. Continues H1a from ADR-0021 against the ADR-0024 grammar tree (ADR-0021's chumsky mechanism is dead). Records the baseline already shipped — per-command
usage:block (38parse.usage.*templates), available-commands fallback, structural "after…, expected …" wording, source-derived ident slot labels ("table name"/"column name"), curatedparse.custom.*near-miss messages, and the ADR-0027/0033/0036 schema-aware[ERR]diagnostics — so H1a is substantially delivered at the intent level. Defines the remaining work as (1) a verified per-command near-miss matrix (tests/typing_surface/+tests/it/parse_error_pedagogy.rs) as the definition of done, test-first; (2) friendlier literal expectation labels — optional prose glosses onWord/Punct/Flagpositions that add role context while always keeping the exact literal visible (e.g. "a filter clause:where …or--all-rows"); (3) advanced-mode SQL near-miss parity (RETURNING scope, CTE-arity positioning,CROSS JOIN … ON, INSERT…SELECT count) — in scope, kept distinct from ADR-0019 §OOS-2 which covers advanced-SQL engine-error sanitisation, a different layer. Catalog/anchor-phrase discipline (ADR-0019) preserved; no public API change. OOS: I3/I4, spell-correction, multi-error reporting, verbosity-gating the usage block - ADR-0043 — Compound-primary-key foreign-key references (T3) — Accepted + implemented 2026-06-09 (all four forks confirmed at the recommended option: full-PK matching, house-style uniform lists, parenthesized DSL syntax, bare-SQL-FK auto-expansion). Closes
requirements.mdT3[x]— the relationship model went list-based across six layers (single-column preserved, no migration), DSLfrom P.(a,b) to C.(x,y)+ SQLFOREIGN KEY (a,b) REFERENCES P(x,y)parse/execute/enforce, 12 tests intests/it/compound_fk.rs. Closes the open leg ofrequirements.mdT3: a foreign key that references a parent's compound primary key. A 2026-06-09 audit found single-column FK woven through ~15–20 sites (metadata table,RelationshipSchema,project.yamlRawEndpoint, both grammar surfaces, executor FK-DDL emission, per-column type-compat, display) — earns an ADR, not an inline build. Decision: reference the parent's full compound PK, matched positionally to an equal-length child column list, per-pairfk_target_typecompat (ADR-0011, element-wise); DSLfrom <P>.(a, b) to <C>.(x, y)(single form unchanged), SQLFOREIGN KEY (x, y) REFERENCES P(a, b)(extend the existing one-cap lists; bare table-level FK auto-expands to the parent PK when arities match). Storage — no migration (back-compat not required, user-confirmed 2026-06-09; no installed base): the relationship endpoint joins the list conventionproject.yamlalready uses —columns: [a, b]likeprimary_key: [id]and indexcolumns: [...](the endpoint was the lone scalarcolumn:holdout); the metadataTEXTcolumns are unchanged and store the list comma-joined (a,b; the bare name for single — safe because identifiers are[A-Za-z0-9_]+). No F3 migrator, no version bump; accepted trade-off is that a pre-changeproject.yamlwith relationships won't load (clean cutover). In-memory model goes list-based (Vec<String>) through all six layers; the enforced FK is the rebuilt child-table DDL (FOREIGN KEY (a,b) REFERENCES P(x,y)), one relationship = one undo step (ADR-0013). Genuine forks escalated: matching policy (full-PK vs subset), storage (house-style uniform lists vs normalized table), DSL syntax (parenthesized vs repeated-dotted), bare-SQL-FK auto-expansion. OOS: subset/non-PK (UNIQUE-targeted) FK references; any single-column behaviour change - ADR-0044 — Public website and documentation site — Accepted 2026-06-04 (originally drafted as ADR-0042 on the
websitebranch; renumbered on merge to avoid colliding with the H1a ADR-0042). The first public website: a marketing landing page plus the canonical user docs. Stack Astro 6 + Starlight + Tailwind v4 (chosen over SvelteKit + Tailwind for a docs-heavy + marketing site; interactive bits as Astro islands). Showcase demos are asciinema.castrecordings (scripted-input driver for paced, re-recordable sessions — nothistory.logreplay), reused inline in docs. The in-page WASM playground is deferred (OOS: deferred) behind a stableDemoseam, with the portable-core (dsl/app/ui, in-memoryrusqliteviaffi-sqlite-wasm-rs) vs native-edge (Tokio/worker-thread/crossterm/persistence/backup-API) boundary recorded for a future ADR + iteration plan. Portable static build (Vercel target, but host-agnostic); no CI yet; monorepo (website/). Docs cover the full supported feature set with "planned" callouts for the unshipped minority; two wording rules bind user-facing copy — no engine name (continues ADR-0002) and no "DSL" ("simple mode" / "advanced mode"). Install docs cover prebuilt binaries + package managers (D1–D3 track the release tooling). Plan:docs/plans/20260604-adr-0044-website.md