fix(tui): clear selection on right-click copy + clearer block boundaries#37786
Merged
Conversation
Contributor
🔎 Lint report:
|
tonydwb
approved these changes
Jun 3, 2026
tonydwb
left a comment
There was a problem hiding this comment.
Code Review Summary
Verdict: Approved
Overview
Clean UX fix: clears selection after right-click copy in TUI and adds clearer visual boundaries between message blocks. Well-scoped, minimal changes.
✅ Looks Good
- Right-click copy clearing selection is a standard UX pattern that prevents confusion
- Block boundary improvements make the chat read more naturally
- Small, focused change (103 additions, 7 deletions)
- No security, performance, or correctness concerns
Reviewed by Hermes Agent
bdc2a24 to
cfb4c6f
Compare
Two TUI polish fixes.
(1) Right-click copy now clears the highlight.
The right-click handler copied an active selection via onCopySelectionNoClear
(the copy-on-select variant that keeps the highlight during a drag) and never
cleared it, so after right-click-to-copy the selection stayed lit with no
confirmation and a follow-up right-click re-copied the stale range instead of
pasting. A successful right-click copy now clears the selection and notifies;
if the copy fails (no clipboard path) the highlight survives and we fall back
to the right-click paste handler, exactly as before.
(2) Group transcript blocks so boundaries read clearly.
Model replies, reasoning/tool trails, and system/error notes rendered with no
vertical separation, so distinct block types butted together and were hard to
scan. Group adjacent blocks by kind: one blank line opens only where the visual
group changes (model prose <-> reasoning/tool trails <-> notes), while a run of
same-kind blocks renders flush. The rule lives in domain/blockLayout.ts
(messageGroup + hasLeadGap) and is applied intrinsically in MessageLine via a
`prev` prop, which fixes the things ad-hoc per-block margins kept breaking:
- Streaming stability: the gap is derived from the stable predecessor, never
the live block's own changing text, so the actively-streaming reply computes
the same gap while it streams as the settled segment does once it flushes.
No reflow/jump.
- Transparent empty trails: a trail hidden by /details, or one carrying only a
token tally (the finalDetails segment message.complete appends), renders
nothing and is transparent to grouping (prevRenderedMsg skips it), so there
are no floating gaps, no doubled gap after a prompt, and no padded space
above the final reply. In the default/collapsed modes content-bearing trails
always render, so the grouping is a no-op there.
The virtual-height estimator counts the group-boundary line so scroll math
stays accurate before Yoga remeasures.
ui-tui/src/domain/blockLayout.ts (new), components/messageLine.tsx,
components/streamingAssistant.tsx, components/appLayout.tsx,
lib/virtualHeights.ts, app/useMainApp.ts.
Tests: blockLayout.test.ts (grouping + hidden/empty-trail visibility),
virtualHeights leadGap, app-mouse.test.ts copy behavior. Full ui-tui suite
green apart from 3 pre-existing local/env failures (cursorDrift, ink-resize,
virtualHeights user-prompt-width) unchanged from main.
cfb4c6f to
dfba3f3
Compare
Collaborator
Author
|
bugbot run |
|
Skipping Bugbot: Unable to authenticate your request. Please make sure Bugbot is properly installed and configured for this repository. |
davidgut1982
pushed a commit
to davidgut1982/hermes-agent
that referenced
this pull request
Jun 5, 2026
…lick-and-boundaries fix(tui): clear selection on right-click copy + clearer block boundaries
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Two TUI polish fixes.
1. Right-click copy clears the selection
Right-click-to-copy used
onCopySelectionNoClear()(the copy-on-select variant that keeps the highlight during a drag), so after a right-click copy the selection stayed lit, there was no confirmation, and a follow-up right-click re-copied the stale range instead of pasting. A successful right-click copy now clears the highlight and notifies; if the copy fails (no clipboard path) the highlight survives and we fall back to the right-click paste handler. Right-click with nothing selected still pastes.packages/hermes-ink/src/ink/components/App.tsx.2. Group transcript blocks so boundaries read clearly
Model replies, reasoning/tool trails, and system/error notes rendered with no vertical separation — distinct block types butted together and were hard to scan.
Adjacent blocks are now grouped by kind: one blank line opens only where the visual group changes (model prose ↔ reasoning/tool trails ↔ notes), while a run of same-kind blocks renders flush. (This is the flex-grouping idea, but expressed as a per-row lead gap because the transcript is virtualized — a wrapper div spanning rows would break the per-row offset cache.)
The rule lives in
domain/blockLayout.ts(messageGroup+hasLeadGap) and is applied intrinsically insideMessageLinevia aprevprop. That placement fixes the two things ad-hoc per-block margins kept breaking:/detailsrenders nothing and is transparent to grouping (prevRenderedMsgskips it), so there are no floating or doubled gaps. In the default/collapsed modes trails always render, so the grouping is a no-op there.The virtual-height estimator (
virtualHeights.ts+useMainApp) counts the group-boundary line so scroll math stays accurate before Yoga remeasures.ui-tui/src/domain/blockLayout.ts(new),components/messageLine.tsx,components/streamingAssistant.tsx,components/appLayout.tsx,lib/virtualHeights.ts,app/useMainApp.ts.Tests
app-mouse.test.ts— clears highlight on successful copy; keeps it when copy fails.blockLayout.test.ts(new) — grouping relationships + hidden-trail visibility (no snapshots).virtualHeights.test.ts— group-boundary lead gap adds one row.Full
ui-tuisuite green apart from 3 pre-existing local/env failures (cursorDriftRegression,ink-resize,virtualHeightsuser-prompt-width) that fail identically onmain. Type-check error count matchesmain(18, all inexecFileNoThrow.ts; zero in touched files).Notes
ui-tui/package-lock.jsoncommitted (repo doesn't track one).