Skip to content

fix(desktop): make terminal output selectable - #4980

Merged
wesbillman merged 6 commits into
mainfrom
carl/terminal-selection
Aug 7, 2026
Merged

fix(desktop): make terminal output selectable#4980
wesbillman merged 6 commits into
mainfrom
carl/terminal-selection

Conversation

@wesbillman

Copy link
Copy Markdown
Collaborator

Summary

  • mirror the retained canvas terminal grid into a transparent, selectable text layer
  • preserve the canvas renderer and terminal focus behavior for ordinary clicks
  • reconstruct wide and combining glyphs correctly for clipboard text

Why

Buzz Term renders output entirely on a canvas and deliberately called preventDefault() on viewport mouse-down, so native selection and copy could not work. A canvas has no selectable text even if that cancellation is removed.

The transparent text layer stays aligned with the visible cell grid, lets WebView native selection drive drag highlighting and copy, and follows active-session switches without changing the renderer or PTY protocol.

Validation

  • pnpm --dir desktop typecheck
  • pnpm --dir desktop test — 4,373 passed
  • pre-push desktop-check, desktop-test, and branch-skew hooks passed on 1f2a3f8db63f6fe36b4a28bc911aea3c5186b2b0

Mirror the retained canvas grid into a transparent text layer so native
mouse selection and clipboard copy work without replacing the terminal's
canvas renderer.

Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
@wesbillman
wesbillman requested a review from a team as a code owner August 5, 2026 23:52
@wesbillman

Copy link
Copy Markdown
Collaborator Author

[P1] Preserve soft-wrap boundaries before exposing clipboard textTerminalGrid.text() unconditionally joins every screen row with "\n". That makes a single terminal line which merely wrapped at the viewport edge copy as multiple newline-delimited commands/records. For example, the backend's existing 5-column fixture feeds abcdef, rendering abcde on row 0 and f on row 1 (desktop/src-tauri/crates/buzz-terminal/tests/clusters.rs:290-313); selecting both rows through this layer copies abcde\nf, not abcdef. This is especially dangerous for copying wrapped shell commands because pasting the result can execute the fragments separately. The backend knows soft wraps via Alacritty's WRAPLINE, but deliberately removes that geometry bit before constructing Style (desktop/src-tauri/crates/buzz-terminal/src/damage.rs:443-466), so the frontend currently cannot distinguish a soft wrap from a real newline. Please retain/transport row-wrap metadata and have the selection serialization omit \n after soft-wrapped rows, with an end-to-end fixture covering both soft wrap and a real newline.

Carry terminal row wrap geometry through the retained-grid transport so
selection serialization joins visual rows only when they belong to the same
logical line.

Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
@wesbillman

Copy link
Copy Markdown
Collaborator Author

[P1] Do not trim spaces from a soft-wrapped row — The wrap separator is now correct, but text() still calls trimEnd() before deciding whether row.wrapped is true. A terminal row can wrap on a literal space: in a 5-column terminal, output such as abcd ef fills row 0 with abcd , marks that row WRAPLINE when e advances to row 1, and then continues with ef. This serializer turns those rows into abcd + ef, so the clipboard contains abcdef rather than abcd ef. That can still mutate copied shell commands and structured output. For a wrapped row, serialize the retained cells without trimming; only hard-ended rows may drop terminal padding. Please make the frontend regression realistic by filling the soft-wrapped row to its terminal width and include a boundary-space case. The incremental retention path itself looks correct: wrap state is replaced with each changed row, and the backend hash already includes cell.flags.bits(), so a wrap-only transition cannot be deduplicated away.

wesbillman and others added 4 commits August 5, 2026 18:14
Only trim terminal padding from hard-ended rows. A boundary space on a
soft-wrapped row is command data and must survive selection serialization.

Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Render one selectable DOM row per terminal screen row for accurate native
hit testing, then serialize the selected cell range on copy so soft wraps
remain absent from clipboard text.

Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Clamp native DOM range endpoints to retained grapheme boundaries before
serializing clipboard text, and keep empty-row placeholder offsets within
the terminal model.

Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Bring in the upstream desktop smoke selector fix and current branch base.

Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
@wesbillman
wesbillman force-pushed the carl/terminal-selection branch from a2cfe63 to 2ff44a8 Compare August 7, 2026 16:00
@wesbillman

Copy link
Copy Markdown
Collaborator Author

Fresh safety review by Carl on Wes’s behalf at exact head 2ff44a805a471f28422f2df73e96ea5ba821090a: no code blocker found.

I re-traced the full backend-to-DOM path rather than inheriting the earlier verdict: Alacritty WRAPLINE capture, damage hashing and incremental retention, Rust wire mapping, per-session grid switching/resizing, transparent row alignment, forward/reverse DOM ranges, grapheme-boundary normalization, hard-break versus soft-wrap serialization, and ordinary click-to-focus behavior. The two prior copy-corruption blockers remain fixed, including literal spaces at soft-wrap boundaries.

Focused validation on this exact head: 31/31 terminal renderer/substrate tests passed; cargo test -p buzz-terminal --tests passed (27 crate unit + 64 integration tests, one intentional performance test ignored); pnpm typecheck passed; Biome found only the pre-existing !important warning outside this diff; git diff --check passed; worktree clean. Current CI has build, Rust lint, all four smoke shards, DCO, Semgrep, and zizmor green; Desktop Core, relay E2E, and Windows Rust are still pending.

