fix(tui): silence default Logger + Claude Code architecture writeup - #81
Merged
Conversation
…ude Code
Two parts, one PR:
1. **Bug fix:** the default framed-block Logger was still writing
to stdout while the OpenTUI alt-screen was rendering. The output
bled through ("Checking formatting... Finished in 476ms..." and
trailing "ted"/"eck" from "executed"/"check" peeking past task
list rows). User screenshot confirmed.
Fix: new `noopLogger()` in src/orchestrator/logger.ts. cli/run.ts
passes it to runOrchestrator when the TUI is mounted; the TUI's
Observer is the only output sink during a TUI run.
2. **Investigation:** wrote up Claude Code's TUI architecture in
`docs/design/tui-claude-code.md` after the user asked how CC does
it. Findings: CC uses `react-reconciler` (like Ink) but with their
OWN custom cell-buffer renderer (Tb1 class with stylePool /
charPool / hyperlinkPool object pools), an op-list optimizer
that collapses adjacent cursorMoves and dedupes styles, and
`fs.writeSync` for sync writes to stdout. This matches Option A
from `docs/design/tui-rebuild.md` — hand-rolled cell buffer.
They built it themselves because the existing libs (incl. Ink)
weren't enough for their polish bar.
Doc revises the available options list:
- Stay on OpenTUI/Solid (current; user-rated "trash")
- Switch to Ink (proven; ~1-2 days)
- Hand-roll like CC (~2 weeks; highest ceiling)
- Drop TUI entirely; invest in non-TUI output (Turbo-style)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two parts:
1. Real bug fix: framed Logger bleeding into TUI alt-screen
Your screenshot confirmed it — the default framed-block Logger
kept writing to stdout (
Checking formatting...,Finished in 476ms..., trailingted/eckfrom "executed"/"check") whileOpenTUI's alt-screen was also rendering. The two interleaved.
Fix: new
noopLogger()insrc/orchestrator/logger.ts. When theTUI is active, the orchestrator gets the no-op logger and the
TUI's Observer becomes the only output sink. The framed-output
goes nowhere; the TUI owns the screen.
2. Investigation: how Claude Code does it
You asked. I reverse-engineered the bundled
@anthropic-ai/claude-codev2.1.42 (~7,500 LOC of minifiedcli.js). Full writeup indocs/design/tui-claude-code.md.Short version: CC uses
react-reconciler(the same primitiveInk uses) but with their own custom cell-buffer renderer. They
wrote it themselves — Ink wasn't enough.
The cool bits I could identify:
class Tb1— their renderer, withstylePool/charPool/hyperlinkPoolobject pools (zero per-frame GC churn).vOAthat collapses adjacentcursor moves, dedupes styles, cancels cursor show/hide pairs,
drops zero-width ops. Emits the minimum ANSI bytes per frame.
fs.writeSync(notprocess.stdout.write) so bytes hit theterminal before the next JS tick — no buffer-then-flush race.
progress + notifications.
This matches Option A from
docs/design/tui-rebuild.md—hand-rolled cell buffer. CC's existence is the strongest signal
that this is the right path for ambitious terminal UIs.
Revised options after seeing CC
The user's "all trash frozen" feedback rules out A. My
recommendation: D first (ship a great non-TUI in days), then
C if you want a real TUI (2 weeks but worth it). Ink (B) is the
"safe middle" but every screenshot I've seen of an Ink-only TUI
has been "fine" not "amazing".
Test plan
bun src/bin.ts run ci— 3/3 pass (format-check + lint + 436 tests)vx run ci --tuiwith this PR merged; framedoutput should no longer bleed into the alt-screen.
Tell me which option to pursue. If it's D or C, the existing TUI
code under
src/tui/should be deleted in a follow-up PR.https://claude.ai/code/session_016HXj6HW6bxSn8EYuKcxTD9
Generated by Claude Code