feat: copy the output panel to the system clipboard (#11)

New app-level `copy` / `copy all` / `copy last` command (ADR-0041).
Delivery is OSC 52 *and* a best-effort native write (arboard), always
both — OSC 52 acceptance is undetectable, so a true fallback can't be
built. Payload is the panel's plain text exactly as rendered (tags,
✓/✗, box-drawing), drift-locked to render_output_line. arboard added
--no-default-features (X11-only; OSC 52 covers Wayland).

Amends ADR-0003's command registry; requirements V6.
This commit is contained in:
claude@clouddev1
2026-06-02 14:23:21 +00:00
parent 1ea376be26
commit d0c8f9d5d2
25 changed files with 1203 additions and 13 deletions
@@ -0,0 +1,20 @@
---
source: tests/typing_surface/app_commands.rs
assertion_line: 146
description: "input=\"copy all\" cursor=8"
expression: "& a"
---
Assessment {
input: "copy all",
cursor: 8,
state: Valid,
hint: Some(
Prose(
"Submit with Enter",
),
),
completion: None,
parse_result: Ok(
"App(Copy)",
),
}
@@ -0,0 +1,20 @@
---
source: tests/typing_surface/app_commands.rs
assertion_line: 154
description: "input=\"copy last\" cursor=9"
expression: "& a"
---
Assessment {
input: "copy last",
cursor: 9,
state: Valid,
hint: Some(
Prose(
"Submit with Enter",
),
),
completion: None,
parse_result: Ok(
"App(Copy)",
),
}
@@ -0,0 +1,52 @@
---
source: tests/typing_surface/app_commands.rs
assertion_line: 161
description: "input=\"copy \" cursor=5"
expression: "& a"
---
Assessment {
input: "copy ",
cursor: 5,
state: Valid,
hint: Some(
Candidates {
items: [
Candidate {
text: "all",
kind: Keyword,
mode: Both,
},
Candidate {
text: "last",
kind: Keyword,
mode: Both,
},
],
selected: None,
},
),
completion: Some(
Completion {
replaced_range: (
5,
5,
),
partial_prefix: "",
candidates: [
Candidate {
text: "all",
kind: Keyword,
mode: Both,
},
Candidate {
text: "last",
kind: Keyword,
mode: Both,
},
],
},
),
parse_result: Ok(
"App(Copy)",
),
}
@@ -0,0 +1,20 @@
---
source: tests/typing_surface/app_commands.rs
assertion_line: 138
description: "input=\"copy\" cursor=4"
expression: "& a"
---
Assessment {
input: "copy",
cursor: 4,
state: Valid,
hint: Some(
Prose(
"Submit with Enter",
),
),
completion: None,
parse_result: Ok(
"App(Copy)",
),
}