- Magic Context tool rendering —
ctx_search,ctx_memory,ctx_note,ctx_expand,ctx_reduce, andtodowritenow use the same Claude-style tool rows as other external tools. - Todo overlay labels — task IDs no longer display a leading
#. - Hermes memory notice styling — the auto-review notice now matches thinking text color and weight instead of applying additional ANSI dimming.
- Thinking presentation — thinking text is no longer italic, visible thinking uses the
∴marker, and collapsed “Thinking…” / “Thought for…” rows omit the marker while retaining the correct text indentation. - Hermes memory notice styling — the
💾 Memory auto-reviewed and updatednotification is restyled locally as a translucent✻ Memory auto-reviewed and updated, without modifying the pi-hermes-memory extension. - Todo overlay alignment — todo headings and task rows now have the missing indent, and their
├─/└─connectors follow the configured tool branch color.
- Idle crash / "job failed" while pi sits stale — leaked blink entries (a tool that completed without clearing, or a turn that ended without
turn_end) kept the 500 ms blink timer re-arming forever, forcing full TUI re-renders twice a second while idle. Each re-render re-ran the layout and either tripped pi's render width-assertion (crash) or grew RSS until the OS killed pi (silent crash → Ghostty "job failed"). Added anagent_endclear and a 15 s staleness watchdog so leaked entries can't sustain the re-render loop. - Render width-assertion crash on wide content —
clampLineWidth/padRenderedLineToWidthnow cap atprocess.stdout.columns, so the extension never emits a line wider than the real terminal even when pi hands it a too-wide width (e.g. content later placed in a narrower side panel).
readonSKILL.mdshows as[skill]— paths ending inSKILL.mduse the same[skill]label styling as custom skill messages (krikchaip).
- Finished tool rows no longer pulse as pending after reload — only
isPartialmarks a row pending; missingexecutionStartedon history rows no longer triggers blink timers (krikchaip). - Tool row backgrounds after
/reload— strip the outerBoxsuccess background ANSI on rebuilt rows so transparent/outline mode stays clean (krikchaip). - Unmatched partial tool calls in old branches — partial rows without
executionStartedshow a static muted dot instead of an endless pending blink (krikchaip). - Partial rows at tree-navigated leaves — when the result lives off the selected branch, blink only while an agent is actually running; settled history renders as finished (green when succeeded) (krikchaip).
- Duplicate bash expand hint — finished bash rows keep “expand” on the summary line only; the preserved output preview no longer repeats it (krikchaip).
- Random crash on large diffs — rendering a large edit or
apply_patchcould throwRangeError: Maximum call stack size exceeded. Root cause: the split/unified diff renderers computed the max line number viaMath.max(...diff.lines.map(...)), spreading the entire diff line array as function arguments — fine for small diffs, but a stack overflow on diffs with thousands of lines. Replaced with a loop-basedmaxLineNumber()that returns identical results. No visual or behavioral change. - Shiki import no longer leaves a dangling rejected promise — a failed
import("@shikijs/cli")(missing dep, transient error) previously left a permanently-rejected promise that could surface as an unhandled-rejection crash under strict modes. The loader now resets on failure so the next render retries.
- Lower CPU / heat during long-running bash — the bash tool's live preview re-split and re-filtered the entire output on every partial update (bash throttles updates every ~100ms and the pending-dot blink re-invalidates every 500ms), scaling linearly with output size. It now collects only the visible tail lines and a total count in a single pass, so cost no longer grows with output length.
- Bounded Shiki concurrency for multi-edit / multi-file diffs — edit and
apply_patchcall-phase previews previously fired all syntax-highlighting jobs at once viaPromise.all, causing CPU spikes on large multi-block diffs. They now run with a small concurrency cap (2), preserving ordered output. - Spinner no longer keeps running after the UI stops — the 250ms Loader animation loop (and its
requestRendercalls) kept firing after the TUI was stopped. It now short-circuits and stops itself when the UI is stopped, so it can't keep the event loop or CPU alive as an orphan. - More timers
unref'd — the deferred chrome-rebindsetTimeout(fired on/resume//new//fork) and the same-frame working-messagesetTimeoutwere not unref'd, keeping the Node event loop alive. Both nowunrefso they can't hold the process open or spin idle.
No functionality changed in this release — output is byte-identical for all existing cases; the diffs above are strictly CPU/stability improvements verified by npm run typecheck and bun scripts/benchmark-tools.ts.
- "Turn took" line no longer appears mid-stream — the end-of-run status line was showing while the assistant was still streaming text. Root cause: the component render path gated on
message.stopReason === "stop", but the Anthropic provider initializes the live message'sstopReasonto"stop"at creation and only updates it to the real value whenmessage_deltaarrives near the end of the stream — so the gate was already true during streaming. The component path now gates on theexplicitDurationflag stamped by themessage_endhandler (which fires aftermessage_delta, once the realstopReasonis known), so the line appears only after the stream truly closes. Themessage_endpath was already correct (it fires post-message_delta); only the live component fallback was premature.
- Renamed "Worked for" → "Turn took" — the end-of-run status line now reads
✻ Turn took 2m 30s (Total time 1h 12m 30s · 14 turns). The session-total duration now always shows seconds and only adds minutes/hours once the session has actually lasted that long (e.g.45s,12m 30s,1h 12m 30s); the bracket label is now capitalized as "Total time".
- "Worked for …" only on the true end of a run — the line now appears only when the model finishes all of its turns for a prompt (
stopReason === "stop"), instead of after every assistant message that didn't end in a tool call. Intermediate stops that pi retries through (error,aborted,length/max-tokens, compaction retries) no longer get a premature "Worked for" line — it shows once, when the model is actually done. - Session total + turn count on the Worked line — the line now reads
✻ Worked for 2m 30s (total time 1h 12m · 14 turns), where the bracket is the running session-wide elapsed time and the number of prompts you've sent. Totals are seeded from the full message history, so/resumepicks up past prompts and the original session start./newresets the counters.
- Scrolling / expand lag on long chats — every re-render (scroll, tool expand, theme tick) re-ran the per-line ANSI stripping behind copy-zone markers (
applyTerminalCopyZones), per-line glyph normalization, and user-message border boxing for every message in the history. That work scaled linearly with chat length and dominated CPU on long sessions (the more messages, the slower each frame). The rendered output of assistant, user, and custom-message components is now memoized per(width, branch-visual-epoch)on the component instance and reused on warm re-renders, with the cache dropped whenever content actually changes (updateContent/rebuild) or the theme chrome epoch bumps. Warm re-render of a 120-message history drops from ~5.9 ms to ~0.16 ms and stays flat as the chat grows instead of scaling with it. Output is byte-identical (same rendered line counts and content); no functionality changed.
- Transparent tool rows after
/resume— Pi’sToolExecutionComponentuses the global theme singleton fortoolPendingBg/toolSuccessBg/toolErrorBg. Re-apply transparent overrides on that object and before everyupdateDisplay(), with extra deferred chrome rebind after history rebuild on resume/new/fork. - Stale tool row chrome on theme switch — bump branch/render epoch when the active theme name or color fingerprint changes so cached tool lines pick up new palette.
- Branch connectors default —
├─└─│use fixed rgb(72) unless you set/cc-tools branch themeor a custom gray./cc-tools branch resetrestores that default.
- Resume / session switch theme mix — on
session_start(especiallyresume,new,fork), rebind tool chrome from the active pi theme (palette cache bust, Shiki light/dark, branch epoch, full UI invalidate) plus deferred passes so other extensions cansetThemein the same tick without cross-package coupling. - Hidden thinking summary sticks on "Thinking…" when
thinking_endlands on the same frame as Pi'supdateContent— per-message active/duration flags plus a deferred UI refresh so "Thought for Ns" appears right away. - Spinner footer applies the same deferred sync on thinking start/end so "thought for Ns" shows immediately when thinking finishes.
- Unified container chrome — user message box, tool outline rules, rounded code fences, and branch connectors share one theme-derived color (
dim→muted→borderMuted) so light themes do not get harsh dark user borders or overly bright branches. - User message fill — strip nested
Box→Markdownbackgrounds so the framed user row stays transparent and matches terminal chrome (fixes dark slabs inside the border). - Light-theme branch chrome — when the active theme has a light panel, outline/branch colors are attenuated toward mid-gray so
├─└─│and user borders are not washed-out bright;/cc-tools statusno longer implies theme mode uses fixed gray 72.
- Theme-adaptive tool chrome re-derives when the active pi theme’s resolved colors change (fingerprint of
success,borderMuted,accent, etc.), not only when the theme object identity changes. Fixes stale borders/dots/diffs after external theme sync (e.g. Ghostty) without coupling to other extensions.
- Palette cache tracks
theme.nameplus color fingerprint; removed cross-extension global bust symbols.
- Internal: theme name in cache key (superseded by 1.0.56 fingerprint).
- Branch connectors (
├─└─│): defaultthememode (was fixed gray). Uses dim → muted → thinkingText, same family as thought/gray prose. - Pending tool dots (○): use theme dim when theme-adaptive; grouped counts use the same pending color.
/cc-tools branch resetrestores theme-following default, not fixed rgb(72).
- Light theme edit/write diffs: auto-select Shiki
github-lightvsgithub-dark; light panel tint base; Shiki contrast normalization for light backgrounds. - Light theme tool status chrome: pending ○ / blink uses softer
borderMutedinstead of heavymuted; grouped tool pending counts match.
- Theme-adaptive diff and branch tooling updates.