Merge branch 'main' into website

This commit is contained in:
claude@clouddev1
2026-06-12 13:22:52 +00:00
38 changed files with 6222 additions and 142 deletions
+14 -11
View File
@@ -8,9 +8,8 @@ to end across three phases + a restyle).
## §1. State at handoff
**Branch:** `main`. **HEAD `2d0f4b2`** plus an **uncommitted docs
finalization** (ADR-0047 status → implemented, README index, this
handoff — see §6). Push is the user's step.
**Branch:** `main`. **HEAD `f0afec3`** — all work committed, nothing
pending. Unpushed (push is the user's step; normal working state).
**Tests: 2290 passing / 0 failing / 0 skipped / 1 ignored** (the 1
ignored is the long-standing `friendly` doctest). **Clippy clean**
@@ -18,6 +17,7 @@ ignored is the long-standing `friendly` doctest). **Clippy clean**
**This session's commits:**
```
f0afec3 docs: session handoff 64 + ADR-0047 implemented (#22/#24)
2d0f4b2 feat(ui): flat filled rectangles for demo overlays (#22, ADR-0047 D4)
241f60c feat(ui): demo-mode step-caption stealth buffer (#22, ADR-0047 D3/D4)
2584e76 feat(ui): demo-mode keystroke badges (#22, ADR-0047 D2/D4/D5)
@@ -26,8 +26,9 @@ e9eb1b1 docs: ADR-0047 — demonstration overlay layer for casts/teaching (#22)
638b4c9 feat(app): vi-style j/k/g/G navigation in the load picker (#24)
```
**Issues closed:** **#24** (vi nav) and **#22** (demo overlays) — close
#22 once the docs finalization commit lands.
**Issues closed:** both **#24** (vi nav) and **#22** (demo overlays) are
**closed on Gitea** with closing comments — verified via the filtered
issue list. Nothing left open from this session's scope.
## §2. #24 — vi-style load-picker navigation (commit `638b4c9`)
@@ -107,13 +108,15 @@ existing `IndicatorDebounce` already takes. A future Tier-4 PTY harness
## §6. How to take over
**Nothing is pending from this session** — both issues are closed, all
docs landed (`f0afec3`), tree is green. The next session **returns to the
open requirements backlog** (§7). Suggested start: run `/whatsnext`
(it reads this handoff), or pick from §7 below.
1. Read handoffs 62 → 63 → 64, `CLAUDE.md`, `docs/requirements.md`,
`docs/adr/README.md`, and **ADR-0047** (fully landed).
2. **Pending:** the docs finalization commit (ADR-0047 status →
implemented; README index; this handoff). Commit as
`docs: session handoff 64 + ADR-0047 implemented (#22/#24)` (the user
confirms commit messages). Then close **#22** on Gitea.
3. **For demo-overlay work:** `App` has `demo_mode`, `demo_badge`,
`docs/adr/README.md`. ADR-0047 is fully landed; revisit only for
demo-overlay follow-ups.
2. **For demo-overlay work:** `App` has `demo_mode`, `demo_badge`,
`demo_badge_seq`, `demo_caption`, `demo_caption_capturing`,
`demo_caption_buffer`, `last_output_area`. Rendering:
`render_demo_overlays` / `render_badge_box` / `render_caption_box` /
+144
View File
@@ -0,0 +1,144 @@
# Session handoff — 2026-06-11 (65)
Sixty-fifth handover. Continues from handoff-64 (ADR-0047 demo
overlays). This session designed and shipped **ADR-0048 — the `seed`
fake-data generation command (SD1)**, Phase 1, end to end: an ADR with
an extended fork dialogue + two `/runda` passes, then a phased
test-first build.
## §1. State at handoff
**Branch:** `main`. **HEAD will be the doc-wrap-up commit** (see §6) —
all seed work committed, nothing pending. Unpushed (push is the user's
step; normal working state).
**Tests: 2358 passing / 0 failing / 0 skipped / 1 ignored** (the long
-standing `friendly` doctest). **Clippy clean** (nursery, all targets).
+68 over handoff-64's 2290.
**`cargo sweep` run** at wrap-up: `target/` 1.6 G → 183 M.
**This session's commits:**
```
202e25a feat(seed): fake-data generation library + fake dependency (P1.1)
f1e9484 feat(seed): command plumbing + walking skeleton (P1.2)
73493fa feat(seed): FK sampling, empty-parent error, block guard (P1.3a)
9c13501 feat(seed): uniqueness, junction distinct-combos, IN-CHECK (P1.3b)
0b3ab3c feat(seed): SeedResult outcome, capped preview, advisory, count cap (P1.3c)
e6ff63d perf(seed): single-transaction multi-row insert path (P1.3d)
fbd219b feat(seed): --seed flag, ambient wiring, and /runda hardening (P1.4 + DA)
```
(plus the earlier `4d0ae77` multi-tab-scope withdrawal and `0af7f56`
ADR-0048 doc, and the wrap-up doc commit.)
## §2. What `seed` does (Phase 1 — read ADR-0048)
`seed <table> [count] [--seed <n>]` — populate a table with realistic
fake data. **Available in both modes** (A1).
- **Realistic, name-aware generation:** the **`fake` crate** (v5,
English) driven by a **type-gated heuristic catalogue** (`src/seed/
heuristics.rs`) — `email`→email, `first_name`→first name, `price`
currency, etc., each only firing when the column *type* is
compatible. **Table-context** disambiguates `name`/`title`
(`products.name`→a hand-rolled **product** name, `users.name`→person,
`vendors.name`→company). **Bounded dates** (`dob`/`created_at`/
`date`/`timestamp` → recent windows, never "all of history", anchored
to a fixed reference epoch for reproducibility). Type-based fallback
otherwise.
- **Uniqueness (D10):** the user-fillable PK, compound UNIQUE
constraints, single-column UNIQUE, and identifier-named columns
(`id`/`code`/…) stay distinct across the batch and vs existing rows;
**junction tables** get **distinct FK combinations** (capped at the
available product, reported). Identifier ints get a monotonic
sequence.
- **FK (D14):** every FK column samples an existing parent row (compound
FK reads one consistent parent row); **empty parent → friendly
error**.
- **`IN`-CHECK (D17):** a simple `col IN ('a','b')` CHECK becomes the
value source (enum-as-CHECK just works); complex CHECKs are flagged in
the advisory and best-effort generated (a violation rolls the batch
back).
- **Reproducibility (D4):** `--seed <n>` → identical data on the same DB
state. **Holds with no exceptions** — serial (rowid/MAX+1), FK
(`ORDER BY`), **shortid (seeded RNG)**, all generators.
- **Output:** the seeded-row count, a **capped preview** (first 20
rows), and a **Hint-styled advisory** naming enum-ish / underivable-
CHECK columns filled generically. Count cap 10 000; `seed t 0` no-op.
- **Safety:** one **undo** step (snapshot wraps the whole seed);
**replay** re-runs it as a data write; the insert path is a single
transaction (O(N), atomic, commit-db-last preserved).
## §3. Where the code lives
- **`src/seed/`** — the pure generation library (no DB): `mod.rs`
(`ColumnSpec`, `Generator`, `SeedRng`, `make_rng`), `heuristics.rs`
(`choose_generator` + the catalogue + `is_enum_ish`), `generators.rs`
(`generate_value` + the `product` generator + bounded dates),
`check.rs` (`parse_in_check_values`). ~40 Tier-1 tests, deterministic.
- **`src/db.rs`** — `do_seed` (+ `SeedColPlan`, `sample_parent_key_
tuples`, `seed_value_list_key`, `seed_max_int`, `SeedResult`,
`DEFAULT_SEED_COUNT`/`MAX_SEED_COUNT`/`SEED_PREVIEW_CAP`), the new
**`insert_one_row`** core extracted from `do_insert` (shared, no
tx/persist — so seed runs N rows in one tx), and the `Request::Seed` /
`Database::seed` / worker wiring.
- **`src/dsl/grammar/data.rs`** — `SEED` `CommandNode`, `build_seed`,
the `--seed` flag grammar (`Seq[Flag("seed"), NumberLit]`, the first
DSL flag with a value). `Command::Seed` in `command.rs`.
- **Runtime/render** — `CommandOutcome::Seed`, `AppEvent::
DslSeedSucceeded`, `App::handle_dsl_seed_success`. Catalog keys
`ok.rows_seeded` / `seed.capped` / `seed.advisory_generic` /
`help.data.seed` / `parse.usage.seed`.
- **Tests** — `tests/it/seed.rs` (25 integration tests),
`tests/typing_surface/mod.rs` (`seed_completion_and_validity`),
`tests/it/parse_error_pedagogy.rs` (bare-`seed` near-miss row),
`src/app.rs` (two render tests), `src/dsl/shortid.rs`
(`generate_with_rng`).
## §4. Process notes (the two `/runda` passes)
- **Pre-build `/runda`** (on the ADR) found six blockers — undo
integration (D15), replay semantics (D16), `set`-value quoting (D2),
CHECK handling (D17), an advisory phase-ordering bug (D13), auto-show
flooding (D18) — all folded into ADR-0048 before any code; the three
genuine forks re-escalated and user-resolved.
- **Post-implementation `/runda`** (on the whole implementation) found
**eight gaps**, all closed: FK-sampling determinism (→ `ORDER BY`),
**shortid not reproducible** (→ seeded RNG, fixed not documented — the
user chose the fix), and six **untested ADR decisions** (D5 advanced
mode, D15 undo, D16 replay, D17 complex-CHECK advisory, atomic
rollback, zero-count) — tests added for each.
## §5. Phase 2 (deferred — designed in ADR-0048, NOT built)
These are the only seed pieces left; both have full designs in
ADR-0048:
1. **The `set` override clause (D2)** — `seed t 20 set role in
('a','b'), status = 'x', work_addr as email, price between 10 and
100`. Value / pick-from-list / explicit-generator / range, **quoted
literals** (grammar-consistent). This is the SD2 "override hooks"
core. The `ColumnSpec.check_in_values` → `PickFrom` plumbing and the
`Generator` vocabulary already exist; this adds the grammar + a `set`
clause that overrides the per-column plan.
2. **Column-fill (`seed <table>.<column>`, D1 form 2)** — fill one
column across *existing* rows (an UPDATE). Refuses PK/autogen targets;
empty-table no-op.
`requirements.md`: **SD1 `[x]`**, **SD2 `[/]`** (core done; the two
above open), **A1 14/15** (only `hint`/**H2** unregistered).
## §6. How to take over
1. Read handoffs 63 → 64 → 65, `CLAUDE.md`, `docs/requirements.md`,
`docs/adr/0048-seed-fake-data-generation.md` (the whole thing — D1
D18 + the as-built status block).
2. **Seed is feature-complete for Phase 1; nothing pending.** Next
options (user's call): seed **Phase 2** (`set` clause + column-fill);
**H2 `hint`** (closes A1) — own ADR; **TT5 CI**; or the larger
**V4 journal** / **tutorial** ADRs.
3. Two minor, user-deferred observations (non-blocking): the uniqueness
retry cap (`MAX_ATTEMPTS=200`) can cap a *medium* unique domain
slightly below its true size (junction/small domains are exact);
`literal_to_value` doesn't type-check an IN-CHECK literal vs a numeric
column (a malformed `int IN ('a')` CHECK fails cleanly at bind).
+145
View File
@@ -0,0 +1,145 @@
# Session handoff — 2026-06-11 (66)
Sixty-sixth handover. Continues from handoff-65 (ADR-0048 `seed`
Phase 1). This session built **ADR-0048 Phase 2** end to end: the
**`set` override clause** (D2) and the **`<table>.<column>`
column-fill** form (D1 form 2) — the two surfaces Phase 1 deliberately
deferred. Designed-then-DA-vetted (a `/runda` pass that caught a real
ADR-vs-grammar conflict), then built test-first.
## §1. State at handoff
**Branch:** `main`. All Phase-2 work is in the working tree;
**commits are pending the user's approval** (see §6). Unpushed is the
normal working state.
**Tests: 2400 passing / 0 failing / 0 skipped / 1 ignored** (the
long-standing `friendly` doctest). **Clippy clean** (nursery, all
targets). +42 over handoff-65's 2358.
## §2. What landed (read ADR-0048 — Status + D1/D2/D9/D13)
`seed <T>[.<col>] [count] [set <overrides>] [--seed <n>]`.
- **`set` override clause (D2):** four forms, comma-separated —
`status = 'active'` (fixed), `role in ('a','b')` (pick-list),
`work_addr as email` (named generator), `price between 10 and 100`
(range; numeric **and quoted dates**). Type-aware; an override
**drops its column from the generic-fill advisory** (D13). Value
slots reuse `update`'s typed `current_column_value` (quoting
enforced structurally — a bare word is rejected).
- **Column-fill (D1 form 2):** `seed users.email [set …]` fills one
column across **existing** rows (an UPDATE). Refuses PK / autogen
(`serial`/`shortid`/`blob`) targets; **empty table → friendly
no-op**; FK target samples the parent; UNIQUE/identifier target gets
collision-free values; **one undo step**; `set` may only adjust the
filled column; a row count is refused.
- **Named-generator vocabulary (D9):** `src/seed/vocabulary.rs`
`KNOWN_GENERATORS` + `generator_for_name` + `is_known_generator_prefix`,
the single source of truth for completion, validity, and the executor.
- **Range generator:** `Generator::Range { low, high }` in
`src/seed/generators.rs`, interpreted per destination type;
`range_bounds_reason` validates compatibility before generation.
- **Ambient wiring:** completion (generator names after `as`, the
`set <col>` and `.col` column slots, the `set` keyword); highlight
(new `HighlightClass::Function` → existing `tok_function`); validity
(new `IdentSource::Generators` — unknown generator flagged `[ERR]`;
unknown column in `set`/`.col` flagged via the existing Columns
path); help (`help.data.seed`); parse-error pedagogy near-miss rows;
the D13 advisory's **Phase-2/3 wording** (points at `set` and the
column-fill repair). Both modes (D5).
## §3. The ADR amendment (a real DA find)
The pre-build `/runda` pass found that **ADR-0048 D2's "dates stay
unquoted" was impossible** — this DSL has **no date-literal token**
(`Value` is `Number`/`Text`; dates are quoted strings validated by
`bind_date`). Escalated to the user, who chose **quoted dates +
amend the ADR** (the grammar-consistent option). D2 now carries a
dated amendment; the range form uses `between '2023-01-01' and
'2024-12-31'`. This was the only divergence from the ADR text; numbers
remain unquoted.
## §4. Where the code lives
- **`src/dsl/command.rs`** — `Command::Seed` gains `target_column:
Option<String>` + `overrides: Vec<SeedOverride>`; new `SeedOverride`
/ `SeedOverrideKind`.
- **`src/dsl/grammar/data.rs`** — `SEED_SET_CLAUSE` + `SEED_DOT_COLUMN`
grammar; `SEED_GENERATOR` slot (`IdentSource::Generators`,
`HighlightClass::Function`); `build_seed` + the override fold
(`build_seed_overrides` / `parse_seed_override_tail`).
- **`src/dsl/grammar/mod.rs`** — `IdentSource::Generators` +
`HighlightClass::Function`.
- **`src/db.rs`** — `apply_seed_overrides` / `seed_override_plan` /
`seed_override_literal`; `do_seed_column_fill`; `do_seed` +
`Database::seed` + worker wiring threaded with the new params.
- **`src/seed/`** — `vocabulary.rs` (new); `generators.rs` (range
generator + `range_bounds_reason`); `mod.rs` (`Generator::Range`).
- **`src/completion.rs`** — generator candidates after `as`; generator
validity. **`src/input_render.rs`** — `"generator"` invalid-ident
kind. **`src/theme.rs`** — `Function → tok_function`.
- **Catalog** — `help.data.seed`, `parse.usage.seed`,
`seed.advisory_generic` (Phase-2/3 wording) in `en-US.yaml`;
`keys.rs` placeholders updated.
- **Tests** — `tests/it/seed.rs` (+~30: builder fold, executor
set/column-fill, undo, advanced mode), `src/seed/{vocabulary,
generators}.rs` (range + vocabulary units), `src/completion.rs`
(generator + column validity), `src/dsl/walker/highlight.rs`,
`tests/typing_surface/mod.rs` (completion slots),
`tests/it/parse_error_pedagogy.rs` (near-miss rows).
## §5. Two implementation refinements vs. the ADR (both met the contract)
- **Quoted dates** (the D2 amendment, §3).
- **Value slots reuse `current_column_value`** (the `update … set`
typed slot) rather than the raw ADR-0026 expression operand — no
spurious column-ref match, typed narrowing, consistent with
`update`. The user-facing contract (quoted literals, type-aware) is
fully met.
The `seed_take_value` / `seed_set_error` builder paths are
drift-guards (the typed slots only ever match value literals, so a bare
word is rejected at the grammar level) — they use the generic
`parse.error_wrapper`, mirroring `expr::build_expr`.
## §6. How to take over / next steps
1. Read handoffs 64 → 65 → 66, `CLAUDE.md`, `docs/requirements.md`,
`docs/adr/0048-…md` (Status block + D1/D2/D9/D13 + the amendment).
2. **Seed is feature-complete (SD1 + SD2).** `requirements.md`: **SD1
`[x]`, SD2 `[x]`**. The only open A1 gap is `hint`/**H2** (own ADR).
3. **Commits pending approval.** Suggested split:
- `feat(seed): set override clause + column-fill (ADR-0048 Phase 2)`
— all `src/` + `tests/` changes.
- `docs: ADR-0048 Phase 2 implemented + handoff 66` — ADR / README /
requirements / this file.
4. Next options (user's call): **H2 `hint`** (closes A1); **TT5 CI**;
the larger **V4 journal** / **tutorial** ADRs; or Tier-4 PTY (TT4).
5. Consider a `cargo sweep` at this milestone (`target/` grows).
## §7. Post-implementation `/runda` pass (done this session)
A DA pass over the completed code found **no correctness bugs and no
dropped requirements**; all D1D18 acceptance criteria verified met,
tests confirmed to catch regressions. One **design fork** was surfaced
and **resolved by the user**:
- **Bounded override × UNIQUE column** — a fixed value / too-short
pick-list on a single-column-UNIQUE target used to silently cap the
run (e.g. `seed users 100 set email = 'x'` → 1 row). Now a **friendly
error** up front (`seed_override_capacity_guard`, `src/db.rs`), for
both whole-row and column-fill; generators/ranges stay cap-based
(unbounded sources). ADR-0048 D2 documents it; two tests pin it.
Remaining **non-blocking** edges (noted, not bugs):
- Overriding an **FK column** with a literal: the override wins (D2); a
non-parent value fails safely through the FK-error layer.
- **Column-fill of one column of a *compound* FK** samples that column
independently → an invalid tuple fails safely (UPDATE rejected,
rollback), never corrupts. Single-column FKs / non-FK columns are
exact.
- The generator slot uses the **default candidate-ladder hint** (offers
the vocabulary), not a dedicated prose intro — discoverability is met
by completion; a prose intro is optional polish.
+119
View File
@@ -0,0 +1,119 @@
# Session handoff — 2026-06-12 (67)
Sixty-seventh handover. Continues directly from handoff-66 (ADR-0048
`seed` Phase 2, committed). This was a **manual-testing pass**: the user
exercised the app, found several rough edges, and we triaged each into
*fix now* vs *file an issue*. Net result: **three bug fixes committed**
and **three enhancement issues filed**.
## §1. State at handoff
**Branch:** `main`. Working tree **clean**; all work committed. Unpushed
(push is the user's step).
**Tests: 2407 passing / 0 failing / 0 skipped / 1 ignored** (the
long-standing `friendly` doctest). **Clippy clean** (nursery, all
targets). +7 over handoff-66's 2400.
**Commits since handoff-65:**
```
f7155ce fix(input): thread the `:` one-shot escape into live SQL feedback
4cacb82 fix(completion): don't flag a table alias used before its FROM clause
c3e0103 fix(completion): flag-aware partial so a dash completes flags, not keywords
30b2677 docs: ADR-0048 Phase 2 implemented + handoff 66
a12facc feat(seed): set override clause + column-fill (ADR-0048 Phase 2)
```
(`a12facc`/`30b2677` are the Phase-2 work documented in handoff-66.)
## §2. Bug fixes this session (all committed, all tested)
1. **`c3e0103` — flag completion ate the dash.** Typing a flag at a
flag position (`add 1:n relationship … -`) offered the `on` keyword
and, on accept, produced `-on` / `---create-fk`: the partial-token
walk stopped at `-`, so the dash was outside the replaced range.
Fix: flag-aware partial detection (a dash-prefixed token at a word
boundary is a flag-in-progress, **gated on a flag being expected** so
`where x = -5` stays a number) + a unified flag matcher
(`trim_start_matches('-')`). Affected **all** flags. 4 tests + 2
partial-flag snapshots updated (they'd captured the latent bug).
2. **`4cacb82` — table alias flagged as an unknown column.** In a
SELECT, the projection (`sum(ol.count*…)`) can reference an alias
whose `FROM … OrderLines ol` sits *after* the cursor. The candidate
engine recovers that via the §10.6 full-input lookahead (ADR-0032),
but `invalid_ident_at_cursor` only walked text *before* the cursor —
so `ol` matched no scope and got a red "ERR" overlay on an otherwise
valid query. Fix: give the validity check the same full-input
lookahead and bail when the partial prefix-matches a binding's alias
or table. 1 test.
3. **`f7155ce` — the `:` one-shot escape broke live SQL feedback.**
Submission strips the `:` (ADR-0003), but the *live* feedback kept it
in the buffer handed to the walker, which bailed at the `:`. Effect:
under `:`, Tab completed nothing and a valid query could flash `[ERR]`
— while the same line in full `mode advanced` worked. (The hint
already stripped it, hence "hint shows the name but Tab does
nothing".) Fix: one shared `App::feedback_view()` (the `:`-stripped
SQL + mapped cursor + stripped offset) routed through completion (with
a `replaced_range` offset shift), the validity verdict, and rendering
(new `render_input_runs_feedback` highlights/overlays the view shifted
by the offset; the `:` renders as plain text); the ambient hint was
consolidated onto it (removing the duplicate `strip_one_shot_prefix`).
3 tests + the 9 existing colon tests still green.
## §3. Investigated, **no code change** (working as designed)
- **Comma-`FROM` implicit join** (`select … from A, B, C`) is
**deliberately rejected** — ADR-0032 §11 / OOS-3: *"comma-FROM teaches
habits we do not want to encourage; `CROSS JOIN` covers the same shape
explicitly."* The explicit equivalent (`CROSS JOIN … WHERE …`) works.
- **`sum(…)` returning one row** with no `GROUP BY` is **correct SQL**
(the aggregate collapses the result to one row; SQLite/the playground
allow the non-aggregated columns where Postgres would error). The
user's query needed `group by o.id`. Verified (1 row).
## §4. Open issues filed this session — **next session's candidates**
All on `git.lazyeval.net/oli/rdbms-playground`, label `enhancement`:
- **#26`seed <table>` hint omits the optional count.** A complete
command's optional positional *number* has no Tab candidate, so it's
invisible. `IntroProse` doesn't fit (it only fires for incomplete
required slots; the completing Seq match clears the hint). Needs a way
to advertise optional positional non-keyword args. *(I attempted +
reverted this during Phase 2; see the analysis in the issue.)*
- **#27 — Bottom status line: keybindings-only, context- and
state-aware.** Per-nav-focus keybindings (Input vs sidebar), **include
transient states** (Tab-cycle, history) — user preference — and add
`mode advanced` to the empty-input hint. May warrant a small ADR.
- **#28 — Reconsider relationship prose in `add column` (incidental DDL)
confirmations.** Currently by design (ADR-0044 §1 keeps prose, not
diagrams, for incidental DDL). **User preference: do NOT show the
`References:` / `Referenced by:` block** in the add-column
confirmation at all — focus on the change just made. This revisits a
decided area → land as a **new ADR** superseding the relevant part of
ADR-0016 §5 / ADR-0044 §1; confirm scope (just `add column`, or all
incidental DDL).
## §5. Other open work (unchanged from handoff-66 §6)
`seed` is **feature-complete** (`requirements.md` SD1 `[x]`, SD2 `[x]`).
Remaining roadmap, user's call:
- **H2 `hint`** — the last A1 gap (its own ADR).
- **TT5 CI** — test infra exists; no CI workflow yet.
- **TT4 PTY (Tier-4)** — ADR-0008 specifies it; not wired.
- Larger: **V4 journal**, **tutorial/lesson system** (each needs an ADR).
A possible quick follow-up: a friendlier "use an explicit `JOIN`"
parse-error for comma-`FROM` (point 1) — not filed; mention if wanted.
## §6. How to take over
1. Read handoffs 65 → 66 → 67, `CLAUDE.md`, `docs/requirements.md`.
2. `seed` Phase 2 is done (ADR-0048 Status block is current). The
manual-testing fixes (§2) are committed and green.
3. Pick from §4 (filed issues #26/#27/#28) or §5 (roadmap). #28 is a
decision/ADR; #27 is UX (maybe ADR); #26 is a hint-system enhancement.
4. Consider a `cargo sweep` at this milestone (`target/` grows across
sessions).