# Session handoff — 2026-05-28 (48)

Forty-eighth handover. **ADR-0038 is feature-complete except for the §4
styled-runs rendering polish.** Buckets A + B and the §6 category-3 prose
all ship, every catalogue row round-trips per line, docs are in sync,
and a /runda Devil's-Advocate pass landed cleanup. The session built
the renderer out from the handoff-46 walking skeleton through Phases
1, 2, and 3 in one go; if you continue, the polish is the only open
item. If you start a different feature, ADR-0038 won't surprise you.

## §1. State at handoff

**Branch:** `main`. **HEAD `5cb105b`.** **Tests: 2015 passing, 0 failing,
0 skipped, 1 ignored.** **Clippy: clean** (`--all-targets -D warnings`,
nursery).

Commits since handoff-47's `c60026c`:

```
5cb105b docs(adr): /runda DA cleanup — reflect Phases 1-3 done, pin Bucket C
e6ad1ae feat: DSL→SQL teaching echo — Phase 3 cat-3 caveat (ADR-0038)
275c726 feat: DSL→SQL teaching echo — Bucket B renderer (ADR-0038 Phase 2)
558cfae docs(adr): promote ADR-0037 + ADR-0038 to Accepted (Phase 1 shipped)
```

(Plus handoff-47 itself, `c60026c`, immediately prior.)

## §2. ADR status

| ADR | What | Build status |
|---|---|---|
| **ADR-0035 Am2** | standard-first dialect + `ALTER COLUMN` gap-fill | ✅ done (handoff-46 `338dc8a`) |
| **ADR-0033 Am4** | `update … --all-rows` falls back to DSL | ✅ done (handoff-46 `9a23e28`) |
| **ADR-0037** | `EffectiveMode` execution-time side-channel | ✅ **Accepted** — channel done (handoff-46 `04c8e42`), validated end-to-end by all four ADR-0038 commits |
| **ADR-0038** | the echo + full catalogue | ✅ **Accepted** — Phases 1-3 done (`90479cb` / `275c726` / `e6ad1ae`); **§4 styled-runs polish remains** |
| **ADR-0039** | EXPLAIN over advanced SQL | ⏸ deferred follow-up — separate feature |

ADR-0037 and ADR-0038 were promoted to **Accepted** with implementation
notes in `558cfae`; the /runda cleanup (`5cb105b`) refreshed both
entries to reflect every shipping commit.

## §3. What landed since handoff-47

### §3a. Phase 2 — Bucket B renderers (`275c726`, +1049 / −53 across 4 files)

Two new echo-build paths the handoff-46 ⚠️ gotcha foreshadowed:

- **`collect_echo_lookups`** (pre-execution, runtime) — resolves names
  the dropped thing or not-yet-created column would erase post-
  execution: positional `drop index` (describe the table, find the
  index by exact column-set match), `drop relationship` in both
  selector forms (`Endpoints` via child describe + matching by
  endpoints; `Named` via a small `list_tables` scan acceptable for
  teaching-playground schemas), and the `--create-fk` pre-state
  (whether the child column already existed + the parent's PK type to
  derive the new column type via `Type::fk_target_type` — `serial →
  int`, `shortid → text`).
- **`build_schema_echo`** (post-execution, runtime) — subsumes the
  Bucket A pure-`Command` schema cases (`create table`, `rename
  column`, `add`/`drop constraint` — delegates to `command_to_sql`)
  *and* renders Bucket B from the description (`add index` resolved
  from `desc.indexes` by column set; `add relationship` from the
  parent's `inbound_relationships` by endpoints) and the lookups.

Plus a **payload refactor** — `Option<String>` → `Option<Vec<String>>`
on all 7 success events + arms + render path, motivated by the
category-2 multi-statement echoes (`drop column --cascade`, `add
relationship --create-fk`). Each catalogue row's pure renderer (and
its round-trip) lives in `src/echo.rs`; the runtime composes them.

The `DropColumn` arm gained `build_drop_column_cascade_echo`, which
reads `DropColumnResult::dropped_indexes` to emit the multi-line
cascade echo; non-cascade falls through to the pre-execution Bucket A
echo unchanged.

### §3b. Phase 3 — category-3 prose (`e6ad1ae`, +130 / −8 across 5 files)

Phase 3 turned out to be small: the existing `client_side.*` notes
already covered **shortid generation** (`auto_fill_add_shortid`,
`auto_fill_transition`) and **type-conversion transforms**
(`transformed`, `transformed_lossy`), and they already render in the
right position (between the echo and the structure). Only the **`change
column … --dont-convert` caveat** was actually missing — the one
Bucket A caveat (every other category-3 line is *illuminating*).

Wiring: a new `dont_convert_caveat: bool` on `DslChangeColumnSucceeded`,
set by the runtime when `submission_mode.is_advanced() && matches!(cmd,
ChangeColumnType { mode: DontConvert, .. })`, rendered by
`handle_dsl_change_column_success` between the client-side notes and
the structure. Gated on advanced mode because the caveat references
"the line above" — the echo, which only fires in advanced mode. New
i18n key `client_side.dont_convert_caveat` (registered in
`keys::KEYS_AND_PLACEHOLDERS`).

### §3c. /runda DA cleanup (`5cb105b`, +128 / −32 across 4 files)

Devil's-Advocate audit at the end of the session caught three doc-drift
bugs the per-phase commits left behind (requirements.md M4 and both
ADR-0038 Status block + README index entry still said "Phase 2 / Phase
3 remain") and one test gap — Bucket C cases (`show table`, `explain`,
`replay`, every `Command::App` variant) flowed through the catch-all
`_ => None` without explicit no-echo coverage. The new
`bucket_c_no_echo_commands_all_return_none` test pins all four; a
future renderer arm at the wrong place can no longer silently leak.

## §4. The architecture in shape now

Two echo construction paths now run in `spawn_dsl_dispatch`, side by
side:

- **Pre-execution** (`echo::echo_for` → `echo::command_to_sql`) — the
  outer `echo` var, used by the **non-Schema** arms (`Update`,
  `Delete`, `AddColumn`, `DropColumn` non-cascade, `ChangeColumn`).
  Pure function of the `Command`. Returns `Option<Vec<String>>`
  (single-element for the Bucket A single-line cases).
- **Post-execution** — three builders, each in the arm matching its
  outcome:
  - `build_schema_echo(command, mode, description, lookups)` for
    `Schema` outcomes (subsumes Bucket A pure-Command schema +
    Bucket B `add index` / `drop index` / `add`/`drop relationship`).
  - `build_show_data_echo(database, command, mode)` for `Query`
    outcomes (only `show data` echoes; the runtime describes the table
    when the query is limited to source the `ORDER BY` PK).
  - `build_drop_column_cascade_echo(command, mode, result)` for the
    `DropColumn` arm when `cascade: true` (reads
    `DropColumnResult::dropped_indexes`); non-cascade falls through to
    the pre-execution echo.

The pre-execution lookup helper `collect_echo_lookups` runs once at the
top of the dispatch, captures whatever drops or `--create-fk` pre-state
will need post-execution, and passes through to `build_schema_echo`.
Replay still bypasses the whole spawn (`run_replay` calls
`execute_command_typed` directly) so no echo fires there — per
ADR-0038 §3.

Rendering: `note_ok_summary` loops over `pending_echo: Vec<String>`,
emitting one `Executing SQL: <line>` per entry via `note_system`.
Plain `[system]` lines today — the polish replaces this with
ADR-0028 styled runs.

## §5. The one open item — §4 styled-runs polish

ADR-0038 §4: "*The App renders it as one or more **de-emphasised**
`OutputLine`s beneath the `[ok]` summary, using the ADR-0028 styled-
runs mechanism (a dimmed `Executing SQL:` prefix; the SQL in a code-ish
run). One statement per line (§6 category 2).*"

Today the echo + the DontConvert caveat surface as plain `note_system`
lines (a single style). The polish needs:

- Read **ADR-0028** for the styled-runs `OutputLine` shape — there is
  already a mechanism (used by the explain plan tree rendering — see
  `output_render::render_explain_plan` for the established pattern of
  pushing pre-built `OutputLine`s via `push_output`).
- Replace `note_system(crate::t!("echo.executing_sql", sql = line))` in
  `note_ok_summary` with a styled `OutputLine`: dimmed `Executing
  SQL:` prefix run + a code-ish run for the SQL itself. Decide the
  exact style (italic? dim? distinct fg colour?) consistent with
  ADR-0028's existing styled-runs surface; check both `theme::light()`
  and `theme::dark()` for legibility.
- The DontConvert caveat is also a category-3 prose line — apply the
  same de-emphasised treatment (or a complementary one — caveat could
  warrant a `WARNING`/yellow accent, since it flags non-equivalence).
- Multi-statement echoes: each line in the Vec becomes its own styled
  `OutputLine`. Today the rendering already emits one line per entry;
  the polish is per-line styling, not per-line splitting.