Risk assessment: additive, desktop-terminal scoped, and backward-compatible inside this repository. It adds one boolean to the internal Tauri frame payload and preserves the existing canvas renderer/PTTY input path; no public protocol, persisted data, dependency version, or terminal command semantics change. Merge only after the remaining exact-head CI gates are green. GitHub cannot accept an approval review from the PR owner’s authenticated account, so this comment records the independent verdict.

@wesbillman
wesbillman merged commit cc9a2f7 into main Aug 7, 2026
26 checks passed
@wesbillman
wesbillman deleted the carl/terminal-selection branch August 7, 2026 16:30
atishpatel added a commit that referenced this pull request Aug 7, 2026
…-log-harness

* origin/main:
  feat(desktop): adding rich link previews to messages (#3818)
  fix(buzz-agent): Responses reasoning summary, Anthropic display:summarized, ACP v2 messageId (#5195)
  fix(desktop): retain distinct agent instances in autocomplete (#5202)
  fix(desktop): defer channel visibility change to Save (#5203)
  feat(desktop): Projects follow-ups — access restrictions, fast loading, activity feed polish (#5073)
  refactor(cli): replace probe/decider/detail split with single typed extractor (#5191)
  fix(desktop): drop unhandled rejection from throwing window.Notification (#5143)
  fix(desktop): fence localStorage SecurityError from killing the React tree (#5142)
  fix(desktop): make terminal output selectable (#4980)
  fix(desktop): use WEBKIT_DMABUF_RENDERER_FORCE_SHM for NVIDIA/AppImage (#3654) (#4505)
  Make public starter channels best effort (#5192)
  Mobile: add anchored reaction popover (#5025)
  feat(mobile): add bee pull-to-refresh (#5059)

Signed-off-by: Atish Patel <atish@squareup.com>
wpfleger96 pushed a commit that referenced this pull request Aug 7, 2026
## Summary

- mirror the retained canvas terminal grid into a transparent,
selectable text layer
- preserve the canvas renderer and terminal focus behavior for ordinary
clicks
- reconstruct wide and combining glyphs correctly for clipboard text

## Why

Buzz Term renders output entirely on a canvas and deliberately called
`preventDefault()` on viewport mouse-down, so native selection and copy
could not work. A canvas has no selectable text even if that
cancellation is removed.

The transparent text layer stays aligned with the visible cell grid,
lets WebView native selection drive drag highlighting and copy, and
follows active-session switches without changing the renderer or PTY
protocol.

## Validation

- `pnpm --dir desktop typecheck`
- `pnpm --dir desktop test` — 4,373 passed
- pre-push `desktop-check`, `desktop-test`, and `branch-skew` hooks
passed on `1f2a3f8db63f6fe36b4a28bc911aea3c5186b2b0`

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
wpfleger96 pushed a commit that referenced this pull request Aug 7, 2026
* origin/main: (32 commits)
  Recover from max-token response truncation (#5223)
  chore(release): release Buzz Desktop version 0.5.6 (#5214)
  fix(mobile): keep latest messages above composer (#4981)
  fix(sdk): preserve self-mention p tags in message and forum event builders (#4975)
  bump @tauri-apps/cli to ~2.11.4 to fix linux app icon issue (#4858)
  feat(desktop): adding rich link previews to messages (#3818)
  fix(buzz-agent): Responses reasoning summary, Anthropic display:summarized, ACP v2 messageId (#5195)
  fix(desktop): retain distinct agent instances in autocomplete (#5202)
  fix(desktop): defer channel visibility change to Save (#5203)
  feat(desktop): Projects follow-ups — access restrictions, fast loading, activity feed polish (#5073)
  refactor(cli): replace probe/decider/detail split with single typed extractor (#5191)
  fix(desktop): drop unhandled rejection from throwing window.Notification (#5143)
  fix(desktop): fence localStorage SecurityError from killing the React tree (#5142)
  fix(desktop): make terminal output selectable (#4980)
  fix(desktop): use WEBKIT_DMABUF_RENDERER_FORCE_SHM for NVIDIA/AppImage (#3654) (#4505)
  Make public starter channels best effort (#5192)
  Mobile: add anchored reaction popover (#5025)
  feat(mobile): add bee pull-to-refresh (#5059)
  Remove agent creation success modal (#5063)
  fix(buzz-agent): escalate LLM timeouts per retry and log per-call latency (#5130)
  ...

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>

# Conflicts:
#	desktop/src/shared/api/tauri.ts
41fred pushed a commit to 41fred/buzz that referenced this pull request Aug 9, 2026
## Summary

- mirror the retained canvas terminal grid into a transparent,
selectable text layer
- preserve the canvas renderer and terminal focus behavior for ordinary
clicks
- reconstruct wide and combining glyphs correctly for clipboard text

## Why

Buzz Term renders output entirely on a canvas and deliberately called
`preventDefault()` on viewport mouse-down, so native selection and copy
could not work. A canvas has no selectable text even if that
cancellation is removed.

The transparent text layer stays aligned with the visible cell grid,
lets WebView native selection drive drag highlighting and copy, and
follows active-session switches without changing the renderer or PTY
protocol.

## Validation

- `pnpm --dir desktop typecheck`
- `pnpm --dir desktop test` — 4,373 passed
- pre-push `desktop-check`, `desktop-test`, and `branch-skew` hooks
passed on `1f2a3f8db63f6fe36b4a28bc911aea3c5186b2b0`

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant