# Session handoff — 2026-06-06 (58)

Fifty-eighth handover. Continues from handoff-57, whose **next job
was H1a**. This session did exactly that, end to end: **H1a
(strong syntax-help in parse errors) is now done and marked `[x]`**
in `requirements.md`, via a new ADR-0042 systematic pass. The arc
was audit → ADR → test-first matrix → four gap fixes → two
adversarial review passes that each caught a real defect. See §2.

## §1. State at handoff

**Branch:** `main`. **HEAD `c305dc7`.** **origin/main at `10f8c2a`**
(pushed mid-session) → **4 commits unpushed** (`649fdcb`, `1d4923b`,
`d6e229f`, `c305dc7`). Push is the user's step.

**Tests: 2158 passing / 0 failing / 1 ignored** (lib 1578, it 388,
typing_surface_matrix 192; the 1 ignored is the pre-existing one).
**Clippy clean** (nursery, all targets, `-D warnings`).

This session's commits (since handoff-57's `a8d0138`):

```
c305dc7 docs: mark H1a done via the ADR-0042 systematic pass
d6e229f feat: H1a CROSS JOIN ON teaching message; advanced-SQL gaps re-verified (ADR-0042)
1d4923b fix: H1a G3 advanced usage shows all valid forms; complete near-miss matrix (ADR-0042)
649fdcb feat: H1a parse-error gaps G2–G4 + advanced near-miss matrix (ADR-0042)
10f8c2a test: H1a near-miss matrix + friendlier `add 1:n relationship` label (ADR-0042)
0e6f767 docs: ADR-0042 — continue H1a parse-error pedagogy on the grammar tree
```

## §2. What happened this stretch — H1a, start to finish

### Audit first (the big correction)

The handoff-57 §6 pointer said "read ADR-0021 + ADR-0020". **Both
are obsolete.** They specify a `chumsky`-over-tokens mechanism
(`UsageEntry` registry, `parse.token.*` keys, a lexer) that
**ADR-0024 deleted** — chumsky is not a dependency; the parser is
the scannerless unified grammar-tree walker. ADR-0020/0021 are now
marked **Superseded** (status notes + README), kept as memory.
ADR-0021's *intent* survived and was already ~80% shipped via the
grammar tree. **Read `docs/adr/0042-h1a-parse-error-pedagogy-grammar-tree.md`,
not 0020/0021, for the live H1a design.**

### ADR-0042 + the user's three scope decisions

Wrote **ADR-0042** (continues H1a against the grammar tree). Three
forks escalated and decided by the user: (1) ADR hygiene =
superseded-notes + new ADR; (2) scope = matrix-verify + friendlier
literal labels; (3) advanced-SQL **in scope**.

### The near-miss matrix (the definition of done)

`tests/it/parse_error_pedagogy.rs` now holds a per-command
near-miss matrix, built **test-first** from an empirical baseline
capture. Covers, in both modes: every entry word's bare /
missing-clause / wrong-token cases, the app-lifecycle trailing-junk
cases, and the **committed multi-forms** (`add index`,
`add constraint`, `add 1:n relationship`, `drop index/constraint/
relationship`, `show table`, `change column`, `create index`,
`alter table add/drop`). Tests: `near_miss_matrix_simple_mode`,
`near_miss_matrix_advanced_mode`, `near_miss_matrix_committed_multiforms`,
plus per-gap tests.

### Four gap fixes (each test-first)

- **G1** — bare `1` cardinality literal → `` `1:n relationship` `` in
  `format_expectation` (render-only; completion still offers `1`).
- **G2** — bare `select`'s 14-item expression first-set → "a
  projection: `*`, a column, or an expression", detected by the
  `distinct`+`all` quantifier pair (unique to a projection start;
  empirically verified non-misfiring). Render-only in
  `format_walker_error`.
- **G3** — usage block was mode-blind. Added `usage_*_in_mode`
  (`src/dsl/grammar/mod.rs`) + mode threading through
  `render_usage_block` (`app.rs`) and the ambient usage
  (`input_render.rs`). **Decision (user, after review):** advanced
  mode shows **all forms valid in the mode, SQL-first then the DSL
  fallback forms** — DSL forms (`create table … with pk`,
  `drop column …`) remain valid input in advanced mode (verified),
  so a usage hint must not hide them. Simple mode = DSL only.
- **G4** — `with` got its own `parse.usage.with` CTE template
  (was borrowing `select`'s).
- **CROSS JOIN ON** — `parse.cross_join_no_on` teaches "a CROSS JOIN
  has no ON clause …" when the failing token is `on` and the most
  recent consumed join is a CROSS join. `is_cross_join_on` in
  `parser.rs`; render-only.

### Two adversarial review passes earned their keep

- Pass 1 caught **G3 over-correction**: an initial "SQL-only"
  advanced usage block *hid* valid DSL fallback forms. Escalated →
  user chose "show all valid forms" → fixed (`1d4923b`).
- "Verify, don't trust the survey" **reversed two of three**
  advanced-SQL "gaps": INSERT…SELECT count and RETURNING scope were
  *already handled* (the Explore-survey list was wrong, twice).
  Only CROSS JOIN ON was real.
- The matrix itself caught a regression mid-work (advanced
  insert/update/delete falling back to available-commands because
  the SQL nodes have empty `usage_ids`; fixed with a union fallback).

### Deferred by decision (low-priority residual)

At **submit** time, a non-projection expression position (bare
`where `, `returning `, `having `, `set col=`) still renders the raw
~14-item expression first-set; only the SELECT projection is glossed
(G2). Low-impact because **typing-time completion already offers the
right candidates** there. User chose to leave it. Documented in
ADR-0042 + `requirements.md`.

Plus one **pre-existing caveat** (not this session's work, noted in
ADR-0042): `insert into T (one_col) select * from Multi` isn't
pre-caught for arity — `SELECT *` isn't expanded; the engine rejects
it at execution (brushes ADR-0019 §OOS-2 engine-error territory).

## §3. ⚠️ Where parse-error pedagogy lives now (read before touching)

- **Usage templates:** `parse.usage.*` in `src/friendly/strings/en-US.yaml`;
  `usage_ids` on each `CommandNode` (`src/dsl/grammar/mod.rs`).
  Mode-aware selection: `usage_keys_for_input_in_mode` /
  `usage_key_for_input_in_mode`.
- **Structural error wording:** `format_walker_error` +
  `format_expectation` in `src/dsl/parser.rs` (this is where the G1
  label, G2 projection gloss, and CROSS JOIN message live —
  render-only, they do **not** mutate the `Expectation` set the
  completion/hint layer consumes).
- **Catalog discipline (ADR-0019):** every new key goes in
  `en-US.yaml` **and** `src/friendly/keys.rs::KEYS_AND_PLACEHOLDERS`
  (the `keys_validate_against_catalog` test enforces it).
- **Tests:** integration parse-error tests live in `tests/it/` per
  the handoff-57 §3 rule — drop the file in `tests/it/` + add a
  `mod` line to `tests/it/main.rs`. Schema-aware diagnostics are
  tested at the walker level with a `SchemaCache` (`vschema` helper
  in `tests/it/sql_insert.rs`).

## §4. Carried / unchanged

- **arboard decisions** (handoff-55 §3): X11-only on Linux; `copy`
  reproduces `[system]` tags. One-line changes if revisited.
- No open GitHub issues (`gh issue list` empty; the project's issue
  tracker is GitHub, not the gitea host `tea` is configured for).

## §5. Other tracks (from `requirements.md`)

Unchanged: Track 2 Iter 6 leftovers (history.log input-history
hydration polish, migration-framework exercise); C3a modify
relationship; C4 m:n convenience; **H1 done** (ADR-0019), **H1a
done** (ADR-0042); H2 `hint`; H3 `help` (partial — general
reference + `help <command>` still missing); V4 session-log /
Markdown export; I1/I1b multi-line + readline; I3 tab completion /
I4 syntax highlighting (the walker already exposes the hooks); TU1
tutorial (needs ADR); TT5 CI (not configured).

## §6. Next job — pick one

No single forced next step. Candidates, roughly by readiness:

1. **H3 `help` completion** — the grammar tree already iterates the
   REGISTRY for the command list; the missing pieces are a general
   reference and `help <command>` per-command detail. The
   `help_id` per `CommandNode` is the hook. Small-to-medium.
2. **I3 tab completion** — the walker's expected-set + completion
   candidates already exist (used by ambient hints); I3 is the
   **UI/UX** (cursor handling, menu, accept). Needs its own ADR.
3. **The deferred H1a residual** (§2) — generalise the projection
   gloss to other expression positions. Low value (completion
   already covers typing-time); only if it bugs you.
4. **CI (TT5)** — test infra is solid (2158 green); no workflow yet.

## §7. How to take over

1. Read handoffs 56 → 57 → 58, then `CLAUDE.md`, then
   `docs/requirements.md` (H1 and **H1a now `[x]`**),
   `docs/adr/README.md`.
2. **For anything parse-error/pedagogy: read ADR-0042, not
   ADR-0020/0021** (those are superseded; chumsky is gone).
3. Codebase on `main` at `c305dc7`, clean, 4 unpushed.
4. Process pins that paid off this arc, again: **audit before
   assuming** (ADR-0021 was obsolete; H1a was mostly already
   shipped), **verify empirically — don't trust the docs/survey**
   (the advanced-SQL gap list was wrong twice; a regression hid in
   "looks fine"), **escalate genuine forks** (the G3 all-forms
   decision was the user's, not mine), and **test-first + matrix as
   a regression lock** (it caught a regression I introduced).
   Commits user-confirmed, append-only, no AI attribution.
