Skip to content

fix(tui): silence default Logger + Claude Code architecture writeup - #81

Merged
Exelord merged 1 commit into
mainfrom
claude/tui-silence-logger
May 13, 2026
Merged

fix(tui): silence default Logger + Claude Code architecture writeup#81
Exelord merged 1 commit into
mainfrom
claude/tui-silence-logger

Conversation

@Exelord

@Exelord Exelord commented May 13, 2026

Copy link
Copy Markdown
Member

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..., trailing ted/eck from "executed"/"check") while
OpenTUI's alt-screen was also rendering. The two interleaved.

Fix: new noopLogger() in src/orchestrator/logger.ts. When the
TUI 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-code v2.1.42 (~7,500 LOC of minified
cli.js). Full writeup in docs/design/tui-claude-code.md.

Short version: CC uses react-reconciler (the same primitive
Ink 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, with stylePool / charPool /
    hyperlinkPool object pools (zero per-frame GC churn).
  • A render-op-list optimizer vOA that collapses adjacent
    cursor moves, dedupes styles, cancels cursor show/hide pairs,
    drops zero-width ops. Emits the minimum ANSI bytes per frame.
  • fs.writeSync (not process.stdout.write) so bytes hit the
    terminal before the next JS tick — no buffer-then-flush race.
  • KITTY / GHOSTTY / iTerm2 protocols for inline graphics + title-bar
    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

Option LOC/Time Quality ceiling
A. Stay on OpenTUI/Solid 0 / 0 You called it trash
B. Switch to Ink ~1–2 days Proven (GitHub CLI, Vercel CLI)
C. Hand-roll like CC ~2 weeks CC-grade
D. Drop TUI, double down on non-TUI output ~3 days Turbo-grade framed blocks + sticky progress

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)
  • Visual: re-run vx run ci --tui with this PR merged; framed
    output 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

…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)
@Exelord
Exelord merged commit 88ce354 into main May 13, 2026
1 check passed
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.

2 participants