Skip to content

fix(tui): cache long-session rendering - #669

Open
daniduro89 wants to merge 2 commits into
code-yeongyu:mainfrom
daniduro89:fix/long-session-render-cache
Open

fix(tui): cache long-session rendering#669
daniduro89 wants to merge 2 commits into
code-yeongyu:mainfrom
daniduro89:fix/long-session-render-cache

Conversation

@daniduro89

@daniduro89 daniduro89 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add revision-aware invalidation and render caching to the TUI component tree
  • reuse stable transcript prefixes and fullscreen scrollbar projections
  • wire interactive messages, tool execution, streaming, and spinner updates into the invalidation path
  • add regression coverage for stable renders, tail-only updates, nested containers, duplicate children, width changes, and fullscreen scrolling

Root cause

Long sessions rebuilt and flattened every historical transcript line on each render. Fullscreen ScrollView then transformed the complete child output for scrollbar styling before clipping it to the viewport. OMO tool streaming and spinner refreshes repeat that work frequently, making each frame O(total rendered history) and causing progressively higher CPU, allocation, and GC pressure.

This is separate from c93e786, which bounds persisted apply_patch result payloads. This branch is based on current main, which already contains that fix.

Cache behavior

The change is confined to terminal rendering and does not alter model request construction. A hermetic baseline-versus-fixed capture produced byte-identical Responses API requests:

  • identical request SHA-256: 32ca2f7408d059d139692de92c4297dce880239a0ee39bf03f785267dbd38b6d
  • identical model, instructions, input, tools, tool choice, parallel-tool setting, and prompt_cache_key
  • localhost-only capture; real auth state unchanged

This preserves Senpi's provider-side prompt-cache hit behavior.

Stress validation

A/B replay used the same 41,082,566-byte, 6,321-entry real Senpi session snapshot against exact upstream main (35af9748b) and this commit, in fullscreen mode at 120x34 with alternating input for about 15.5 seconds:

Metric Upstream main This PR Change
One-core CPU 77.38% 7.94% -89.78%
Peak working set 1,136.83 MiB 1,011.42 MiB -125.41 MiB
Peak private memory 1,153.48 MiB 1,065.33 MiB -88.15 MiB

Both cases ran in isolated offline sandboxes and their spawned Senpi processes were terminated after measurement.

Validation

  • npm ci --ignore-scripts
  • npm run build
  • node --import tsx --test packages/tui/test/container-render-cache.test.ts (6 passed)
  • focused interactive rendering suite (10 files, 102 tests passed)
  • cache integrity suite (6 files, 45 tests passed)
  • Biome check on all 16 changed files
  • request-equivalence harness (raw request and all cache-relevant fields identical)
  • real-session A/B stress harness

The broader TUI suite was also compared before publication: both baseline and fixed runs had 81 passing and the same 7 Windows-environment failures (WSL /bin/bash, chmod semantics, crash-dump environment, and path separators).


Summary by cubic

Add revision-aware render caching with shared-child invalidation to the tui component tree so long sessions don’t re-render history. Scrolling and streaming now update only the changed tail, making fullscreen sessions smooth and much faster.

  • Bug Fixes
    • Cache stable prefixes and fullscreen scrollbar projections across Container, ScrollView, Text, Markdown, Spacer, Box, and DynamicBorder.
    • Add revision and invalidation APIs (getRenderRevision, getRenderChangeStart, setRenderInvalidationCallback, isRenderCacheTrackable) and binding‑aware Container mutations (insertChild, replaceChild, detachChildrenFrom); children is now read‑only. Invalidation now fans out to every parent for shared children and supports repeated references; coding‑agent paths were updated to use these calls.
    • Wire invalidation through interactive pieces: assistant/user/custom messages, tool execution, loader, and spinners.
    • Results: ~-90% one-core CPU and lower peak memory in a 6k-entry session; tests cover cache integrity, tail-only updates, shared-child fan-out, repeated references, width changes, and fullscreen scrollbar projection.

Written for commit d13301a. Summary will update on new commits.

Review in cubic

@daniduro89
daniduro89 marked this pull request as ready for review August 3, 2026 07:40
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@daniduro89

Copy link
Copy Markdown
Contributor Author

Thanks — the shared-child concern was real. This TUI does legitimately project one component through multiple containers, so I implemented fan-out instead of asserting a strict tree:

  • one dispatcher occupies the component's callback slot and notifies every containing parent
  • duplicate references inside one parent remain reference-counted
  • detaching from one parent removes only that parent's subscription
  • the new regression test mutates a shared child, verifies both parents refresh, detaches one parent, then verifies only the remaining parent refreshes

I also closed the mutable-array footgun: Container.children is now a read-only view backed by private storage, and mutations go through the binding-aware methods. I grep-audited direct mutations and converted the live TUI-container cases in test/chat-simple.ts; remaining matches are unrelated layout/data-tree models or comments.

The smaller notes are covered too: Box now documents why bgFn("test") is only a best-effort compatibility probe, and a no-change render resets changeStart to the end of the cached output.

Validation on d13301a92:

  • render-cache/layout tests: 19 passed, including 8 focused cache regressions
  • prompt-cache affinity/retention tests: 76 passed, 5 skipped
  • root npm run check and npm run build: passed
  • hermetic Responses request SHA remains 32ca2f7408d059d139692de92c4297dce880239a0ee39bf03f785267dbd38b6d; request fields including prompt_cache_key are unchanged
  • the 41,082,566-byte / 6,321-entry real session was replayed again with the native Windows ConPTY backend for 150 interactions; the source session and auth files remained unchanged

The PR body now includes the implementation details and refreshed validation evidence.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant