docs: session handoff 55 — #11 resolved, ADR-0041 + arboard dep
This commit is contained in:
@@ -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;<base64>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).
|
||||
Reference in New Issue
Block a user