- Match View() and getItemAndLineAtY() row counts for empty items so
streaming-reasoning placeholders no longer offset hit-testing by one
row each (exposed when extension widgets like subagent-monitor shrink
the scrollback).
- Honor IsLineInRange's endCol=-1 'to end of line' sentinel in
HighlightLine and ExtractText so the start row of a multi-line drag
actually renders highlighted and is included in clipboard copies.
- Add regression tests for both invariants in scrolllist and selection.
- Lock viewport scroll while a drag-select is active so highlighted
content stays under the cursor (SetItems, appendStreamingChunk,
MouseWheelDown all now honor IsMouseDown).
- HandleMouseDrag defensively clears autoScroll on every update so a
racy re-enable can't shift the row mid-drag.
- Recompute scrollback yOffset/viewport height on each mouse event
via currentScrollbackBounds() instead of relying on stale values
cached during the previous View() pass.
- Account for canceling/ctrlCPressedOnce warning rows in
distributeHeight and mark layoutDirty when those flags toggle so
the height budget and mouse origin stay in sync.
- Add ScrollList regression tests covering the three invariants.
- Add heightCache map to ScrollList, keyed by item ID, avoiding
repeated Render() calls purely to count lines
- Rewrite GotoBottom() to walk backwards from the end in O(visible)
instead of two full O(N) forward passes over all items
- Replace all height-only Render() calls in clampOffset(), AtBottom(),
ScrollBy(), and ScrollPercent() with cached itemHeight() lookups
- Invalidate cache on width changes (SetWidth) and item mutations
(AppendChunk, AppendStdout/Stderr via InvalidateItemHeight)
- Refresh cache entries in View() from authoritative renders
The getItemAndLineAtY() method was using item.Height() which returns 0
for reasoning blocks (StreamingMessageItem with role='reasoning') because
their render cache is intentionally never populated (they include a live
duration timer).
This caused all items below a reasoning block to have incorrect Y
coordinates — clicking on the reasoning text would highlight the
assistant text below it instead.
Two fixes:
1. getItemAndLineAtY() now uses renderedHeight() which calls Render()
and counts lines — matching exactly what View() does. This is the
single source of truth for item height during hit-testing.
2. StreamingMessageItem.Height() now falls back to Render(0) when
cachedRender is empty, fixing the same issue for other callers
(GotoBottom, ScrollBy, clampOffset, etc.).
Enhanced clampOffset() to detect when the viewport has scrolled past the
bottom of the content (would show empty space) and automatically reposition
to show the last line of content at the bottom of the viewport.
This prevents the 'floating' effect where multiple PgDn or scroll down
operations would push content off the top while showing blank space below.
The clamping logic:
1. Calculates total content height
2. If content fits in viewport, forces position to top
3. Otherwise, checks if remaining content < viewport height
4. If so, repositions to show exactly the last line at viewport bottom
Also updated clampOffset to use rendered height calculation (handles
non-cached items like reasoning blocks) instead of cached Height().
Root cause: GotoBottom() was calculating heights using Height() which returns
0 for non-cached items. Reasoning blocks never cache renders due to live
duration updates, causing incorrect scroll calculations during reasoning →
assistant transitions.
Fix: Calculate heights directly from rendered strings instead of relying on
cached Height() values. This ensures accurate scroll positioning for all
message types.
Changes:
- ScrollList.GotoBottom(): Render items and calculate height from string
- ScrollList.AtBottom(): Same pattern for bottom detection
- appendStreamingChunk(): Call GotoBottom() directly for existing messages
- refreshContent(): Remove redundant GotoBottom() (handled by SetItems)
Tested with 'explore this repo' prompt - autoscroll now works correctly
throughout reasoning and assistant streaming phases.
Disable the mouse selection and keyboard copy features while keeping
all the supporting code infrastructure:
- Comment out MouseClickMsg, MouseMotionMsg, MouseReleaseMsg handlers
- Comment out keyboard shortcuts (c/y keys) for copying
- Keep all ScrollList selection tracking code
- Keep clipboard utilities (clipboard.go)
- Keep highlighting functions in scrolllist.go
This allows the features to be easily re-enabled later while keeping
the codebase clean for now.
Implement visual feedback for text selection in the scrollback:
- Add isLineInSelection() to check if a line is within the current selection
- Add applyHighlight() using the theme's Highlight color for selected lines
- Add applyFocusIndicator() using MutedBorder for focused items
- Update View() to apply highlighting during rendering
- Add getItemAndLineAtY() for precise mouse position tracking
- Track both item index and line index within item for selection
Selection highlighting uses the user's selected theme colors for
consistent visual feedback across all themes.
Implement mouse selection and keyboard copy functionality following
crush's patterns:
- Add clipboard.go with dual-write clipboard support (OSC 52 + system)
- Add CopySelection tracking to ScrollList for text selection
- Implement HandleMouseDown/HandleMouseDrag/HandleMouseUp methods
- Add keyboard shortcuts (c/y) for copying messages
- Mouse click+drag to select text, auto-copy on release
- Toast notifications for copy feedback
Note: Full text extraction from selection requires additional work to
properly extract raw text from styled message content.