# Session handoff — 2026-05-19 (21) Twenty-first handover. This session **finished ADR-0028** — query plans / `explain`. Steps 2–5 (planned in full in handoff-20 §3) are implemented, tested and committed; step 1 landed in the previous session. **ADR-0028 is complete**, and with it the handoff-16 design trio (ADR-0026 / ADR-0027 / ADR-0028) is fully implemented. ## State at handoff **Branch:** `main`. Working tree clean. **3 feature commits** this session (plus this handoff); all local — push asynchronously, not blocking. ``` docs: handoff 21 — ADR-0028 complete ae99276 explain: typing-surface matrix cells (ADR-0028 step 5) a7d459f explain: styled plan tree + annotation taxonomy (step 4) d17addd explain: `explain` command end to end (steps 2–3) c1fcf28 docs: handoff 20 — ADR-0028 step 1 done, 2-5 planned 03d8a09 ui: styled-output-line mechanism (ADR-0028 step 1) ``` **Tests:** **1172 passing, 0 failing, 1 ignored** (`cargo test`). The ignored test is the long-standing `` ```ignore `` doc-test in `src/friendly/mod.rs`. Typing-surface matrix: **174 cells** (was 161 — +13 for `explain`). **Clippy:** clean (`cargo clippy --all-targets -- -D warnings`, nursery group). ## §1. What ADR-0028 delivered The `explain` prefix command — `explain show data …`, `explain update …`, `explain delete from …` — captures a query's plan via `EXPLAIN QUERY PLAN` and renders it as an annotated, span-styled tree. `EXPLAIN QUERY PLAN` never executes the wrapped statement, so explaining a destructive `update` / `delete` is safe and changes nothing. - **Grammar** (`src/dsl/grammar/data.rs`): a new `EXPLAIN` `CommandNode` whose shape is a `Choice` over the three explainable query shapes, reached through `Subgrammar` — the inner command is parsed, completed and hinted exactly as it is standalone. `Command::Explain { query: Box }`. - **Worker** (`src/db.rs`): SQL construction split out of `do_query_data` / `do_update` / `do_delete` into `build_query_data_sql` / `build_update_sql` / `build_delete_sql`, so `EXPLAIN QUERY PLAN` runs the exact same statement. `Request::ExplainPlan` / `do_explain_plan` capture the plan; `QueryPlan` / `ExplainRow` carry it back. - **Display SQL**: the executed statement with `?N` params inlined as standard-SQL literals (`<>` for inequality, double-quoted idents, the implicit `ORDER BY ` that `limit` adds). - **Render** (`src/output_render.rs`): `render_explain_plan` draws the box-drawing tree; `PLAN_TAXONOMY` classifies each node's `detail` and only the category-bearing keyword run carries a semantic colour (efficient / expensive / automatic-index). An automatic-index node also gets the `← add an index?` advice tag. - **Catalog**: `parse.usage.explain` plus `help.data.explain` — `explain` appears in the in-app `help` listing. ## §2. Decisions / deviations from handoff-20's plan handoff-20 §3 was followed closely. The three gotchas it flagged all held — `static` wrappers (`EXPLAIN_SHOW_DATA` etc.) for the `Subgrammar` `&'static` requirement; the role-based `build_show_data` extraction; steps 2+3 combined into one commit because the `Command::Explain` variant breaks every exhaustive `match Command`. Two deliberate deviations: 1. **Display SQL via param-inlining, not a parallel `compile_operand`.** handoff-20 suggested a second literal-rendering variant of the WHERE compiler. Instead, `inline_params_for_display` (`src/db.rs`) takes the executed SQL and substitutes each `?N` with its bound literal in a single quote-aware scan. This is structurally guaranteed to match the executed statement and avoids a second expression compiler that could drift from the first. (`<>`, double-quoted idents and the implicit `ORDER BY` all come for free — they are already in the executed SQL.) 2. **`help_id: Some("data.explain")`, not `None`.** handoff-20 planned no help entry; at the user's request `explain` now has a `help.data.explain` catalog entry and appears in `help` like every other command. Other notes: - `render_explain_plan` returns `Vec` (styled). Commit A built the tree with plain lines; commit B enriched the *same* function with the taxonomy + styled runs — no duplicated tree-walking logic. - `explain` is **not** written to `history.log` (`do_explain_plan` takes no `source`) — it is a read-only diagnostic, and `EXPLAIN QUERY PLAN` does not execute. - The plan tree renders wholly in the neutral foreground colour (not the `System` green) so connectors / names stay neutral per ADR-0028 §6; only category keywords are coloured. Every plan line therefore carries a `styled_runs` payload, including the display-SQL line. ## §3. Test coverage added - **Parse** (`src/dsl/grammar/data.rs` `explain_tests`): each wrapped form parses to `Command::Explain`; `explain` of an incomplete inner command is the same parse error; `explain show table` is rejected (`explain` covers `show data` only). - **Worker** (`src/db.rs`): scan vs. index-search plans; `explain delete` / `explain update` leave data untouched; display SQL inlines literals, quotes idents, shows the implicit `ORDER BY`, writes `<>`; missing-table errors. - **Render** (`src/output_render.rs`): the taxonomy classes, the automatic-index tag, neutral fallback, a cyclic-parent guard. - **App** (`src/app.rs`): the `DslExplainSucceeded` handler renders the `[ok]` header, display SQL and tree. - **Typing-surface matrix**: 13 cells in `tests/typing_surface/explain.rs`. ## §4. What's next ADR-0028 closes the handoff-16 design trio. No feature is in flight. Open clusters, unchanged from handoff-16/17/18/19/20 (prioritisation is a **user decision — ask**): - Snapshot / undo / replay `U`-series (designed in ADR-0006). - Constraints `C3`; m:n convenience `C4`; modify-relationship `C3a`; column drop/rename/type-change grammar `C1` (the rebuild primitive exists). - Friendly-error layer `H1` (partial); strong syntax-help in parse errors `H1a`. - Session-log / Markdown export `V4` — would be the first real reuse of the `OutputLine` styled-runs mechanism beyond the plan renderer. - `SD1`; CI workflow `TT5`; readline shortcuts `I1b`; multi-line input `I1`; `TU1`. ## §5. How to take over 1. **Read this file**, then `CLAUDE.md` (working-style rules), then `docs/requirements.md` (per-item progress — `QA1` / `QA2` now ticked). 2. **Run `cargo test`** — 1172 passing, 0 failing, 1 ignored. 3. **Run `cargo clippy --all-targets -- -D warnings`** — clean. 4. Pick the next cluster *with the user* — §4 has no default. ### Note on the typing-surface matrix `tests/typing_surface/` is **174 cells**. The matrix-snapshot discipline from handoff-17/18/19/20 stands: a failing cell with *correct* new behaviour → update its snapshot; with *wrong* behaviour → the cell earned its keep. `cargo insta` is not installed on this machine — regenerate snapshots with `INSTA_UPDATE=always cargo test --test typing_surface_matrix ` and review the written `.snap` files before committing.