docs: session handoff 54 — #10/#14 resolved, ADR-0037 Am1 + ADR-0015 Am1

#10 (output [error]/[system] tag-colour collision) and #14 (per-project
input-mode persist & restore) closed. Two ADR amendments authored.
Records the two /runda saves on #14 (persist semantics re-decided by the
user; missing integration test added red-first) and a detailed pickup
brief for #11 (clipboard — new dependency, security review + command
surface to escalate).
This commit is contained in:
claude@clouddev1
2026-06-02 08:11:01 +00:00
parent 516848ff63
commit 1ea376be26
+272
View File
@@ -0,0 +1,272 @@
# Session handoff — 2026-06-02 (54)
Fifty-fourth handover. **Two issues off handoff-53's open list,
resolved in sequence.** Closed **#10** (output `[error]`/`[system]`
tag-colour collision) and **#14** (per-project input-mode persist &
restore). Two ADR amendments authored: **ADR-0037 Amendment 1** and
**ADR-0015 Amendment 1**.
The session is worth reading for the **same process lesson, paid twice**:
the `/runda` (DA-hat) pass on the **implementation** found real,
ship-blocking gaps that the green suite did not — a semantics
discrepancy on #14 (which the user then re-decided), and a missing
integration test for the #14 unload wiring (added red-first afterwards).
See §2.
## §1. State at handoff
**Branch:** `main`. **HEAD `516848f`.** **Ahead of `origin/main` by
5** — handoff-52's `8311de4` (#9) and handoff-53's `6d8c9ee` (#15/#16)
were already unpushed at the start, and this session adds three on top.
**Tests: 2134 passing / 0 failing / 1 ignored** (full `cargo test`;
lib-only is **1565**). The 1 ignored is the same long-standing
`` ```ignore `` doctest in `src/friendly/mod.rs` as every prior baseline.
**Clippy: clean** (nursery, all targets, `-D warnings`). Push is the
user's step.
Commits since handoff-53's `6d8c9ee`:
```
516848f test: integration-test the mode persist-on-unload wiring (#14)
4cd574b feat: persist & restore per-project input mode (#14)
ae57c6f feat: colour output tags by status, not mode — readable error bodies (#10)
```
**The five unpushed commits resolve four issues — close them on GitHub
once pushed: #15, #16, #10, #14.** (#15/#16 are from handoff-53;
#10/#14 are this session.)
## §2. The process saves that paid off (read this)
Both issues followed the now-standard arc: read the issue + the ADRs it
names → trace the exact symbols → **escalate the genuine design choices
to the user** → test-first build → **`/runda` on the implementation** →
fix findings red-first → commit. The DA pass earned its keep on both,
and twice on #14.
1. **#14, round one (during impl): a semantics discrepancy.** My first
build persisted the mode *on change* (the `mode` command + any DDL).
I had told the user it would persist "at quit." The DA pass caught
the divergence and I escalated it rather than shipping. The user
re-decided: **persist on *unload*** (quit + project switch), rejecting
both persist-on-change (confusingly selective — whether a `--mode`
override "stuck" depended on whether you happened to run a DDL) and
persist-on-*any*-command (too heavy — would rewrite `project.yaml`
and bump its mtime, which orders the load picker, on every read-only
`select`/`show data`). The shipped design is the user's.
2. **#14, round two (post-commit `/runda`, user-requested): a missing
test.** The persist-on-unload code was written *after* round one's DA
pass, so it had never had its own adversarial review. The second pass
verified every exit path (the `quit` command, **Ctrl-C**, and the
**fatal-persistence quit** all route through `Action::Quit` →
`set_mode`; only an abnormal channel-close skips it, acceptably) and
the temp-cleanup interaction (writing `mode:` keeps `tables: []`, so
`is_unmodified_temp` still deletes empty temps). It found one real
gap: the unload *wiring* had no integration test. Added
`runtime::switch_persists_the_outgoing_projects_mode`, driving the
real (private) `handle_project_switch` end-to-end — **red-first
verified** (disable the `set_mode` call → `None` vs `Some(Advanced)`).
Commit `516848f`.
3. **#14 design: the user simplified my over-engineering — twice.** My
first instinct mirrored the mode in the db metadata table (like
`created_at`). The user pushed back: mode is *live UI state, known at
any time* — just write the current value on every `project.yaml`
write; there is nothing to "preserve." That deleted a whole layer.
Lesson reinforced (held since handoff-52 §2.2 / handoff-53 §2):
**`/runda` on the implementation, not just the design.** A green suite
is necessary, not sufficient.
## §3. Resolved issues
### #10 — `[error]`/`[system]` tag-colour collision (`ae57c6f`, ADR-0037 Amendment 1)
**Problem.** `render_output_line` (`src/ui.rs`) coloured the output tag
by `mode_at_submission` for *every* line kind, so a `[system]` line and
an `[error]` line had an identical leftmost tag (blue in simple, orange
in advanced) — distinguishable only by body colour. And flooding the
whole error body in red made long messages hard to read.
**Change — the status-coloured-tag model.** The tag is coloured by the
message's **status** (`OutputKind`): `[system]` → green, `[error]` →
red, **echo keeps the mode tint** (the *sole* exception — that is
ADR-0037's actual stated purpose; per-command success rides the ✓/✗
marker). Bodies go neutral `theme.fg`; the **error body is bold**
(rustc-style: severity-coloured label, readable bold message). Yields a
status traffic-light (green = ok, red = error) matching the ADR-0040
✓/✗ palette. This *narrows* ADR-0037's mode side-channel to the echo
line it was always for, and closes the tag-colour gap ADR-0040 had
flagged as orthogonal (issue #10).
**Two autonomous decisions flagged + user-confirmed:** `TeachingEcho`
tag → green (it is a `[system]`-tagged line); echo tag stays
mode-tinted.
**Files:** `src/ui.rs` (the single rendering site — confirmed no other
call site colours tags); ADR-0037 Amendment 1 + README. **Coverage:** 4
`ui.rs` tests incl. `error_and_system_tags_are_distinguishable_in_both_themes`
(the direct regression guard, dark + light). The insta snapshots only
capture glyphs (`symbol()`), not colour, so no snapshot churn.
### #14 — per-project input-mode persist & restore (`4cd574b` + `516848f`, ADR-0015 Amendment 1)
**Decision (user's).** The input mode is **per-project state stored in
`project.yaml`** (`project.mode:`, optional, default `simple`), restored
on every open and persisted on **unload**. A teacher can ship a project
that opens in advanced mode and hand/export it to students; a learner's
mode is restored per project ("loading triggers the mode switch each
time"). The export carries the mode (it is part of `project.yaml`) —
that is the intended teacher→student behaviour, not a leak.
**Mechanism (deliberately *not* the db-meta round-trip used for
`created_at`).** Mode is live UI state, not schema, and is **not stored
in the database**. The persistence handle carries the current mode and
the worker **stamps it into `project.yaml` on every write** — so a later
schema-mutating command rewrites the live value rather than clobbering
it; there is nothing to preserve. `rebuild` ignores the field. Optional
field with a `simple` default → pre-#14 files parse unchanged, no
version bump, no migrator (same pattern as the `unique` index flag).
**Restore precedence: `--mode` > stored > `simple`.** New CLI flag
`--mode simple|advanced` overrides at startup; combines with `--resume`
and a positional path (not mutually exclusive — flag wins on collision).
`Mode::resolve_startup(flag, stored)` is the extracted, tested precedence
helper. The `--mode` override applies **only at boot**; a later switch
restores that project's own stored mode.
**Persist on unload (the deciding rule).** The mode is written whenever
the current project is unloaded — on quit and on a project switch
(load/new/save-as/import), the runtime calls `set_mode(App.mode)` on the
outgoing database before it is dropped. By the time you leave a project,
the mode you were in is always recorded — including a bare `--mode`
override or a read-only session. The `mode` command also persists
immediately (`Action::PersistMode` → `Database::set_mode`, crash-safe).
All unload persistence is **best-effort** (a write failure must never
escalate a UI action into a fatal). A switch saves the outgoing mode,
then restores the incoming project's stored mode via the
`ProjectSwitched` event.
**Files:** `src/mode.rs` (keyword parse/round-trip + `resolve_startup`),
`src/cli.rs` (`--mode`), `src/persistence/mod.rs` (`Persistence`
current-mode + `read_stored_mode`), `src/persistence/yaml.rs`
(serialize/deserialize + `parse_stored_mode` — `None` distinguishes
"no preference" from explicit `simple`), `src/db.rs`
(`SchemaSnapshot.mode`, `Request::SetMode`, `Database::set_mode`,
`finalize_persistence` stamp), `src/action.rs` (`PersistMode`),
`src/app.rs` (mode command emits it; `ProjectSwitched` restores),
`src/event.rs` (`ProjectSwitched { mode }`), `src/runtime.rs` (boot
resolve + the two unload call sites), `src/archive.rs` (export
round-trip test) + ADR-0015 Amendment 1, ADR-0003 note, README,
`requirements.md` L1b, `--help` banner.
**Coverage (23 tests):** `mode.rs` parse/round-trip + `resolve_startup`;
`yaml.rs` mode round-trip / default-when-absent /
`parse_stored_mode` absent-vs-explicit / unknown-value;
`persistence` read_stored_mode round-trip + missing-file; `db`
`set_mode_persists_and_survives_a_later_ddl_command` (no-clobber) +
`set_mode_persists_even_with_no_prior_command` (unload-with-no-work);
`archive::export_carries_the_stored_input_mode`; `cli` `--mode`
parse/precedence/combines-with-resume; `app` mode-command-emits-persist
/ one-shot / `ProjectSwitched`-restores;
`runtime::switch_persists_the_outgoing_projects_mode` (the real-path
integration test, `516848f`).
## §4. ADR / docs work
- **ADR-0037 Amendment 1 — new** (`ae57c6f`): the status-coloured-tag
model (table + rationale + the rustc-precedent for bold-neutral error
bodies + coverage). README ADR-0037 entry gains the amendment note;
ADR-0040's "issue #10 is orthogonal" gap is now closed (cross-noted).
- **ADR-0015 Amendment 1 — new** (`4cd574b`, coverage note extended in
`516848f`): per-project mode in `project.yaml`; mode-is-live-state /
worker-stamps-on-write; `--mode` + precedence; persist-on-unload (with
the two rejected alternatives recorded — persist-on-change and
persist-on-any-command — and *why*). README ADR-0015 + ADR-0003
entries updated; `requirements.md` gains **L1b** (marked done).
- **ADR-0003 note**: the startup mode is no longer always `simple`.
## §5. What's open
### Bug reports still open (filed handoff-50)
| # | Title | Label |
|---|-------|-------|
| [11](https://github.com/oliversturm/rdbms-playground/issues/11) | Copy output panel contents to the system clipboard | enhancement |
**Closed this session:** #10, #14 (commits `ae57c6f`, `4cd574b`,
`516848f` — **close on GitHub once pushed**, along with the still-open-
on-GitHub #15/#16 from handoff-53).
### Categorisation / notes for pickup — #11
- **#11 is the only one left** from the handoff-50 bug-list trio. It is
a *different shape* from the recent run: it pulls in a **new
cross-platform clipboard crate** — the first new third-party
dependency in a while.
- **Security-Reviewer hat applies** (per the global standards: new
dependency → include it). Vet the crate before adopting: run the
mandated scanners (`trivy fs`, `grype`, `osv-scanner`, `cargo audit`/
`cargo deny` if available), check maintenance/popularity, and prefer a
well-maintained option (e.g. `arboard` is the common choice — confirm
current status, ~36 month freshness, and Wayland/X11/macOS/Windows
support). Escalate the crate choice to the user.
- Likely a new app-level command (`copy` / `yank`?) + a key binding —
**escalate the command surface** (name, sigil-free per ADR-0009, which
modes) before building. There is no ADR for clipboard yet; decide
whether it needs one (probably a short ADR, given it adds a dep and a
command).
### Other tracks (from `requirements.md`)
- Track 2 project storage Iter 5/6 — export/import shipped; `--resume`
shipped; **mode restore (L1b) shipped this session**; remaining Iter 6:
`history.log`-based persistent input-history hydration (§12),
migration-framework exercise.
- C3a (modify relationship), C4 (m:n convenience).
- H1 friendly DB-error layer (partial); tutorial/lesson system (needs
its own ADR); V4 session log + Markdown export; I1/I1b multi-line +
readline; I3 tab-completion polish; I4 highlighting beyond input echo.
## §6. Process pins (carried forward + this session's reinforcement)
- **`/runda` on the implementation, not just the design.** This session,
twice on #14 (semantics discrepancy → re-decided by user; missing
integration test → added red-first). Held from handoff-52/-53.
- **Red-first proof for every fix *and* every load-bearing test.** The
#14 integration test was proven by disabling the `set_mode` call and
watching it fail (`None` vs `Some(Advanced)`) before restoring.
- **Escalate genuine design choices — do not decide for the user.** This
session: #10's error-tag treatment + the two autonomous tag decisions;
#14's storage location (project.yaml vs db-meta vs new file), the
persist timing (the big one), and the `--mode` precedence. The user
re-decided the persist semantics *after* I had built the wrong one —
surfacing it beat shipping it.
- **Let the user simplify.** Twice on #14 the user removed complexity I
had added (the db-meta mirror; the over-selective persist rule). Lead
with the simplest correct design; flag low-confidence improvisation.
- **Commits on `main`, append-only, user-confirmed, no AI attribution.**
Each commit message was proposed and approved before `git commit`.
Push remains the user's step; five unpushed commits is a normal
working state.
- **insta snapshots capture glyphs, not colour** (handoff-aid for future
output-rendering work): colour-only changes to `render_output_line`
do not churn snapshots.
## §7. How to take over
1. Read this note, then `CLAUDE.md`, then `docs/requirements.md` (L1b is
now done), then `docs/adr/README.md` and any ADR you'll touch
(ADR-0037 + ADR-0015 each gained an Amendment 1 this session).
2. The codebase is on `main` at `516848f`, working tree clean, **5
commits ahead of origin** (all unpushed — see §1). Closing #15, #16,
#10, #14 on GitHub is pending the push (the user's step).
3. If continuing the bug-list run: **#11 is the only one left.** It is a
new-dependency task — engage the Security-Reviewer hat for the
clipboard crate, and escalate both the crate choice and the command
surface before building (§5). Consider whether it warrants a short
ADR (new dep + new command suggests yes).
4. Honour the process pins in §6 — `/runda` on implementation +
red-first + escalate-don't-decide are the three that earned their
keep this session.