How I Ported Claude Code’s Rendering Engine to Modern C++
Twelve parts. One terminal. Zero flicker.
I wanted to understand how Claude Code renders its terminal UI. So I did what any reasonable person would do: I decompiled the Bun-compiled binary, traced through thousands of lines of minified JavaScript, reverse-engineered the entire rendering pipeline, and then rebuilt it from scratch in C++23.
What I found was genuinely surprising. Claude Code doesn’t just printf its way through terminal output like most CLI tools. It runs a real rendering engine — the kind of architecture you’d find in a game engine or GPU compositor. Double-buffered screen buffers. Interned styles with pre-computed transition caches. Damage-tracked differential rendering. Hardware scroll regions. The works.
This series documents the whole journey: what I found in the binary, why it works the way it does, and how I redesigned it in modern C++ with phantom types, constexpr evaluation, zero-cost abstractions, and compile-time guarantees that JavaScript can never provide.
The Series
Part 1: The Problem — Why Terminals Are Broken
I found a race condition in my own code. Two threads, one stdout, total corruption. This is the bug that sent me down the rabbit hole — and it turns out every serious TUI has to solve the same fundamental problem.
Part 2: The Screen Buffer — A 2D Cell Grid
Every pixel on a terminal is a cell. Claude Code stores them in a flat array with a dual-view memory trick that makes comparison blazing fast. Our C++ version does the same thing — without the trick.
Part 3: Cell Packing — Fitting a Universe into 8 Bytes
How do you fit a character, a style reference, a width flag, and room for future expansion into exactly 8 bytes? Claude Code uses bitfield packing. We use struct layout. Both compile down to a single cmp instruction.
Part 4: Interning Pools — Strings Are Integers Now
The moment you intern a style, comparison becomes integer equality. The moment you cache the transition between two styles, rendering becomes a hash table lookup. This is where the real magic happens.
Part 5: The Output Builder — Recording Before Rendering
Claude Code doesn’t render directly. It records what it wants to do — blit, write, clear, clip, shift — then replays the recording onto the screen buffer. This decoupling unlocks optimizations that immediate-mode rendering can’t touch.
Part 6: The Render Tree Walk — From Layout to Cells
The renderNode function is the most complex piece in the pipeline. It walks the component tree, decides what to re-render and what to skip, and orchestrates the entire frame. We extracted it from the minified binary, and it’s beautiful.
Part 7: The Diff Engine — Only Paint What Changed
The heart of the renderer: compare two screen buffers, emit the minimal ANSI to transform one into the other. Every optimization upstream exists to make this loop’s job easier. Every optimization in this loop directly reduces bytes to stdout.
Part 8: Style Transitions — The Transition Cache
Computing the minimal ANSI to go from style A to style B. Caching the result forever. This single optimization saves 30-50% of terminal output and turns the diff engine’s inner loop into a hash map lookup.
Part 9: Hardware Scroll — Moving Rows Without Rewriting
The terminal can shift rows for you. You just have to ask nicely (with CSI sequences), and then lie to your own diff engine about what the previous frame looked like. 99% reduction in scroll I/O.
Part 10: Double Buffering and Frame Lifecycle
Two buffers. Swap on every frame. Clear, render, replay, diff, emit, swap. The lifecycle of a single frame, from event to pixel, in ~230 microseconds.
Part 11: Thread Safety — The Compositor
The spinner thread and the main thread both want stdout. They can’t have it. The Compositor is the bouncer: one lock, one writer, zero corruption. Plus: the self-pipe trick, mutable regions, and why the lock must cover the write() syscall.
Part 12: Putting It All Together — The Full Pipeline
The complete architecture. The main.cpp that wires it all. What changed from the broken version to the working one. Performance numbers. Future directions. And what I learned from reading someone else’s rendering engine.
The Design Philosophy
Six principles emerged from studying Claude Code’s renderer:
- Intern everything, compare nothing. Strings are expensive. Integers are free. Convert every string and style into an integer ID, and suddenly comparison is a single
cmpinstruction. - Cache the transition, not the result. Don’t cache what a style looks like. Cache what it takes to get there from somewhere else. The transition cache is the highest-leverage optimization in the entire pipeline.
- Track damage, don’t scan the world. Only diff the cells that could have changed. Damage rectangles bound the work per frame. Everything else is skipped at full memory bandwidth.
- Record first, render later. The output builder pattern decouples “what to render” from “how to render.” This unlocks blit (reuse previous frame), clip (nested scroll regions), and shift (hardware scroll) — optimizations that are impossible with immediate-mode rendering.
- Types are free documentation that the compiler enforces. Phantom-tagged IDs,
constexprstyle builders,static_asserton struct sizes. The C++ type system catches an entire class of bugs that JavaScript tests can only hope to find at runtime. - The terminal is a GPU. Treat it like one. Scroll regions, synchronized output, cursor positioning — these are hardware features. Use them instead of rewriting entire screens character by character.