Tests will need adjustment: the App-level tests (`bucket_a_success_*`,
`bucket_b_multi_line_*`, `change_column_dont_convert_renders_*`)
currently assert `texts[…].contains("Executing SQL: …")` from
`note_system` output. After the polish, the same content will live in
styled-runs — the tests should still find the substring within the
output line text but may need to look at `OutputLine` styled-run
content rather than the flat `text` field. Decide style-vs-text
assertion shape early.

The work is rendering-only — no Command-level changes, no event-shape
changes, no worker changes. Estimated scope: small. The biggest care
point is consistency with ADR-0028's existing styled-runs usage.

## §6. Catalogue cheat-sheet (authoritative: ADR-0038 §7)

Unchanged since handoff-47. **Every Bucket A and Bucket B row is now
implemented and round-tripped** (`echo::tests::*` + the runtime
end-to-end tests `bucket_b_resolved_name_echoes_against_real_worker` +
`bucket_b_multi_statement_echoes_against_real_worker`). Bucket C
(no-echo) is explicitly pinned by
`bucket_c_no_echo_commands_all_return_none`. Category-3 lines flow
through the existing `client_side.*` infrastructure plus the new
`dont_convert_caveat`.

## §7. Known follow-ups outside the ADR-0038 scope

These are **not** the styled-runs polish — they are separate items
the /runda surfaced or that emerged during the session, captured here
so they aren't lost.

- **Unquoted-identifier round-trip caveat.** The echo style (`render_*`
  in `src/echo.rs`) emits bare identifiers — matching the existing
  `render_create_table` aesthetic. A user-created column or table
  named after a SQL keyword (e.g. `ORDER`, `SELECT`, `WHERE`) would
  produce an echo whose round-trip is fragile. Pre-existing limitation
  (the create-table echo had it from handoff-46); the §1 contract is
  only formally violated for this edge case. If it ever bites a
  learner, the fix is to add identifier quoting (probably backticks or
  double-quotes per the advanced grammar's accepted spelling, plus a
  conservative "needs quoting" predicate).
- **No full-spawn integration test for the echo pipeline.** Coverage
  today is layered: pure renderers + runtime-helper unit tests (with
  real `Database`) + App-level rendering tests. The unwired glue is
  small (~3 lines per arm in `spawn_dsl_dispatch`). A Tier-3 test
  that drives the full submit → runtime → App pipeline would complete
  the picture; not blocking.
- **Pure-Command Schema commands double-compute echo.** `echo_for` is
  called for every command, and the Schema arm then computes
  `build_schema_echo` (whose catch-all redelegates to
  `command_to_sql`). Minor inefficiency, no behaviour bug. Could be
  optimized later; not worth the code complexity now.
- **ADR-0039 (EXPLAIN over advanced SQL)** — separate deferred
  follow-up, not part of ADR-0038. Recorded 2026-05-27 (handoff-45/46);
  pick up when prioritised.

## §8. Process pins (unchanged)

- **Confirm every commit** (propose message, wait). **No AI attribution.**
- **Test-first**; green + clippy-clean is the only acceptable end
  state; current baseline **2015 / 0 / 1**.
- **Keep docs lockstep**: ADR + `README.md` index + `requirements.md`.
  This session's `5cb105b` was the corrective pass when the prior
  per-phase commits left M4 / ADR-0038 status drifted.
- **Round-trip every catalogue row** (the §1 copy-paste contract; for
  multi-statement, *per line*).
- **Pre-execution lookups** for any future Bucket-B-style additions
  whose echo needs info the dropped/created thing erases. Mirror the
  `collect_echo_lookups` shape (best-effort, fail-soft) and consume
  in the post-execution arm.

## §9. How to take over

1. **Read, in order:** this file → **ADR-0038** (§4 styled-runs
   reference is the relevant bit for the polish) → **ADR-0028**
   (`OutputLine` styled-runs mechanism; `output_render::render_*` for
   established patterns) → **`src/echo.rs`** (the renderer surface
   itself is settled — but read to understand the catalogue shape if
   you'll touch rendering) → **`src/app.rs`**'s `note_ok_summary` and
   `handle_dsl_change_column_success` (the two consumers of the echo
   + caveat strings — these are what the polish replaces).
2. **Baseline:** `cargo test` (2015 / 0 / 1) + `cargo clippy
   --all-targets -- -D warnings` (clean).
3. **If continuing on ADR-0038**: the polish is the only open item.
   See §5. Decide the App-test assertion shape early (style-vs-text).
4. **If picking up something else**: ADR-0038 is done modulo the
   polish; the §4 line of the ADR remains the formal outstanding
   item but does not block other work. ADR-0039 (EXPLAIN over
   advanced SQL) is a self-contained deferred follow-up if you want
   the next feature.
