You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy in the explorer today is a detail-pane feature that copies exactly one thing: the body of the record under the cursor. A tmux-style visual select and yank already ships (0.1.0a42, #140), and Textual's native mouse selection already works on the detail body — but a mouse selection has no reachable copy key outside macOS, the results list has no selection model at all, and nothing can yank a reference to a record rather than its text. This issue covers the remaining selection and yank surface: making an existing native selection copyable, giving visual mode a real binding, yanking a provenance citation, yanking a canonical ID once #80 lands, and selecting a range of records in the results list.
What ships today
Everything below is on master and was verified by reading src/agentgrep/ui/ and by driving App.run_test pilots. Version-sensitive observations were made against Textual 8.2.8; pyproject.toml floors textual>=3.2.0, so older resolutions may differ.
Detail pane, whole-record copy.DetailScroll (src/agentgrep/ui/widgets/detail.py) binds y → detail.copy_source and Y → detail.copy_rendered. Both delegate to the HUD layout in src/agentgrep/ui/layouts/_hud_detail_interaction.py. y copies _detail_body_text, the body already bounded by DETAIL_BODY_MAX_CHARS (64 * 1024 characters) and DETAIL_BODY_MAX_LINES (1000), both defined in src/agentgrep/_text.py, and notifies copied source or copied source (truncated). Y copies _detail_rendered_plain, the flattened rendered projection (Markdown flattened, JSON pretty-printed at indent=2 when the pretty-print is bounded), and always notifies copied rendered text.
Detail pane, visual select and yank.DetailScroll.on_key routes keys into handle_detail_visual_key before the stock bindings fire. Outside visual mode only v and space are claimed. Inside it: h/j/k/l and the arrows move the selection cursor, 0/home and $/end jump to the line edges, g/G jump to the document edges, v/space re-anchor, y/enter yank, and escape/q cancel. The yank reads the widget's native get_selection over a textual.selection.Selection and calls App.copy_to_clipboard. Selection is inclusive of the cell under the cursor, matching tmux copy-mode-vi. tests/test_detail_visual_yank.py locks this behaviour.
Entering visual mode swaps the pane to raw source._begin_detail_visual repaints the body Static with a plain Text of the bounded source so selection extraction is exact and identical for text, Markdown, and JSON bodies. Verified with a pilot: on a Markdown record the rendered view has no fences, pressing v makes ```python visible again, and escape restores the rendered view — while _detail_raw_mode stays False throughout. That is a deliberate trade-off documented in the method, not an accident, but it means the pane visibly changes shape when you start selecting.
Mouse selection works, and then dead-ends. The body is a plain Static with Textual's default ALLOW_SELECT = True. A pilot mouse-down plus a button-held MouseMove over #detail-body populates app.screen.selections with a real Selection, and mouse-up leaves it in place. Textual's Screen binds ctrl+c,super+c → screen.copy_text for exactly this case, but the HUD layout binds ctrl+c → smart_quit ("Stop / Quit"). Resolved bindings on the live screen confirm the shadowing: ctrl+c → smart_quit, super+c → screen.copy_text. Pressing ctrl+c with a live selection left the clipboard untouched and armed the confirm-exit gutter; super+c copied the selected substring. So the only key that copies a mouse selection is the super (Cmd) chord, which Linux and WSL terminals generally do not emit.
y ignores a native selection. With a live mouse selection on the body, pressing y copies the entire bounded body, not the selected span. Verified with a pilot.
The results list has no selection of any kind.SearchResultsList (src/agentgrep/ui/widgets/results.py) sets ALLOW_SELECT = False, so there is no text selection there. Its only cursor state is highlighted: reactive[int | None] — there is no anchor, no marked set, no range. With the results list focused and a row highlighted, y, Y, v, and space are all no-ops (verified by pilot: clipboard unchanged, highlight unchanged, screen.selections empty, app still running).
Nothing yanks metadata or a reference. The provenance block lives in a separate #detail-metaStatic and carries Agent, Kind, Store, Adapter, Timestamp, Model, and Path rows, plus Cwd / Repo / Worktree rows when the record carries an origin. Visual mode assigns screen.selections = {self._detail_body: ...}, replacing the whole map, so a visual selection can never span the header. Neither y nor Y includes any of it. There is no key that copies a path, a session id, or a record identifier.
The grep-log layout has no yank vocabulary.src/agentgrep/ui/layouts/greplog.py is a search input over a RichLog with four bindings, of which ctrl+c is app.quit. A pilot on that layout finds no y, Y, or v binding and no detail pane. Its RichLogis natively selectable (ALLOW_SELECT = True), and super+c still resolves to screen.copy_text — so it inherits the same dead-end as the HUD without any of the yank commands.
Discoverability and remappability are uneven.y and Y are real Bindings with ids, and detail.copy_source / detail.copy_rendered are both in REMAPPABLE_BINDING_IDS in src/agentgrep/ui/keymaps.py, so a user keymaps.toml can move them. Visual mode is not a Binding — it is intercepted in on_key, and no v binding exists anywhere in src/agentgrep/ui/ — so it appears in no footer and in no /keys help panel, and cannot be remapped. docs/tui/index.md and docs/tui/reference.md do not mention copy, yank, clipboard, or visual select anywhere; the only user-facing description is the 0.1.0a42 changelog entry.
How the clipboard actually works
Every copy path in src/ calls App.copy_to_clipboard and nothing else — three call sites, all in _hud_detail_interaction.py. In Textual 8.2.8 that method base64-encodes the string and writes one ESC ] 52 ; c ; <base64> BEL to the driver — plain OSC 52. Textual's own screen.copy_text funnels into the same method. There is no subprocess fallback: searching src/, tests/, docs/, and scripts/ for pyperclip, xclip, xsel, wl-copy, and pbcopy returns nothing (pyperclip appears in uv.lock only as a transitive dependency of fastmcp-slim, never imported). Nothing in the repository configures, probes, or documents OSC 52 behaviour.
Consequences worth stating here rather than rediscovering in review:
It is fire-and-forget. OSC 52 has no acknowledgement, so copied source is a claim that the sequence was written, not that a clipboard changed. There is currently no way for a user to tell a working terminal from a silent no-op.
ssh is not the problem; the terminal is. The sequence rides the tty stream rather than a local helper process, so it crosses ssh without a clipboard helper on the remote side. Failures come from terminals that do not implement OSC 52 — Textual's own docstring for copy_to_clipboard notes it does not work on macOS Terminal.
tmux needs opt-in.tmux(1) documents that it only attempts this when the terminfo description has an Ms entry, and its behaviour is governed by set-clipboard: on accepts the escape to create a tmux buffer and sets the terminal clipboard, external sets the terminal clipboard but ignores an application's attempt to set tmux buffers, and off does neither. tmux 3.7b started with an empty config reports external, so under that default a y does not populate a tmux buffer and whether anything lands at all depends on the outer terminal.
Payload size. A whole-body y is bounded at 64 * 1024 characters and 1000 lines, and base64 inflates by 4/3, so a full ASCII body goes out as roughly 85 KiB in one escape sequence — larger for non-ASCII text, because the cap counts characters while the encode runs over UTF-8 bytes. Whether every target terminal and multiplexer accepts a single OSC 52 write that large is not established here.
On the stylesheet question: scrollbar-size: 0 0 in src/agentgrep/ui/styles.tcss and the terminal-transparent ansi_color = True posture in src/agentgrep/ui/_shell.py do not obstruct selection. Textual's own selection is unaffected, and for the terminal's native drag-select a zero-width scrollbar is a mild benefit because no scrollbar glyph column contaminates the copied cells. The real interaction is that Textual enables mouse tracking, so the terminal's own drag-select is generally reachable only through a terminal-specific modifier (commonly Shift) — terminal behaviour agentgrep does not control and should not try to.
Gaps
A mouse selection cannot be copied outside macOS.ctrl+c is spent on stop/quit and super+c is unreachable on most Linux and WSL terminals. This is the sharpest gap: the selection renders, the user drags it, and no key takes it.
y with a live native selection copies the whole body. Two selection models coexist (visual-mode state and screen.selections) and only one of them is consulted by y.
No range select in the results list. There is no multi-select state to build on — highlighted is a single index.
No yank of a reference. No path, no session id, no canonical id. Everything that leaves the explorer is prose.
No yank of a citation. The provenance header cannot be selected and is not included in either copy command, so a pasted prompt arrives with no indication of which agent, store, or session it came from.
Y truncates silently.y distinguishes copied source from copied source (truncated); Y always says copied rendered text even though _detail_rendered_plain is derived from the already-truncated body.
Visual mode is undiscoverable and unremappable. No Binding, no id, no footer entry, no /keys entry, no docs.
The grep-log layout has no copy contract. If copy is part of the explorer's promise, a peer layout shipping none of the yank commands is a decision that should be made explicitly rather than by omission.
Relationship to other issues
This overlaps three open items conceptually and must not absorb any of their scope.
Export prompts and conversations #81 / PR Add portable record export across surfaces #122 (export). Export owns portable artifacts: format writers (NDJSON, Markdown, ML messages profiles), a deterministic total order, redaction policy, and file/MCP sinks. PR Add portable record export across surfaces #122 adds Binding("e", "export_selected", ...) to the same HUD module and exports one selected record or its observed thread. This issue must not add a format writer, an on-disk sink, a --format-style choice, or a second definition of what "selected" means for export. If results-list range select ships, export should be able to consume it later — but this issue does not change export's selection contract.
Find similar prompts and conversations #82 (find similar). Consumes records and identities; not a clipboard surface. No interaction beyond eventually seeding from a yanked reference.
Key-space contention is real.b (PR #121), e (PR #122), and y/Y/v/space (shipped) all occupy the same bare-letter HUD key space, and PR #122's export review pane binds y to Save and n to No. Any new bare letter proposed here needs to be checked against both open branches before it is chosen.
Constraints
ADR 0011 (docs/dev/adr/0011-non-blocking-tui-invariants.md) is the binding constraint on any bulk yank. NB-1 forbids blocking I/O and unbounded CPU on the pump; NB-5 keeps watchers and render / compose O(1); NB-9 hard-bounds inline fast-path work. The existing code already carries this reasoning: action_copy_detail_source deliberately encodes the already-truncated body rather than record.text because copy_to_clipboard base64-encodes on the calling pump thread, and _yank_detail_visual documents its extract as O(selected) over an already-bounded source.
The direct consequence for a multi-record yank: concatenating N record bodies, casefolding, sorting, or serializing them cannot happen on the pump. Assembly belongs in a thread=True worker (NB-2) with a stable group and exclusive=True where a newer action should cancel it (NB-6), and the pump-side callback may only hand an already-bounded string to copy_to_clipboard. A selection-state watcher that recomputes a preview over the whole selection would violate NB-5. Bulk row repaints go through stream_apply (NB-4).
ADR 0012 applies to the results widget: selection state is typed reactive on the widget (RW-4), leaves as a typed Message rather than a back-reference into siblings (RW-2), and RW-5 binds the widget to NB-1..NB-10.
ADR 0013 makes the grep-log layout a peer, not a lesser surface (PL-1, PL-5), so the copy contract question there is a real decision.
Privacy. The detail header already routes its Path row through format_compact_path (which calls format_display_path) and its origin rows through format_display_path directly. A citation yank must reuse those helpers so a raw absolute path never reaches a clipboard, a notification, or a log — it must not build its own path string.
Proposed scope
Ordered so each step is independently shippable and the blocked item is last.
Make an existing native selection copyable. Bind a key other than ctrl+c to Textual's screen.copy_text so a mouse drag on the detail body is not a dead end, and decide whether y should prefer a live screen.selections entry over the whole body. That second half is a behaviour change to something that shipped in 0.1.0a42, so it needs an explicit call rather than a silent switch.
Give visual mode a real Binding with an id. It then shows in the footer and /keys, and joins REMAPPABLE_BINDING_IDS so keymaps.toml can move it. Document the whole copy surface in docs/tui/, which currently says nothing about it.
Fix the Y truncation notice so both copy commands report truncation the same way.
Yank a citation. One key that copies the record's provenance (agent, store, timestamp, collapsed path) with or without the body, so a pasted prompt says where it came from.
Results-list range select and multi-yank. Add anchor-plus-extend selection state to SearchResultsList, render the marked rows, and yank the selected records with assembly on an offload worker under a named cap. Decide the truncation policy for a selection that exceeds the cap: refuse, truncate with a notice, or copy identifiers only.
A subprocess clipboard fallback. It would add a platform dependency to a surface that currently has none; if OSC 52 delivery proves insufficient it deserves its own issue with evidence.
Changing Textual's mouse capture or the terminal's native drag-select behaviour.
Open questions
Does the visual anchor land where the user is looking on a scrolled, rendered body? _visual_top_visible_row walks source lines against a scroll offset produced by the rendered view, and its own docstring calls the result "a close estimate for a markdown/code body". Not tested here; worth a pilot test before building on top of it.
Should y change meaning when a native selection is live, given it shipped with a different meaning?
Is a single roughly-85-KiB OSC 52 write accepted by every terminal and multiplexer agentgrep targets, and is a chunked or size-gated write needed?
Should multi-select reuse PR Add portable record export across surfaces #122's records / observed-thread vocabulary so a later export can consume it, or stay clipboard-local and vocabulary-free until a second consumer exists?
Is there any way to give the user feedback that a clipboard write actually landed, or does the notification stay a best-effort claim?
Acceptance criteria
A mouse-drag selection on the detail body can be copied with a key that Linux and WSL terminals deliver, without disturbing ctrl+c stop/quit staging.
The relationship between y and a live native selection is explicit, tested, and documented.
Visual mode is a real binding: it appears in /keys, and a keymaps.toml entry moves it.
y and Y report truncation identically.
A citation yank reuses format_display_path / format_compact_path and emits no raw absolute path, in the clipboard or in any notification or log.
Results-list range selection has typed reactive state on the widget, leaves as a typed Message, and its yank assembles off the pump under a named cap, exercised once with the explicit watchdog setting against a large real store.
docs/tui/ documents the full copy and selection surface, which today it does not mention at all.
Summary
Copy in the explorer today is a detail-pane feature that copies exactly one thing: the body of the record under the cursor. A tmux-style visual select and yank already ships (0.1.0a42, #140), and Textual's native mouse selection already works on the detail body — but a mouse selection has no reachable copy key outside macOS, the results list has no selection model at all, and nothing can yank a reference to a record rather than its text. This issue covers the remaining selection and yank surface: making an existing native selection copyable, giving visual mode a real binding, yanking a provenance citation, yanking a canonical ID once #80 lands, and selecting a range of records in the results list.
What ships today
Everything below is on
masterand was verified by readingsrc/agentgrep/ui/and by drivingApp.run_testpilots. Version-sensitive observations were made against Textual 8.2.8;pyproject.tomlfloorstextual>=3.2.0, so older resolutions may differ.Detail pane, whole-record copy.
DetailScroll(src/agentgrep/ui/widgets/detail.py) bindsy→detail.copy_sourceandY→detail.copy_rendered. Both delegate to the HUD layout insrc/agentgrep/ui/layouts/_hud_detail_interaction.py.ycopies_detail_body_text, the body already bounded byDETAIL_BODY_MAX_CHARS(64 * 1024 characters) andDETAIL_BODY_MAX_LINES(1000), both defined insrc/agentgrep/_text.py, and notifiescopied sourceorcopied source (truncated).Ycopies_detail_rendered_plain, the flattened rendered projection (Markdown flattened, JSON pretty-printed atindent=2when the pretty-print is bounded), and always notifiescopied rendered text.Detail pane, visual select and yank.
DetailScroll.on_keyroutes keys intohandle_detail_visual_keybefore the stock bindings fire. Outside visual mode onlyvandspaceare claimed. Inside it:h/j/k/land the arrows move the selection cursor,0/homeand$/endjump to the line edges,g/Gjump to the document edges,v/spacere-anchor,y/enteryank, andescape/qcancel. The yank reads the widget's nativeget_selectionover atextual.selection.Selectionand callsApp.copy_to_clipboard. Selection is inclusive of the cell under the cursor, matching tmux copy-mode-vi.tests/test_detail_visual_yank.pylocks this behaviour.Entering visual mode swaps the pane to raw source.
_begin_detail_visualrepaints the bodyStaticwith a plainTextof the bounded source so selection extraction is exact and identical for text, Markdown, and JSON bodies. Verified with a pilot: on a Markdown record the rendered view has no fences, pressingvmakes```pythonvisible again, andescaperestores the rendered view — while_detail_raw_modestaysFalsethroughout. That is a deliberate trade-off documented in the method, not an accident, but it means the pane visibly changes shape when you start selecting.Mouse selection works, and then dead-ends. The body is a plain
Staticwith Textual's defaultALLOW_SELECT = True. A pilot mouse-down plus a button-heldMouseMoveover#detail-bodypopulatesapp.screen.selectionswith a realSelection, and mouse-up leaves it in place. Textual'sScreenbindsctrl+c,super+c→screen.copy_textfor exactly this case, but the HUD layout bindsctrl+c→smart_quit("Stop / Quit"). Resolved bindings on the live screen confirm the shadowing:ctrl+c→smart_quit,super+c→screen.copy_text. Pressingctrl+cwith a live selection left the clipboard untouched and armed the confirm-exit gutter;super+ccopied the selected substring. So the only key that copies a mouse selection is thesuper(Cmd) chord, which Linux and WSL terminals generally do not emit.yignores a native selection. With a live mouse selection on the body, pressingycopies the entire bounded body, not the selected span. Verified with a pilot.The results list has no selection of any kind.
SearchResultsList(src/agentgrep/ui/widgets/results.py) setsALLOW_SELECT = False, so there is no text selection there. Its only cursor state ishighlighted: reactive[int | None]— there is no anchor, no marked set, no range. With the results list focused and a row highlighted,y,Y,v, andspaceare all no-ops (verified by pilot: clipboard unchanged, highlight unchanged,screen.selectionsempty, app still running).Nothing yanks metadata or a reference. The provenance block lives in a separate
#detail-metaStaticand carries Agent, Kind, Store, Adapter, Timestamp, Model, and Path rows, plus Cwd / Repo / Worktree rows when the record carries an origin. Visual mode assignsscreen.selections = {self._detail_body: ...}, replacing the whole map, so a visual selection can never span the header. NeitherynorYincludes any of it. There is no key that copies a path, a session id, or a record identifier.The grep-log layout has no yank vocabulary.
src/agentgrep/ui/layouts/greplog.pyis a search input over aRichLogwith four bindings, of whichctrl+cisapp.quit. A pilot on that layout finds noy,Y, orvbinding and no detail pane. ItsRichLogis natively selectable (ALLOW_SELECT = True), andsuper+cstill resolves toscreen.copy_text— so it inherits the same dead-end as the HUD without any of the yank commands.Discoverability and remappability are uneven.
yandYare realBindings with ids, anddetail.copy_source/detail.copy_renderedare both inREMAPPABLE_BINDING_IDSinsrc/agentgrep/ui/keymaps.py, so a userkeymaps.tomlcan move them. Visual mode is not aBinding— it is intercepted inon_key, and novbinding exists anywhere insrc/agentgrep/ui/— so it appears in no footer and in no/keyshelp panel, and cannot be remapped.docs/tui/index.mdanddocs/tui/reference.mddo not mention copy, yank, clipboard, or visual select anywhere; the only user-facing description is the 0.1.0a42 changelog entry.How the clipboard actually works
Every copy path in
src/callsApp.copy_to_clipboardand nothing else — three call sites, all in_hud_detail_interaction.py. In Textual 8.2.8 that method base64-encodes the string and writes oneESC ] 52 ; c ; <base64> BELto the driver — plain OSC 52. Textual's ownscreen.copy_textfunnels into the same method. There is no subprocess fallback: searchingsrc/,tests/,docs/, andscripts/forpyperclip,xclip,xsel,wl-copy, andpbcopyreturns nothing (pyperclipappears inuv.lockonly as a transitive dependency offastmcp-slim, never imported). Nothing in the repository configures, probes, or documents OSC 52 behaviour.Consequences worth stating here rather than rediscovering in review:
copied sourceis a claim that the sequence was written, not that a clipboard changed. There is currently no way for a user to tell a working terminal from a silent no-op.copy_to_clipboardnotes it does not work on macOS Terminal.tmux(1)documents that it only attempts this when the terminfo description has anMsentry, and its behaviour is governed byset-clipboard:onaccepts the escape to create a tmux buffer and sets the terminal clipboard,externalsets the terminal clipboard but ignores an application's attempt to set tmux buffers, andoffdoes neither. tmux 3.7b started with an empty config reportsexternal, so under that default aydoes not populate a tmux buffer and whether anything lands at all depends on the outer terminal.yis bounded at 64 * 1024 characters and 1000 lines, and base64 inflates by 4/3, so a full ASCII body goes out as roughly 85 KiB in one escape sequence — larger for non-ASCII text, because the cap counts characters while the encode runs over UTF-8 bytes. Whether every target terminal and multiplexer accepts a single OSC 52 write that large is not established here.On the stylesheet question:
scrollbar-size: 0 0insrc/agentgrep/ui/styles.tcssand the terminal-transparentansi_color = Trueposture insrc/agentgrep/ui/_shell.pydo not obstruct selection. Textual's own selection is unaffected, and for the terminal's native drag-select a zero-width scrollbar is a mild benefit because no scrollbar glyph column contaminates the copied cells. The real interaction is that Textual enables mouse tracking, so the terminal's own drag-select is generally reachable only through a terminal-specific modifier (commonly Shift) — terminal behaviour agentgrep does not control and should not try to.Gaps
ctrl+cis spent on stop/quit andsuper+cis unreachable on most Linux and WSL terminals. This is the sharpest gap: the selection renders, the user drags it, and no key takes it.ywith a live native selection copies the whole body. Two selection models coexist (visual-mode state andscreen.selections) and only one of them is consulted byy.highlightedis a single index.Ytruncates silently.ydistinguishescopied sourcefromcopied source (truncated);Yalways sayscopied rendered texteven though_detail_rendered_plainis derived from the already-truncated body.Binding, no id, no footer entry, no/keysentry, no docs.Relationship to other issues
This overlaps three open items conceptually and must not absorb any of their scope.
agc1:content,agr1:record, andagt1:thread handles and closes Deterministic IDs for conversations / prompts #80. "Yank a reference" is a consumer of that recipe. This issue must not mint an identity, must not pick between the two identity notions live on master (record_dedupe_keyinsrc/agentgrep/_engine/orchestration.pyandsearch_record_fingerprintinsrc/agentgrep/mcp/refs.py), and the reference-yank item is blocked until Deterministic IDs for conversations / prompts #80's recipe is settled.Binding("b", "toggle_bookmark", "Bookmark")tosrc/agentgrep/ui/layouts/hud.py.messagesprofiles), a deterministic total order, redaction policy, and file/MCP sinks. PR Add portable record export across surfaces #122 addsBinding("e", "export_selected", ...)to the same HUD module and exports one selected record or its observed thread. This issue must not add a format writer, an on-disk sink, a--format-style choice, or a second definition of what "selected" means for export. If results-list range select ships, export should be able to consume it later — but this issue does not change export's selection contract.hud.pyand the same HUD key space.Key-space contention is real.
b(PR #121),e(PR #122), andy/Y/v/space(shipped) all occupy the same bare-letter HUD key space, and PR #122's export review pane bindsyto Save andnto No. Any new bare letter proposed here needs to be checked against both open branches before it is chosen.Constraints
ADR 0011 (
docs/dev/adr/0011-non-blocking-tui-invariants.md) is the binding constraint on any bulk yank. NB-1 forbids blocking I/O and unbounded CPU on the pump; NB-5 keeps watchers andrender/composeO(1); NB-9 hard-bounds inline fast-path work. The existing code already carries this reasoning:action_copy_detail_sourcedeliberately encodes the already-truncated body rather thanrecord.textbecausecopy_to_clipboardbase64-encodes on the calling pump thread, and_yank_detail_visualdocuments its extract as O(selected) over an already-bounded source.The direct consequence for a multi-record yank: concatenating N record bodies, casefolding, sorting, or serializing them cannot happen on the pump. Assembly belongs in a
thread=Trueworker (NB-2) with a stablegroupandexclusive=Truewhere a newer action should cancel it (NB-6), and the pump-side callback may only hand an already-bounded string tocopy_to_clipboard. A selection-state watcher that recomputes a preview over the whole selection would violate NB-5. Bulk row repaints go throughstream_apply(NB-4).ADR 0012 applies to the results widget: selection state is typed reactive on the widget (RW-4), leaves as a typed
Messagerather than a back-reference into siblings (RW-2), and RW-5 binds the widget to NB-1..NB-10.ADR 0013 makes the grep-log layout a peer, not a lesser surface (PL-1, PL-5), so the copy contract question there is a real decision.
Privacy. The detail header already routes its Path row through
format_compact_path(which callsformat_display_path) and its origin rows throughformat_display_pathdirectly. A citation yank must reuse those helpers so a raw absolute path never reaches a clipboard, a notification, or a log — it must not build its own path string.Proposed scope
Ordered so each step is independently shippable and the blocked item is last.
ctrl+cto Textual'sscreen.copy_textso a mouse drag on the detail body is not a dead end, and decide whetheryshould prefer a livescreen.selectionsentry over the whole body. That second half is a behaviour change to something that shipped in 0.1.0a42, so it needs an explicit call rather than a silent switch.Bindingwith an id. It then shows in the footer and/keys, and joinsREMAPPABLE_BINDING_IDSsokeymaps.tomlcan move it. Document the whole copy surface indocs/tui/, which currently says nothing about it.Ytruncation notice so both copy commands report truncation the same way.SearchResultsList, render the marked rows, and yank the selected records with assembly on an offload worker under a named cap. Decide the truncation policy for a selection that exceeds the cap: refuse, truncate with a notice, or copy identifiers only.Out of scope
Open questions
_visual_top_visible_rowwalks source lines against a scroll offset produced by the rendered view, and its own docstring calls the result "a close estimate for a markdown/code body". Not tested here; worth a pilot test before building on top of it.ychange meaning when a native selection is live, given it shipped with a different meaning?Acceptance criteria
ctrl+cstop/quit staging.yand a live native selection is explicit, tested, and documented./keys, and akeymaps.tomlentry moves it.yandYreport truncation identically.format_display_path/format_compact_pathand emits no raw absolute path, in the clipboard or in any notification or log.Message, and its yank assembles off the pump under a named cap, exercised once with the explicit watchdog setting against a large real store.docs/tui/documents the full copy and selection surface, which today it does not mention at all.