diff --git a/docs/handoff/20260602-handoff-55.md b/docs/handoff/20260602-handoff-55.md new file mode 100644 index 0000000..df1d745 --- /dev/null +++ b/docs/handoff/20260602-handoff-55.md @@ -0,0 +1,199 @@ +# Session handoff — 2026-06-02 (55) + +Fifty-fifth handover. **One issue resolved: #11 — copy the output +panel to the system clipboard.** New ADR-0041; new `copy` app-command; +first native-clipboard dependency (`arboard`). The session is a clean +run of the full phased method: all four design axes escalated to and +decided by the user (twice — a first round of four, then a second +round of two once a hard technical constraint surfaced), `/runda` on +the **design** before any code, test-first build, full-stack PTY +verification at the end. + +## §1. State at handoff + +**Branch:** `main`. **HEAD `1ea376b`** (handoff-54). **3 commits ahead +of `origin/main`** at session start — the three session-54 commits +(`4cd574b`, `516848f`, `1ea376b`); handoff-53's `#15/#16` and earlier +are now pushed (origin advanced since handoff-54 was written). + +**This session's #11 work is committed on top** (see the commit that +accompanies this handoff). **Tests: 2151 passing / 0 failing / 1 +ignored** (full `cargo test`; lib-only **1578**). Baseline at session +start was **2134 / 0 / 1** → **+17** new tests, no regressions, no new +skips. The 1 ignored is the same long-standing `` ```ignore `` doctest +in `src/friendly/mod.rs`. **Clippy: clean** (nursery, all targets, +`-D warnings`). Push is the user's step. + +## §2. #11 — copy the output panel to the clipboard (ADR-0041) + +**Friction removed:** filing a bug report meant terminal-selecting the +output panel and fighting wrapping/borders. Now: a typed command. + +**Command (app-level, sigil-free, both modes — ADR-0003 registry):** + +| Form | Copies | +|------|--------| +| `copy`, `copy all` | the whole output panel | +| `copy last` | from the most recent echo line to the end | + +`copy last` boundary is fuzzy by design: app commands push echo-less +`[system]` lines, so "from the last echo" reaches past them to the +previous DSL command — documented + accepted in ADR-0041. + +**Mechanism — OSC 52 *and* native `arboard`, ALWAYS BOTH.** The +deciding constraint (surfaced in escalation round 2): OSC 52 acceptance +is **undetectable** — the terminal sends no ack — so a literal "fall +back when OSC 52 is unsupported" *cannot be built*. So both fire every +time: emit the OSC 52 escape (`ESC ]52;c;BEL`; tmux-passthrough +wrapped when `$TMUX` set), then a best-effort native write whose +failure is ignored (headless host → OSC 52 carried it). Identical +content, so the local double-write is harmless. OSC 52 needs **no new +dep** (`base64` + `crossterm` already present); it works over SSH. + +**Format — plain text verbatim *as rendered*:** each line's +`[tag] body ✓/✗` joined by `\n`, **without** the viewport's right-edge +padding or soft-wrapping (full logical lines, reflow-friendly, no +trailing whitespace). A **drift-lock test** (`ui.rs` +`plain_text_matches_rendered_line_content`) pins `OutputLine:: +plain_text` to the concatenated span content of `render_output_line`, +so the copy can never silently diverge from the screen. + +**Files (6 new, 18 modified):** +- `src/clipboard.rs` *(new)* — pure `osc52_sequence` / `emit_osc52` / + `in_tmux`, the `NativeClipboard` trait + `deliver`, and the + `SystemClipboard` arboard impl (lazy-init, kept alive — X11 serves + the selection from a thread owned by the handle). +- `src/action.rs` — `Action::CopyToClipboard(String)`. +- `src/app.rs` — `OutputLine::plain_text`, `App::copy_text`, + `handle_copy_command`, dispatch arm + 6 Tier-1 tests. +- `src/dsl/command.rs` / `dsl/mod.rs` — `AppCommand::Copy { scope }` + + `CopyScope { All, Last }` + re-export + source mapping. +- `src/dsl/grammar/app.rs` + `grammar/mod.rs` — `COPY` CommandNode + (`copy [all|last]`, unknown→`copy.unknown`), REGISTRY entry. +- `src/runtime.rs` — long-lived `SystemClipboard` + the + `Action::CopyToClipboard` arm (thin call to `clipboard::deliver`). +- `src/ui.rs` — the drift-lock test. +- `src/friendly/strings/en-US.yaml` + `friendly/keys.rs` — + `copy.done`/`copy.nothing`/`copy.unknown`, `help.app.copy`, + `parse.usage.copy`, CLI `--help` banner line. +- `src/completion.rs` — `copy` added to the app-keyword test list + (the candidate itself is REGISTRY-driven). +- `tests/typing_surface/{mod,app_commands}.rs` + 4 new snapshots. +- `Cargo.toml` / `Cargo.lock` — `arboard` `--no-default-features`. +- `docs/adr/0041-*` *(new)*, `docs/adr/0003-*` (registry note), + `docs/adr/README.md` (index + 0003 note), `docs/requirements.md` + (new **V6**, done). + +**Coverage (17):** clipboard module 6 (exact OSC 52 bytes, base64 +round-trip incl. non-ASCII, tmux wrap, `deliver` both-paths + +writer-error-still-fires-native); app 6 (all / bare-=-all / last-slice +/ empty-nothing / no-echo-nothing / unknown-target-friendly-error); +drift-lock 1; typing-surface 4 (copy / copy all / copy last parse, +`copy ` offers all+last). + +**Full-stack PTY verification (the real binary, not a mock):** drove +`target/debug/rdbms-playground` through a pseudo-terminal, typed +`create table …` then `copy`, and confirmed the OSC 52 escape reached +the tty with a base64 payload decoding to the exact on-screen content +(echo + `✓` + `[system]` structure + box-drawing table). This is the +end-to-end proof the unit seams don't give on their own. + +## §3. The arboard decision — current state (READ if revisiting) + +`arboard` `3.6.1` (1Password, `MIT OR Apache-2.0`), added +**`--no-default-features`**. Two deliberate choices are **flagged to +the user as open-for-correction** — they were made on low-stakes, +principle-aligned grounds and explicitly surfaced, not silently +decided: + +1. **No `wayland-data-control` feature → X11-only native on Linux.** + That feature ~doubles the dep tree (~30 crates: `wl-clipboard-rs`, + `wayland-*`, `quick-xml`, `nom`, `petgraph`). Since "always do both" + makes **OSC 52 the universal path** (covers native-Wayland + regardless) and most Wayland desktops run XWayland (so plain + `x11rb` works), the feature was omitted to keep the dependency + surface minimal (secure-by-default). **If a concrete + native-Wayland-without-OSC-52 need appears, add the feature** — one + line in `Cargo.toml`. + +2. **Copy is verbatim on-screen, including the per-line `[system]` + tags** (the PTY payload shows `[system] ┌──…`). This is the literal + reading of the user's "exactly what's rendered" choice. **If tag + stripping on table rows is wanted, that's a `plain_text` change** + (+ re-baseline the drift-lock test). + +**Security posture (re-run before any dep bump):** arboard introduces +**zero new vulnerabilities** — `cargo audit`, `osv-scanner`, and +`grype` are all clean for arboard's tree (`log` / `parking_lot` / +`percent-encoding` / `x11rb`, with `gethostname`/`rustix` already ours). +Write-only (no OSC 52 *read* — the exfiltration vector is out of play). +`gitleaks` + `trivy` clean on the new code. + +**Pre-existing, orthogonal, FLAGGED not fixed:** `osv-scanner` / +`cargo audit` report **RUSTSEC-2025-0067 (`libyml`)** and +**RUSTSEC-2025-0068 (`serde_yml`)**. These are a **pre-existing direct +dependency** (`Cargo.toml` `serde_yml = "0.0.12"` → `libyml`), **not +pulled by arboard** (`cargo tree -i serde_yml` shows only +rdbms-playground as parent), known since **handoff-14**. cargo audit +treats them as allowed warnings (exit 0). **Out of scope for #11**; +fixing means migrating off `serde_yml` (the YAML lib behind +`project.yaml`) — its own task, needs user sign-off. + +## §4. Process notes (what worked) + +- **Escalate genuine forks — don't decide.** Six questions across two + rounds. Round 1: mechanism / scope / trigger / format. Round 2 was + *forced by honesty* — the chosen "OSC 52 + native **fallback**" is + not literally buildable (no ack), so the coordination had to be + re-decided (→ "always do both") and the trigger confirmed. Surfacing + the constraint beat shipping a thing that didn't match the label. +- **`/runda` on the design.** The DA pass on ADR-0041 found: the + `copy last` boundary fuzziness (→ documented), the "exactly on + screen" overclaim (→ reworded to "as rendered, not padded"), and the + full-stack-coverage obligation (→ `deliver` both-paths test + PTY + run). Folded in before any code. +- **Test-first + drift-lock.** The renderer composes the visible line + at render time (tag, `running:`→`✓/✗`, teaching prefix), so copying + raw `OutputLine.text` would be wrong; `plain_text` mirrors the + renderer and the drift-lock test guarantees they stay in step. +- **Verify by running the app.** The PTY harness proved the seam the + unit tests can't (OSC 52 bytes actually reaching a real tty). + +## §5. What's open + +### Bug reports / enhancements + +**#11 is resolved this session.** The handoff-50 bug-list trio +(#10/#11/#14) is now fully closed. No open issues remain on the +recent list — **confirm on GitHub after push.** + +### Other tracks (from `requirements.md`) + +- Track 2 Iter 6 leftovers: `history.log`-based persistent + input-history hydration polish (§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 (the broader + output-panel UX — V6/clipboard is a complementary "get the session + out" path now shipped); I1/I1b multi-line + readline; I3 + tab-completion polish; I4 highlighting beyond input echo. +- **Pre-existing security debt:** migrate off `serde_yml`/`libyml` + (RUSTSEC-2025-0067/0068) — needs user sign-off (§3). + +## §6. How to take over + +1. Read this note, then `CLAUDE.md`, then `docs/requirements.md` + (V6 now done), then `docs/adr/README.md` and any ADR you'll touch + (ADR-0041 is new; ADR-0003 gained a registry note). +2. The codebase is on `main`; this session's #11 commit sits on top of + the 3 unpushed session-54 commits. Closing **#11** on GitHub is + pending the push (the user's step). +3. If revisiting the clipboard: §3 has the two open-for-correction + arboard decisions (no-Wayland-feature; verbatim-with-tags) and the + security posture. Re-run the scanners before any dep bump. +4. Honour the process pins: escalate genuine forks (and surface + technical constraints that contradict a chosen option's label), + `/runda` the design *and* implementation, test-first with a + drift-lock where a copy mirrors a renderer, and verify by running + the real app (the PTY harness pattern is reusable for any + terminal-side behaviour).