Commit Graph
20 Commits
Author SHA1 Message Date
lengxiaouserandSivanCola e5a025c5fb fix(cli): preserve UTF-8 when copying from WSL (#10074)
* fix(cli): preserve UTF-8 when copying from WSL

* fix(cli): preserve native WSL clipboard backends

Problem: WSL sessions lost working Wayland and X11 clipboard writes when Windows PowerShell was unavailable.

Root cause: WSL detection unconditionally replaced every Linux clipboard backend with the Windows bridge.

Fix: Prefer wl-copy, xclip and xsel, and reserve the UTF-8 PowerShell bridge for the Windows fallback. Never invoke raw clip.exe.

Verification: Reproduced all five native-backend failures before the fix. Native backend, process stdin, command failure and OSC 52 fallback tests pass with the race detector.

* fix(cli): bound WSL clipboard command waits

Problem: A stalled clipboard subprocess could prevent the asynchronous copy operation from reaching its OSC 52 fallback.

Root cause: The WSL command had neither a cancellation deadline nor a bounded pipe-drain wait.

Fix: Apply a five-second context deadline, cancel the process tree through the shared proc helper, and bound inherited output-pipe draining to one second.

Verification: Full CLI tests, focused race tests including deterministic cancellation after child startup, go vet, repolint and golangci-lint all pass. Native WSL clipboard round-trip validation remains external.

---------

Co-authored-by: SivanCola <[email protected]>
2026-09-11 19:03:03 +08:00
SivanCola b298511eaa fix(cli): satisfy current lint contracts
Problem:
- The updated connector path left rewriteTranscriptBlock unused, and the current linter rejected a HasPrefix/TrimPrefix pair.

Root cause:
- Connector rewrites now preserve copy metadata through rewriteConnectorBlock, making the old text-only helper obsolete.
- main-v2 enables the modernize string-prefix rule.

Fix:
- Remove the unused helper and use strings.CutPrefix while retaining the same copy-span indentation behavior.

Verification:
- make lint
- go test ./internal/cli -count=1
- go test -race ./internal/cli -count=1
- go vet ./internal/cli
- go run ./tools/repolint

Refs #9244
2026-09-02 19:15:52 +08:00
SivanCola 20341c41f6 fix(cli): preserve source text when omitting copy gutters
Problem:
- Character-based gutter stripping silently removed legitimate lines beginning with │, ▎, or ⎿.
- Multi-line tool and reasoning connectors retained one leading space on the first line and five on continuation lines.

Root cause:
- Selection copy inferred decoration from the copied characters after provenance had been discarded.
- Connector continuation padding was not represented by the single-glyph heuristic.

Fix:
- Extend the existing copy-only span rendition to mark renderer-owned gutters explicitly while keeping display fallback byte-faithful.
- Preserve copy renditions for live tool blocks and regenerate semantic Markdown and reasoning spans on demand.
- Move selection-copy ownership into a focused module and cover rendered, replayed, fallback, literal-glyph, connector, and CJK UTF-8 paths.

Verification:
- go test ./internal/cli -count=1
- focused copy tests under go test -race
- go vet ./internal/cli
- go run ./tools/repolint

Refs #9244
2026-09-02 19:02:31 +08:00
SivanCola 0b6d36bd69 fix(cli): strip block gutters from in-app selection copy / 应用内选区复制剥离装饰竖线
Closes #9244
2026-08-31 12:25:57 +08:00
YHH 4fbbca6a99 refactor(go): adopt modern stdlib idioms and gate them (#7831)
* refactor(go): adopt modern stdlib idioms across kernel and desktop

Mechanical sweep with golangci-lint's modernize/intrange/copyloopvar/
usestdlibvars fixers plus hand-fixes for what they could not rewrite:
slices.Contains, min/max, slices.Backward, WaitGroup.Go, reflect.TypeFor,
maps.Copy, strings.Cut/CutPrefix/SplitSeq, strings.Builder, range-over-int.
Net -549 lines with no behavior change.

Three loops in session.go stay index-based: slices.Backward yields element
copies, so the fixer's rewrite silently dropped writes to DecisionReceipts
and ToolCalls (caught by TestDelivery* and TestDependentSameBatch*). All 49
slices.Backward sites were audited for that shape; only these three write
through the index, and they carry a nolint with the reason.

CaptureAfter's scan loop had an empty body and never ran any logic, so the
fixer could not express it. Drop it and the accessor it was the only caller
of. omitzero is deliberately untouched: turning omitempty into omitzero
changes the JSON written for nested structs, which is a wire-format change
to session files and the extension protocol.

* build(lint): gate the modern-idiom linters

Nothing stopped the codebase from drifting back to pre-1.22 idioms. Both
modules are now clean for modernize, intrange, copyloopvar, and
usestdlibvars, so enable them rather than leaving the sweep as a one-off.

omitzero stays disabled with the reason inline; usetesting, perfsprint, and
errorlint still have residue and land separately.
2026-08-07 15:30:33 +08:00
SivanCola 5509b36df6 fix(cli): transcript scroll state, wrap cache, and mouse re-enable / 滚动状态机、wrap 缓存与鼠标重启用 (#7706)
* fix(cli): transcript scroll state, wrap cache, and mouse re-enable

Problem
- Opening approval/chooser panels shrank the viewport and flipped AtBottom
  false, which permanently disabled tail-follow until a manual return to
  bottom (#6430). Long sessions with frequent panels looked "broken" (#6978).
- Every streaming commit re-wrapped the entire joined transcript, so long
  sessions paid full O(history) cost on each keystroke of agent output.
- Windows ConPTY dropped mouse tracking after long turns/resize/focus loss;
  wheel then became Up/Down history cycling (#7583). /mouse toggle twice was
  the only recovery.
- Composer cursor coordinates were not clamped to terminal bounds after VS
  Code fullscreen/resize storms (#6282, #7236).

Root cause
- Tail-follow relied on a pre-Update AtBottom snapshot, not an explicit
  followTail/userScrolled mode; modal height changes were misread as user
  scroll. Wrap cache did not exist. Mouse mode was set only in View() without
  re-emitting enable sequences. Cursor position was raw offset math.

Fix
- Explicit scrollFollowMode: wheel/PgUp/scrollbar leave follow; empty Enter,
  Ctrl+End, forceGotoBottom restore it. Height-only modal changes keep
  followTail and re-pin when appropriate.
- Per-block wrap cache with append-only extension; full rebuild on width
  change, mid-history rewrite, or dirty reflow.
- Rate-limited mouse enable on resize, FocusMsg, and TurnDone.
- clampCursorToTerminal on both alt-screen and native-scrollback paths.

Verification
- go test ./internal/cli -count=1
- TestModalOpenDoesNotDisableTailFollow
- TestUserScrollBreaksAndEmptyEnterRestoresFollow
- TestWrapCacheAppendOnlyMatchesFullRebuild
- TestLongTranscriptAppendKeepsFollowAndDoesNotFullRebuildEachTime (2k blocks)
- TestMouseReenableRateLimit, TestClampCursorToTerminal

Refs #6282 #6430 #6978 #7028 #7180 #7236 #7537 #7583 #6563

* fix(cli): suffix wrap invalidation, drag scroll sync, trailing mouse re-enable

Problem
- streamAnswer set transcriptDirty → forceFullWrap rebuilt the entire wrap
  cache on every flushed paragraph, the long-session hot path #7706 claimed
  to remove.
- Scrollbar drag only synced scrollMode on MouseRelease, so agent events
  mid-drag still saw followTail and GotoBottom.
- Mouse re-enable rate limit dropped in-window requests with no trailing
  retry; the last resize in a storm could leave ConPTY without tracking.
- Lint: SA9003 empty branch, unused resetThenEnableMouseTracking/modalOpen.

Root cause
- Collapsed "dirty" with "full rebuild"; gesture state lagged YOffset; leading
  edge only rate limit.

Fix
- invalidateWrapFrom / rewriteTranscriptBlock for live rewrites; Update only
  force-full-wraps on width change or history shrink; suffix path re-wraps
  from wrapBlockCount; feedViewportContent uses SetContentLines.
- dragScrollbar and edge autoScroll sync scrollMode on every YOffset change.
- maybeReenableMouse sets pending + arms trailing timer (mouseReenableMsg);
  controllable mouseNowFn for tests without sleep.
- batchCmds collapses nil; mouse re-enable only on real size change.

Verification
- go test ./internal/cli -count=1
- TestStreamAnswerSuffixInvalidationNotFullRebuild
- TestScrollbarDragMotionSyncsBeforeRelease
- TestMouseReenableTrailingEdge (fake clock)
- BenchmarkStreamAnswerSuffixWrap hist=500/1000/2000 (~const allocs/op)

Refs #6430 #6978 #7583 #6282 #7236

* fix(cli): clear Integration C lint and docs-impact blockers

- Fill empty answerIdx branch with t.Fatal; use wrappedContentString
- Combine fakeNow declare/assign; drop unused mouseReenableSeq generation
- Trailing mouse timer is single-armed pending only (no stale seq)

Verification: go test ./internal/cli -count=1; staticcheck ./internal/cli
2026-08-06 17:02:56 +08:00
SivanCola ee36c07157 cli: render isolated subagent progress
Give every sub-agent child its own progress state and a fixed transcript
slot keyed by its namespaced call ID, independent of the single live
toolStreamID, so concurrent children never cross-stream. By default the
block shows phase, running elapsed, and recent activity; /verbose (Ctrl+O)
adds bounded 4 KiB reasoning/text tails with a truncation marker. Terminal
children fold to a one-line summary; Termux native scrollback queues a
status line on phase changes and terminal only (verbose previews at most
once per 2s per child). Adds localized phase labels and progress formats.
2026-08-05 09:31:21 +08:00
SivanCola ba414a342f fix(tui): restore LaTeX from replayed history
Problem:
Selecting rendered math from a resumed session or replayed branch copied only the Unicode display glyph instead of the original LaTeX source.

Root cause:
The copy transcript path marker-rendered live Markdown blocks but reused the already-rendered text for replay bundles, so historical assistant formulas had no source spans.

Fix:
Share replay traversal between visible and clipboard renderers, inject the marker-aware assistant renderer for copy operations, and assign unique prefixes across replayed assistant messages. Add coverage for ordinary and local recovery history.

Verification:
- go test ./internal/cli -count=1
- go test -race ./internal/cli -run 'TestSelectedText|TestReplaySections' -count=1
- go test -p 2 ./...
- go vet ./...
- cd desktop && go test ./...
- pnpm test:typecheck
- pnpm exec tsx src/__tests__/message-selection-copy.test.ts
- pnpm build
2026-07-31 01:06:37 +08:00
SivanCola f5684e24d0 fix: satisfy staticcheck in copy transcript parser
Problem:
CI reported SA4006 because the no-marker branch assigned the loop position immediately before breaking.

Fix:
Remove the dead assignment while preserving the branch's existing output and control flow.

Verification:
- go test ./internal/cli -count=1
- go test -race ./internal/cli -run 'TestSelectedText|TestLanguageCommandRefreshesCurrentController|TestCurrencyCommandPersistsAndRefreshesCurrentController' -count=1
- go vet ./internal/cli
- golangci-lint v2.12.2: 0 issues

Co-authored-by: SivanCola <[email protected]>
2026-07-31 00:25:40 +08:00
SivanCola 412d5cb499 fix: preserve LaTeX copy selection boundaries
Problem:
Desktop copy could discard prose around a single formula, while the TUI reused display coordinates against a separately wrapped raw transcript. The original commit also dropped the /currency dispatcher and discarded the /language rebuild command.

Root cause:
The desktop path performed global string substitution instead of respecting DOM range boundaries. The TUI assumed rendered Unicode and LaTeX source had identical line and column geometry.

Fix:
Reconstruct desktop selections from ordered KaTeX DOM ranges and MathML annotations. Re-render semantic CLI Markdown only when copy is requested, attach zero-width source markers to the identical visible rendition, and map selected display cells back to formula spans. Restore both runtime-setting dispatchers and add focused regressions for partial, mixed, duplicate, and wrapped selections.

Verification:
- go test ./internal/cli -run 'TestSelectedText|TestLanguageCommandRefreshesCurrentController|TestCurrencyCommandPersistsAndRefreshesCurrentController|TestRenderInlineMath|TestCopyToClipboard' -count=1
- go vet ./internal/cli
- pnpm exec tsx src/__tests__/message-selection-copy.test.ts
- pnpm typecheck
- pnpm test:typecheck
- git diff --check

Co-authored-by: SivanCola <[email protected]>
2026-07-31 00:16:17 +08:00
EMEEEEMMMM 1e722fa84a fix: copy-paste restores original LaTeX source instead of Unicode glyphs
Desktop (Wails):
- MarkdownRenderer annotates .katex/.katex-display/.katex-html with
  data-latex-source attribute from rehype-katex MathML annotation
- messageSelectionCopy handler reads data-latex-source and replaces
  KaTeX rendered text with the original $...$ / ... LaTeX markup
- Auto-locks to the full formula when any part is selected
- Collects all display blocks in the range for multi-row formulas

CLI (TUI):
- mathNode retains both value (Unicode display) and source (raw LaTeX)
- mdRenderer gains rawMath mode and RenderRaw() method
- renderAssistantMarkdownRaw builds a parallel transcript with
  raw LaTeX preserving $...$ delimiters
- selectedText() uses rawWrappedLines when available to restore
  original LaTeX on clipboard copy

Fixes: Ctrl+C copy of LaTeX math expressions in both desktop and CLI
2026-07-28 16:43:21 +08:00
SivanCola 40ef98de92 Fix CLI terminal interactions and transcript hierarchy / 修复 CLI 终端交互与消息层级 (#6632)
* fix(cli): restore terminal interactions and transcript hierarchy

Problem:
- Captured mouse mode could copy selections but could not right-click paste clipboard text.
- Composer drag selections were visible but did not reliably reach the system clipboard.
- Non-Git workspaces reserved or misaligned an empty footer data band.
- Assistant answers lacked a distinct identity and spacing from reasoning and turn receipts.

Root cause:
- Mouse handling had no text-only clipboard read path, composer selection release stopped at local state, and footer/transcript rendering assumed fixed visual bands.

Fix:
- Route right-click clipboard text through the canonical paste event and auto-copy composer drag selections.
- Make optional footer telemetry compact and left-aligned outside Git repositories.
- Render assistant markdown with a Reasonix identity, stable indentation, replay support, and semantic spacing.
- Document the interaction changes and add focused regression coverage.

Verification:
- go test ./internal/cli
- go test ./...
- git diff --check

* fix(cli): clarify right-click paste over SSH

Problem: Captured right-click paste attempted to read the remote host clipboard during SSH sessions and the documentation implied it could access the user's local clipboard.

Root cause: The right-click path called the native clipboard reader without applying the remote-session boundary already used by clipboard copy.

Fix: Detect SSH sessions before reading clipboard text, preserve the composer, show a localized terminal-paste hint, and document local versus remote behavior.

Verification: go test ./internal/cli; go test ./...; go vet ./...; git diff --check.

* test(cli): isolate local clipboard cases from SSH

Problem: Four local clipboard interaction tests inherited SSH environment variables and failed when the suite ran on a remote development host.

Root cause: The tests stubbed native clipboard operations without explicitly selecting a local clipboard session.

Fix: Add a shared test helper that clears all supported SSH markers and use it in each local paste and drag-copy case.

Verification: Normal and simulated-SSH go test ./internal/cli -count=1; go test ./...; go vet ./...; git diff --check.
2026-07-18 05:11:47 +00:00
SivanColaandbillycat 1bd5f04d2a Polish CLI composer and responsive status UI / 优化 CLI 输入框与响应式状态栏 (#6591)
* feat(cli): polish composer and responsive status UI

* fix(cli): remove obsolete status helpers

* chore: credit status footer co-contributor

Attribution:
Credit @billycat for the full-data TUI status footer restoration and height-accounting coverage contributed in #6587.

Relationship:
This PR carries that behavior forward in the redesigned responsive footer. This attribution-only commit does not change the runtime tree.

Verification:
Confirmed this commit has the same tree as its parent.

Co-authored-by: billycat <[email protected]>

* polish(cli): refine receipt and themed footer

Problem:
The wide footer overemphasized per-turn metadata, separated effort from its model context, and used faint or hard-coded colours that did not preserve the same hierarchy across dark and light terminals.

Fix:
Use a single receipt boundary with softer separators, group model/effort/work together, add a theme-specific subtle contrast token, and render explicit effort through each theme's semantic info colour. Keep dark, light, and no-colour geometry identical.

Verification:
- go test ./internal/cli -count=1
- DEEPSEEK_API_KEY= go test ./... -count=1
- git diff --check

* polish(cli): tint composer borders with theme accent

Problem: The composer top and bottom borders stayed neutral, so the input area did not reflect the active CLI theme.

Root cause: refreshCLIStyles bound the composer border foreground to the neutral border token.

Fix: Bind both composer borders to the active accent and cover every dark and light theme style plus NO_COLOR.

Verification: Focused composer theme test and the full internal/cli suite pass with an isolated user home.

* fix(cli): localize persistent footer labels

Problem: Chinese and Traditional Chinese sessions still showed English metric labels in the persistent footer.

Root cause: The new footer embedded MODEL, EFFORT, WORK, CACHE, CTX, COMPACT, JOBS, and BAL as literals outside the i18n catalog.

Fix: Add localized footer labels, use them at every metric call site, and calculate model width from the active label.

Verification: i18n catalog tests and the full internal/cli suite pass with an isolated user home.

* fix(cli): spell out footer shortcuts

Problem: The persistent footer used terminal shorthand for Shift+Tab and Ctrl+Y, which was difficult to recognize at a glance.

Root cause: The compact locale strings preferred symbolic key notation without a responsive fallback for the extra width of readable names.

Fix: Spell out both shortcuts in every locale, wrap semantic groups on medium terminals, and hide optional shortcut help only when a complete key-name group cannot fit.

Verification: The i18n catalog and full internal/cli test suites pass with an isolated user home.

* fix(cli): localize work mode values

Problem:
Chinese CLI sessions showed an English work-mode value such as '模式 balanced', unlike the localized desktop UI.

Root cause:
The CLI used one helper both for stable command identifiers and user-facing profile labels, so English command names leaked into localized status and notices.

Fix:
Separate stable work-mode names from localized display labels, add English, Simplified Chinese, and Traditional Chinese catalog entries, and keep slash-command completion values unchanged.

Verification:
- go test ./internal/i18n -count=1
- go test ./internal/cli -run 'Test(StatusFooter|WorkMode|RuntimeProfileDisplay|RenderWorkModes)' -count=1
- git diff --check

* fix(cli): use platform image paste shortcut in tests

Problem: The image-paste selection regression test always sent Ctrl+V, so the Windows CI job could not enter its Alt+V image-paste path.

Root cause: The test ignored the platform mapping already enforced by imagePasteShortcut.

Fix: Select Ctrl+V on macOS/Linux and Alt+V on Windows, and make the test name and diagnostics platform-neutral.

Verification: The isolated internal/config, internal/i18n, and internal/cli suites passed; the Windows/amd64 internal/cli test binary also cross-compiled successfully.

* docs(cli): document responsive composer controls

Documentation: Explain footer states and telemetry, theme-aware composer behavior, platform-specific text and image paste, and the stable mode shortcuts in the English and Chinese CLI guides.

Website: Mirror the operation and shortcut guidance on the public docs page and refresh the CLI homepage summary.

Changelog: Record the responsive composer, localized footer, clipboard split, and theme behavior under Unreleased.

Verification: The site test suite and Astro static build passed, and git diff --check reported no whitespace errors.

---------

Co-authored-by: billycat <[email protected]>
2026-07-17 16:27:09 +08:00
SivanCola ee9d976622 feat(cli): auto-copy mouse selection and add mouse-capture toggle
Releasing a left-drag selection in the TUI transcript now copies it to
the clipboard automatically (native terminal convention), with a
transient status-line "copied to clipboard" notice shared by the
mouse, right-click, and Ctrl+C copy paths.

Add "/mouse" to release mouse capture back to the terminal on demand,
restoring its native click-drag selection and right-click context
menu at the cost of in-app drag-select, the scrollbar, and
wheel-scroll while it's off. REASONIX_DISABLE_MOUSE=1 starts every
session with it off.
2026-07-02 15:02:17 +08:00
SivanCola 6641a92f11 fix(tui): harden transcript scroll interactions 2026-06-27 03:49:37 +08:00
SivanCola 54922b9760 docs: clarify OSC 52 clipboard behavior
Clarify that OSC 52 clipboard copy depends on terminal support while avoiding remote-host platform clipboard writes in SSH/tmux flows.

Co-authored-by: SivanCola <[email protected]>
2026-06-25 21:27:53 +08:00
meteorchen 256a62fd40 fix: use OSC 52 directly for clipboard instead of platform-tool-then-fallback
Remove the atotto/clipboard dependency from copyToClipboard and the
two-tier strategy that tried the platform clipboard (xclip/wl-copy/pbcopy)
first and fell back to OSC 52 on failure.

Over SSH, the platform tool writes to the remote server's clipboard — the
fallback never triggered locally because clipboardWriteAll returned nil
success. OSC 52 works in all environments (local, SSH, tmux, modern
terminals) and is the correct single path.

- transcript.go: remove clipboardWriteAll var, simplify copyToClipboard
- transcript_test.go: replace two-tier tests with direct OSC 52 test
- chat_tui_test.go: remove clipboard stubs from Ctrl+C tests
2026-06-25 19:26:00 +08:00
SivanCola 08e76c3563 feat(theme): wire mode and accent themes (#2734)
* feat(theme): wire mode and accent themes

* fix(cli): defer terminal theme probing
2026-06-02 04:52:30 -07:00
YHHandreasonix 271dd8419c fix(cli): copy via OSC 52 when the platform clipboard tool fails (tmux/SSH) (#2719)
copyToClipboard discarded the atotto error and never reached the system
clipboard inside tmux or over SSH, where xclip/xsel/wl-copy cannot see the
display server. Fall back to OSC 52 so tmux and modern terminals forward
the copy to the host clipboard.

The fallback returns tea.SetClipboard(text)() — the *message*, run here —
not the command: a tea.Cmd returned as a tea.Msg is not recognized by the
runtime's dispatch and is dropped, so it would emit nothing. The message
(setClipboardMsg) is handled by the runtime, which writes the OSC 52
sequence to the terminal.

Closes #2716.

Co-authored-by: reasonix <[email protected]>
2026-06-02 02:43:27 -07:00
YHHandreasonix 0048df6af9 TUI: alt-screen transcript viewport with scrollbar, selection, and copy (#2524)
* feat(cli): add transcript buffer + viewport plumbing for alt-screen migration

Inline rendering can't repaint cleanly when the terminal shrinks — bubbletea
v2's inline renderer has no width-change full-reset path, so a narrowed frame
leaves ghost borders. The chat TUI will move to alt-screen + an in-app
viewport to fix it.

First stage, no visible change: maintain the committed transcript and a
sized viewport alongside the existing tea.Println scrollback path. View()
still renders inline; the viewport is populated and sized but not yet shown.

* feat(cli): render chat transcript in an alt-screen viewport

Switch the chat TUI from inline rendering to alt-screen with an in-app
scrollable viewport. Alt-screen owns the grid and repaints the whole frame,
so terminal resize no longer strands ghost borders — the bug inline mode
couldn't fix (bubbletea v2's inline renderer has no width-change full reset).

- View renders the viewport (transcript) above the pinned bottom region and
  requests AltScreen + mouse; the viewport pads to fill the screen.
- Update wraps the model loop to keep the viewport sized and fed, tail-
  following newest output only while the user is at the bottom.
- Wheel + PgUp/PgDn scroll the transcript (history is no longer terminal-
  native scrollback).
- Drop the inline-only machinery: tea.Println chunking, the forceRepaint /
  repaintToggle resize nudge, chunkLines, scrollChunkHeight.

* feat(cli): scrollbar + mouse text selection with Ctrl+C copy

Restore the read-back affordances alt-screen took away, in-app:

- A scrollbar column on the right of the transcript (thumb tracks scroll
  position; blank when content fits).
- Left-drag selects transcript text and reverse-highlights it across lines;
  selection is keyed by absolute content line so scrolling never moves it.
- Ctrl+C copies the selection to the system clipboard (terminal convention:
  copy when something is selected, otherwise the existing interrupt/quit).
  Selection clears on the next keystroke or plain click.

Transcript rendering, selection, and clipboard live in transcript.go to keep
chat_tui.go from growing further.

* fix(desktop): bind vite dev server to IPv4

Unset host makes Vite listen on ::1; the Wails dev proxy dials [::1] and
fails on Windows hosts where IPv6 loopback is filtered, so the webview shows
a 502. Pin server.host to 127.0.0.1.

* fix(cli): correct alt-screen transcript rendering, scroll, and selection

Follow-ups after testing the alt-screen viewport across terminals:

- Wrap the transcript with lipgloss instead of ansi.Hardwrap so styled spans
  that span a wrap point are closed at the line end and reopened next line;
  Hardwrap left them open and bled the attribute into the padding and the
  following row on stricter terminals.
- Pad the status rows to full width so they overwrite the whole line (an
  unpadded status left stale cells from the prior frame on the right).
- Force a full repaint whenever the viewport offset moves (wheel, PgUp/PgDn,
  edge auto-scroll, tail-follow) — some terminals mishandle the renderer's
  scroll/insert-line optimization and strand rows.
- Tame edge-drag auto-scroll: one line per tick, stop at the boundary so a
  held edge can't run the selection to the end.
- Cache the wrapped lines and drop the per-row lipgloss pad in renderTranscript
  so a drag (which re-renders on every mouse move) stays cheap.

clipboard moves to a direct dependency (used by Ctrl+C copy).

---------

Co-authored-by: reasonix <[email protected]>
2026-05-31 22:46:02 -07:00