rustfmt and `git diff` close every coloured run with `ESC ( B` (designate G0
as ASCII) and write it after the newline, ahead of the SGR reset. With no arm
for nF escapes the parser fell through to "two-char escape", ate `ESC (` and
left the `B` as text, so each diff line in the feed read `B+ added line`.
Intermediates 0x20-0x2f now run to a final 0x30-0x7e, and an unterminated
sequence stops where the CSI arm would.
Three streams of data were being lost or mangled in the feed.
WebSearch is not purely client-side: it issues a nested /v1/messages that
declares Anthropic's hosted web_search under the parent session id and with
no agent-id header. Read as a turn start it pushed a fake user prompt,
clobbered the main lane's system/tools signatures and downgraded a [1m]
session to the short window on the next resume. Requests are now classified
three ways (Turn / ServerTool / Side) from the tools array shape alone, and a
nested call gets its own lane, readable only in the A popup. Its result and
citations arrive complete in the stream and are echoed back in no later
request body, so they are attached as the stream delivers them.
An Agent call returns its tool_result immediately ("async agent launched"),
so the real completion is a <task-notification> injected into the parent's
next user turn. Those are lifted out of the prompt: the report moves onto the
Agent entry it answers, the usage totals onto the lane, and the status
becomes one glyph-led note line. A finished agent used to keep reading as
running.
Tool output we do not control carries SGR codes. A self-contained parser maps
them to styles instead of leaving [1m as literal text; filled blocks keep
their own colours and take only the attributes.
Also: a non-2xx upstream response now surfaces as an error entry instead of a
silent stall, tool renderers cover the file/shell/task/prompt/web families,
and the fake upstream answers the nested hosted-tool request so both search
paths run offline.
Sessions panel now shows on-disk JSONL history for the current directory
(background scanner, ~1/s, deduped against live sessions). space/→ expands
a session into a lazygit-style turn tree; j/k walks sessions and turns; v
anchors a visual range; b materialises a new fully-decoupled branch file.
ctrl-↓ is the commit point for attach/resume/spawn; live external sessions
get a 3-second liveness guard (past_embeds skips the guard for sessions we
killed ourselves).
n opens a model-picker popup and spawns a fresh --session-id session;
model aliases are auto-discovered by scanning the installed claude binary
(no API call, no exec). ctrl-f toggles fullscreen for the embedded pane.
Mouse is now captured: wheel always scrolls the feed, left-drag selects
screen text copied on release via OSC 52 (like Claude Code). Native
selection needs shift held.
FeedCache stores per-entry rendered lines + wrapped heights, rebuilt only
when the content fingerprint changes — work while holding the app mutex is
proportional to what changed, and feed scroll now works past u16::MAX lines.
Kind::User entries (deep-blue ❯ prefix) surface user prompts from request
bodies via record_user_prompt.
src/markdown.rs added: GFM table rendering (box-drawing, width-fitted
columns) and heading # marker stripping, compensating tui-markdown 0.3.5
gaps without upgrading.
The proxy tap now runs on a dedicated tokio task via a bounded mpsc channel
(try_send; drop-on-full) so it never delays forwarded bytes. Listener is
bound before the TUI starts so multi-instance port coexistence works from
launch; CT_UPSTREAM added for fully offline testing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
metadata.user_id is now a JSON-ish blob containing "session_id":"<uuid>"
(verified against the 2.1.173 binary: user_id is built from an object
with device_id/account_uuid/session_id). split("session_") was matching
inside "session_id":" and producing mangled keys like `id":"a78…`,
which broke the embed-session match — so the pane never grew for
interactive prompts. New session_key() handles both the JSON blob and
the legacy user_…_session_<uuid> format, with tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Render window is now content-anchored instead of the raw screen top:
ends BOTTOM_CROP(2) rows above the last non-blank row (hides the
"? for shortcuts"/mode hint rows) and starts no higher than row 2
(hides "✻ Worked for 1s"; the streaming spinner stays visible since it
sits next to the input box). The PTY gets PTY_PAD(4) extra rows so
Claude Code still has room to draw what we crop. Compact pane shrinks
12 → 8 rows.
AskUserQuestion pane height is now estimated from the tool input
(max over questions of: question text + 2 rows per option incl. the
implicit "Other" + submit/hint rows + tab header), replacing the flat
75% — which remains the fallback for ExitPlanMode/parse failures.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The pane was showing Claude Code's own transcript (tool calls, diffs) —
redundant with the feed above. Two measures:
- Default pane height is now compact (12 rows): room for the input box
and status, (almost) none for transcript. Interactive grow to 75% is
unchanged.
- When a turn of the embedded session finishes streaming (Tap drop),
schedule a ctrl-l into the PTY 400ms later: Claude Code clears the
screen but keeps the conversation, leaving just the prompt UI.
Residual: while a response is streaming, a few transcript rows can
still scroll through the compact pane until the turn-end wipe fires.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude Code clips AskUserQuestion option lists to the rows available;
60% was still cutting options off. Also relax the upper clamp so small
terminals give the prompt as much as possible (6 rows reserved).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- src/term.rs: self-contained module spawning `claude --session-id <uuid>`
with ANTHROPIC_BASE_URL pointed at our proxy, inside a portable-pty;
wezterm-term models the screen (and answers terminal queries), a small
renderer paints cells into the ratatui buffer.
- Toggle with alt-c; off by default, hiding keeps the session alive.
- Modifier-split input: plain keys go to claude, alt-keys drive the feed
(alt-q quit, alt-j/k scroll, alt-f filter, alt-n/p session).
- Dynamic pane height: the tap flags AskUserQuestion/ExitPlanMode in the
embedded session (matched via --session-id) before Claude Code renders
the prompt → pane grows 35%→60%; shrinks when the tool_result echoes
back in the next request.
- wezterm-term isn't on crates.io: pinned git rev of the monorepo.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>