Files
claude@clouddev1 1ea376be26 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).
2026-06-02 08:11:01 +00:00

14 KiB
Raw Permalink Blame History

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::Quitset_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::PersistModeDatabase::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_modeNone 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 Copy output panel contents to the system clipboard enhancement

Closed this session: #10, #14 (commits ae57c6f, 4cd574b, 516848fclose 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.