Scroll the fullscreen pane's own scrollback
Claude Code grabs no mouse and stays off the alternate screen, so a plain terminal answers the wheel with its own scrollback and the child never hears about it. The fullscreen pane is that terminal, so it does the same job: the view is a stable row index into wezterm-term's scrollback, which pins the rows you scrolled to while the child keeps writing. The ctrl-l transcript wipe is cancelled while fullscreen — there the pane is not prompt-only, and its transcript is the thing being scrolled.
This commit is contained in:
66
CLAUDE.md
66
CLAUDE.md
@@ -151,7 +151,10 @@ src/term.rs embedded claude pane: spawns `claude --session-id <uuid>` in a
|
||||
ship a `[1m]` variant — see the 1M invariant). `EmbeddedTerm::adopt`
|
||||
rebuilds a pane around an inherited pty fd + pid after a hot
|
||||
reload (`AdoptedMaster` / `PidKiller` stand in for the
|
||||
portable-pty handles, which do not survive an exec)
|
||||
portable-pty handles, which do not survive an exec).
|
||||
`scroll` / `follow_live` / `scrolled_rows` give the
|
||||
**fullscreen** pane the scrollback a plain terminal would —
|
||||
see the pane-scroll invariant
|
||||
src/reload.rs hot reload: ctrl-r `execve`s the binary now on disk *into this
|
||||
process* — same pid, so the listener socket, the `claude` child
|
||||
and (via a JSON snapshot) the live feed all cross over. Builds
|
||||
@@ -480,7 +483,38 @@ agentId: <hex>`), and the real completion is injected into the parent's next
|
||||
ExitPlanMode (sized from the question's option count) before Claude Code
|
||||
renders the prompt, shrinks when the tool_result echoes back, and schedules
|
||||
a ctrl-l transcript wipe 400ms after each turn (the pane is prompt-only;
|
||||
the feed shows the context).
|
||||
the feed shows the context) — **except in fullscreen**, where that wipe is
|
||||
*cancelled*, not deferred (see the pane-scroll invariant).
|
||||
- **In fullscreen the scroll is the pane's own scrollback, never a forwarded
|
||||
mouse event.** Claude Code enables no mouse tracking and never leaves the
|
||||
normal screen (verified on the wire: for 2.1.247 tmux reports every mouse
|
||||
flag clear and `alternate_on=0`), so in a plain terminal the wheel scrolls
|
||||
*that terminal's* scrollback and the child never hears about it. The
|
||||
fullscreen pane is that terminal, so it does the same job:
|
||||
`EmbeddedTerm::scroll` moves a view into wezterm-term's scrollback (3500
|
||||
rows, the crate default) and `render` reads the window from there instead of
|
||||
the live screen. Four rules keep it honest:
|
||||
1. The view is a **`StableRowIndex`, not an offset** — the child keeps
|
||||
writing while you read, and a terminal pins the rows you scrolled to
|
||||
rather than sliding them up under you. Reaching the live top re-engages
|
||||
follow mode instead of pinning to it, and no cursor is reported while
|
||||
scrolled away (its row does not index that window).
|
||||
2. **Only `PaneView::Full` scrolls.** The cropped views frame Claude Code's
|
||||
input box, which is always at the live bottom, so `draw` calls
|
||||
`follow_live` for them — one place, instead of at each of ctrl-f /
|
||||
ctrl-↑ / F2 / session-switch.
|
||||
3. **Any key snaps back to live** (xterm's scroll-on-key) before it is
|
||||
forwarded, so typing can never leave you reading history while the child
|
||||
answers off-screen. `shift`+PgUp/PgDn is the exception: a real terminal
|
||||
keeps those for its own scrollback too, so they page the pane and are not
|
||||
forwarded.
|
||||
4. The **ctrl-l wipe is cancelled while fullscreen**, because there the pane
|
||||
is not prompt-only — its transcript is the whole context, and the thing
|
||||
being scrolled. Cancelled rather than deferred: firing it later would
|
||||
delete that history the moment ctrl-f dropped out of fullscreen. A wipe
|
||||
that already ran *before* you went fullscreen is gone for good though —
|
||||
Ink redraws only the live frame, so fullscreen shows history from that
|
||||
point on.
|
||||
- Tool input streams as raw JSON fragments; pretty-printed only on
|
||||
`content_block_stop`. Streaming text re-renders markdown on every change
|
||||
(FeedCache fingerprints by content length + done + result), so partial
|
||||
@@ -590,8 +624,10 @@ agentId: <hex>`), and the real completion is injected into the parent's next
|
||||
characters (alt-c = ©) and never reaches the app as a modifier. Pane keys:
|
||||
F2 toggle, ctrl-↓ attach pane to selected session (resume/spawn/focus),
|
||||
ctrl-↑ focus feed, ctrl-f fullscreen toggle (only while the pane is
|
||||
focused), ctrl-q quit (global; needed while the pane is focused, where
|
||||
plain `q` is forwarded to the child), c attach most-recent past session.
|
||||
focused), shift+PgUp/PgDn page the pane's scrollback while it is fullscreen
|
||||
(any other key snaps back to live), ctrl-q quit (global; needed while the
|
||||
pane is focused, where plain `q` is forwarded to the child), c attach
|
||||
most-recent past session.
|
||||
List keys: j/k/↑/↓ move the session/turn highlight, space/→/← expand/
|
||||
enter/leave the turn tree, a opens the model picker popup and spawns a
|
||||
brand-new `claude --session-id … [--model …]` (kills any current pane —
|
||||
@@ -629,10 +665,17 @@ agentId: <hex>`), and the real completion is injected into the parent's next
|
||||
ctrl-r hot-reloads onto the binary now on disk (you rebuild outside; this
|
||||
swaps the running instance onto it) — global like ctrl-q, because it has to
|
||||
work while the pane holds focus.
|
||||
`CT_DEBUG_KEYS=1` shows raw key events in the status bar.
|
||||
- Mouse is captured: wheel always scrolls the feed (regardless of focus), and
|
||||
left-drag selects screen text, copied on release via OSC 52 (like Claude
|
||||
Code). Native terminal selection therefore needs shift held.
|
||||
`CT_DEBUG_KEYS=1` shows raw key *and* mouse events in the status bar
|
||||
(`mouse: ScrollUp … fullscreen=true back=0`) — the wheel's two silent
|
||||
failure modes look identical on screen otherwise: no event delivered at all
|
||||
(an outer tmux without `set -g mouse on` swallows them) versus an event
|
||||
delivered to a pane with no scrollback above the live screen.
|
||||
- Mouse is captured: the wheel scrolls the feed regardless of focus — except
|
||||
while the pane is fullscreen, where the feed is off screen and the wheel
|
||||
scrolls the child's scrollback instead (`EmbedUi::scroll_pane`, the same
|
||||
`WHEEL_ROWS` step either side of ctrl-f). Left-drag selects screen text,
|
||||
copied on release via OSC 52 (like Claude Code). Native terminal selection
|
||||
therefore needs shift held.
|
||||
- Bracketed paste is enabled on the outer terminal (`EnableBracketedPaste`):
|
||||
a multiline paste arrives as one `Event::Paste` and, when the claude pane
|
||||
has focus, is handed to the child via `EmbeddedTerm::paste`
|
||||
@@ -735,9 +778,10 @@ agentId: <hex>`), and the real completion is injected into the parent's next
|
||||
under the app mutex; `source.type == "url"` shows the url) and a
|
||||
`tool_reference` as `[tool <name>]`. The bare `[<type>]` placeholder remains
|
||||
the fallback for everything else. No terminal graphics protocol.
|
||||
- Embedded pane: no mouse forwarding yet; no scrollback view (live screen
|
||||
only); shift+enter needs kitty keyboard protocol pushed on the outer
|
||||
terminal (not done); permission prompts aren't detected for pane growth
|
||||
- Embedded pane: no mouse forwarding to the child (it asks for none — see the
|
||||
pane-scroll invariant); the scrollback view is fullscreen-only, and the
|
||||
cropped views stay live-screen-only by design; shift+enter needs kitty
|
||||
keyboard protocol pushed on the outer terminal (not done); permission prompts aren't detected for pane growth
|
||||
(not visible in the API stream — would need a Notification hook hitting a
|
||||
local control endpoint).
|
||||
- Materialized branch files satisfy our own parser (round-trip tested) but
|
||||
|
||||
Reference in New Issue
Block a user