Problem Statement
Users need to copy log content out of the TUI log viewer — for pasting into bug reports, Slack messages, scripts, or further analysis. Currently there is no clipboard integration at all; the only option is native terminal mouse selection, which is cumbersome when mouse capture is active (for scroll support) and impossible for copying filtered/structured data cleanly.
Technical Context
The TUI log viewer (crates/navigator-tui/src/ui/sandbox_logs.rs) renders structured log lines with timestamp, source, level, message, and key=value fields. It supports vim-style scrolling (j/k/g/G), autoscroll/follow mode, source filtering, and a detail popup for individual lines. The viewer already tracks a cursor position (log_cursor) and scroll offset (sandbox_log_scroll) over a filtered view of Vec<LogLine>. No clipboard or selection code exists today.
Affected Components
| Component |
Key Files |
Role |
| App state |
crates/navigator-tui/src/app.rs |
LogLine struct (L51-59), log state fields (L347-359), key handler handle_logs_key (L887-962) |
| Log renderer |
crates/navigator-tui/src/ui/sandbox_logs.rs |
draw() (L11-104), render_log_line() (L215-250), detail popup (L110-188) |
| Event system |
crates/navigator-tui/src/event.rs |
Event enum (L12-35) — may need new variant for copy feedback |
| Nav bar / chrome |
crates/navigator-tui/src/ui/mod.rs |
Key hints for SandboxLogs focus (L226-264) |
| Main loop |
crates/navigator-tui/src/lib.rs |
Mouse event handling (L224-230), log stream management |
Technical Investigation
Current Log Viewer Architecture
Data model: Vec<LogLine> stored on App, filtered by LogSourceFilter via filtered_log_lines() (app.rs:472-481). Each LogLine has: timestamp_ms, level, source, target, message, fields: HashMap<String, String>.
Viewport model: sandbox_log_scroll (first visible line index in filtered list) + log_cursor (offset from scroll position). Absolute selected index = scroll + cursor. log_viewport_height is set each frame by the draw pass (area.height - 2 for borders).
Rendering: Each line is rendered as styled spans: HH:MM:SS {source:7} {level:5} {message} {key=value ...}, truncated to viewport width with …. The detail popup shows the full untruncated content.
Key bindings in use: j/k (navigate), g/G (top/bottom), f (follow), s (source filter), Enter (detail popup), Esc (back), q (quit), r (rules), p (policy). Available keys: y, v, Y, c, C, Shift+arrows, Ctrl+O, Ctrl+S are all unbound.
Clipboard Mechanism: Manual OSC 52
Write \x1b]52;c;{base64}\x07 directly to stdout. The terminal emulator intercepts this and copies to the system clipboard.
- Works over SSH/tmux/mosh — the escape sequence forwards through the connection to the local terminal
- Zero new dependencies — only needs base64 encoding
- Supported by: iTerm2, Alacritty, Kitty, WezTerm, Windows Terminal, Konsole, foot, xterm
- Limitation: Write-only (no paste), fire-and-forget (no success/failure feedback), some terminals disable by default
- Decision: Manual implementation (~5 lines), not upgrading crossterm from 0.28 to 0.29 for built-in support
Decisions Made
| Decision |
Resolution |
| Clipboard format |
Match display format as-is: HH:MM:SS {source:7} {level:5} {message} {key=value ...} |
| Copy feedback |
No feedback toast/status message — silent fire-and-forget |
| crossterm upgrade |
Manual OSC 52 implementation, stay on crossterm 0.28 |
Copy UX Features
Feature 1: Copy Current Line (y)
Single keypress copies the line at the cursor.
- Key:
y (vim yank convention)
- What's copied: Plain text rendering of the log line matching display format, ANSI codes stripped
- Implementation: Read
filtered_log_lines()[scroll + cursor], format as plain text, write via OSC 52
- Complexity: Low — ~30 lines of new code
Feature 2: Copy Viewport (Y)
Copy all lines currently visible in the viewport.
- Key:
Y (capital Y — "yank all visible")
- What's copied: All visible lines joined with newlines, plain text
- Implementation: Slice
filtered_log_lines()[scroll..scroll+viewport_height], format each, join with \n
- Complexity: Low — reuses the single-line formatter
Feature 3: Visual Selection Mode (v + j/k + y)
Vim-style visual mode for selecting a range of lines.
- Enter:
v to enter visual/selection mode
- Extend:
j/k/Up/Down/g/G move the cursor while anchor stays fixed
- Copy:
y yanks the selected range
- Cancel:
Esc exits visual mode without copying
- Visual indicator: Selected lines get a distinct background highlight (new
Theme field, e.g., selection style)
- Status bar: Show
"VISUAL" mode indicator plus line count "3 lines selected"
State model additions:
selection_mode: bool
selection_anchor: Option<usize> // absolute index in filtered list where 'v' was pressed
Selected range = min(anchor, cursor)..=max(anchor, cursor) in the filtered log lines.
- Complexity: Medium — new mode, modified key dispatch, selection rendering, ~100-150 lines
Feature 4: Shift+Arrow Range Select (Alternative to v-mode)
- Key:
Shift+Up / Shift+Down extends selection from current cursor
- Copy:
y yanks, Esc clears selection
- Note: Some terminals don't distinguish
Shift+Up from Up — less reliable than v-mode
- Complexity: Low incremental if
v-mode exists — additional entry point into selection state
Feature 5: Export to File (Ctrl+S / :export)
For large log volumes where clipboard is impractical.
- Key:
Ctrl+S (k9s screendump convention) or :export command
- What's exported: All filtered log lines to a timestamped file
- Path:
$TMPDIR/openshell-logs-{sandbox}-{timestamp}.log
- Complexity: Low-Medium — file I/O, path formatting, ~50 lines
Code References
| Location |
Description |
app.rs:51-59 |
LogLine struct — needs a to_plain_text() or Display impl for clipboard formatting |
app.rs:347-359 |
Log state fields — needs selection_mode, selection_anchor additions |
app.rs:887-962 |
handle_logs_key() — add y/Y/v key arms, modify j/k to extend selection in visual mode |
sandbox_logs.rs:60-76 |
Line rendering loop — needs selection highlight logic (distinct from cursor highlight) |
sandbox_logs.rs:79-97 |
Status bar — needs mode indicator ("VISUAL") and selection count |
sandbox_logs.rs:215-250 |
render_log_line() — reuse for plain-text clipboard formatting (strip styles) |
ui/mod.rs:226-264 |
Nav bar hints — add [y] Copy [v] Select [Y] Copy View hints |
theme.rs |
Need a selection style for highlighted selected lines (distinct from log_cursor) |
Patterns to Follow
- k9s autoscroll pattern is already implemented — selection mode should auto-pause autoscroll
- Detail popup pattern (Enter to open, Esc to close) — copy could work from within the popup too
- Nav bar key hints are context-sensitive — selection mode should show different hints
- InputMode enum already exists for command mode — visual/selection mode is analogous
- Status feedback pattern:
status_text on App is used for transient messages
Proposed Approach
Implement in phases: (1) y to copy current line and Y to copy viewport using manual OSC 52 — covers the most common use case with minimal code. (2) v-mode visual selection with j/k to extend and y to yank — covers multi-line selection. (3) Optionally Shift+Up/Down as alternative selection entry and Ctrl+S / :export for file export. Clipboard format matches the display rendering. No new crate dependencies. No copy feedback UI.
Scope Assessment
- Complexity: Medium (Phase 1 is Low, Phase 2 is Medium)
- Confidence: High — clear path, well-understood patterns, no architectural unknowns
- Estimated files to change: 5 (app.rs, sandbox_logs.rs, ui/mod.rs, theme.rs, event.rs)
- Issue type:
feat
Risks & Open Questions
- OSC 52 terminal coverage: Not all terminals support it (notably older macOS Terminal.app versions, some Linux TTYs). Silent failure — the sequence is just ignored. Users on unsupported terminals still have native
Shift+click selection as fallback.
- Large clipboard payloads: Some terminals truncate OSC 52 payloads (e.g., xterm limits to ~100KB base64). File export (
Ctrl+S) is the right mechanism for large volumes.
- Mouse capture interaction: When mouse capture is on, native terminal selection is blocked. Adding
v-mode is the right answer. Shift+click bypasses mouse capture in most terminals as a fallback.
- Selection mode + autoscroll conflict: Visual selection mode must auto-pause autoscroll to prevent the selection from shifting under the user.
- Shift+arrow reliability: Some terminals don't report
Shift+Up distinctly from Up. v-mode is the primary mechanism; shift+arrow is best-effort.
Test Considerations
- Unit tests for plain text formatting: Test
LogLine::to_plain_text() output for various line shapes (with/without fields, long messages, special characters)
- Unit tests for selection range math: Test
selection_anchor + cursor → range calculation, edge cases (anchor == cursor, anchor > cursor, empty log list)
- Integration: Manual testing is primary — clipboard operations are inherently side-effectful. Test across iTerm2, Alacritty, and at least one Linux terminal. Test over SSH.
- No existing test infrastructure for the TUI crate — tests would be the first in this crate
Created by spike investigation. Use build-from-issue to plan and implement.
Problem Statement
Users need to copy log content out of the TUI log viewer — for pasting into bug reports, Slack messages, scripts, or further analysis. Currently there is no clipboard integration at all; the only option is native terminal mouse selection, which is cumbersome when mouse capture is active (for scroll support) and impossible for copying filtered/structured data cleanly.
Technical Context
The TUI log viewer (
crates/navigator-tui/src/ui/sandbox_logs.rs) renders structured log lines with timestamp, source, level, message, and key=value fields. It supports vim-style scrolling (j/k/g/G), autoscroll/follow mode, source filtering, and a detail popup for individual lines. The viewer already tracks a cursor position (log_cursor) and scroll offset (sandbox_log_scroll) over a filtered view ofVec<LogLine>. No clipboard or selection code exists today.Affected Components
crates/navigator-tui/src/app.rshandle_logs_key(L887-962)crates/navigator-tui/src/ui/sandbox_logs.rsdraw()(L11-104),render_log_line()(L215-250), detail popup (L110-188)crates/navigator-tui/src/event.rscrates/navigator-tui/src/ui/mod.rscrates/navigator-tui/src/lib.rsTechnical Investigation
Current Log Viewer Architecture
Data model:
Vec<LogLine>stored onApp, filtered byLogSourceFilterviafiltered_log_lines()(app.rs:472-481). EachLogLinehas:timestamp_ms,level,source,target,message,fields: HashMap<String, String>.Viewport model:
sandbox_log_scroll(first visible line index in filtered list) +log_cursor(offset from scroll position). Absolute selected index =scroll + cursor.log_viewport_heightis set each frame by the draw pass (area.height - 2 for borders).Rendering: Each line is rendered as styled spans:
HH:MM:SS {source:7} {level:5} {message} {key=value ...}, truncated to viewport width with…. The detail popup shows the full untruncated content.Key bindings in use:
j/k(navigate),g/G(top/bottom),f(follow),s(source filter),Enter(detail popup),Esc(back),q(quit),r(rules),p(policy). Available keys:y,v,Y,c,C,Shift+arrows,Ctrl+O,Ctrl+Sare all unbound.Clipboard Mechanism: Manual OSC 52
Write
\x1b]52;c;{base64}\x07directly to stdout. The terminal emulator intercepts this and copies to the system clipboard.Decisions Made
HH:MM:SS {source:7} {level:5} {message} {key=value ...}Copy UX Features
Feature 1: Copy Current Line (
y)Single keypress copies the line at the cursor.
y(vim yank convention)filtered_log_lines()[scroll + cursor], format as plain text, write via OSC 52Feature 2: Copy Viewport (
Y)Copy all lines currently visible in the viewport.
Y(capital Y — "yank all visible")filtered_log_lines()[scroll..scroll+viewport_height], format each, join with\nFeature 3: Visual Selection Mode (
v+j/k+y)Vim-style visual mode for selecting a range of lines.
vto enter visual/selection modej/k/Up/Down/g/Gmove the cursor while anchor stays fixedyyanks the selected rangeEscexits visual mode without copyingThemefield, e.g.,selectionstyle)"VISUAL"mode indicator plus line count"3 lines selected"State model additions:
Selected range =
min(anchor, cursor)..=max(anchor, cursor)in the filtered log lines.Feature 4: Shift+Arrow Range Select (Alternative to
v-mode)Shift+Up/Shift+Downextends selection from current cursoryyanks,Escclears selectionShift+UpfromUp— less reliable thanv-modev-mode exists — additional entry point into selection stateFeature 5: Export to File (
Ctrl+S/:export)For large log volumes where clipboard is impractical.
Ctrl+S(k9s screendump convention) or:exportcommand$TMPDIR/openshell-logs-{sandbox}-{timestamp}.logCode References
app.rs:51-59LogLinestruct — needs ato_plain_text()orDisplayimpl for clipboard formattingapp.rs:347-359selection_mode,selection_anchoradditionsapp.rs:887-962handle_logs_key()— addy/Y/vkey arms, modifyj/kto extend selection in visual modesandbox_logs.rs:60-76sandbox_logs.rs:79-97sandbox_logs.rs:215-250render_log_line()— reuse for plain-text clipboard formatting (strip styles)ui/mod.rs:226-264[y] Copy [v] Select [Y] Copy Viewhintstheme.rsselectionstyle for highlighted selected lines (distinct fromlog_cursor)Patterns to Follow
status_texton App is used for transient messagesProposed Approach
Implement in phases: (1)
yto copy current line andYto copy viewport using manual OSC 52 — covers the most common use case with minimal code. (2)v-mode visual selection withj/kto extend andyto yank — covers multi-line selection. (3) OptionallyShift+Up/Downas alternative selection entry andCtrl+S/:exportfor file export. Clipboard format matches the display rendering. No new crate dependencies. No copy feedback UI.Scope Assessment
featRisks & Open Questions
Shift+clickselection as fallback.Ctrl+S) is the right mechanism for large volumes.v-mode is the right answer.Shift+clickbypasses mouse capture in most terminals as a fallback.Shift+Updistinctly fromUp.v-mode is the primary mechanism; shift+arrow is best-effort.Test Considerations
LogLine::to_plain_text()output for various line shapes (with/without fields, long messages, special characters)selection_anchor+ cursor → range calculation, edge cases (anchor == cursor, anchor > cursor, empty log list)Created by spike investigation. Use
build-from-issueto plan and implement.