Case study 05

A control tower for every Claude Code session on a machine.

Role
Solo engineer
Year
2026, open source
Stack
Next 16, Node, node-pty, xterm.js, ws, SSE
Links
180checks in the deck smoke suite, run against a real production build1
10 sinterval at which core reconciles every session2
70commits on main3
CIlint, typecheck, format, roadmap and tests run as one gate4
  1. README.md, "npm run build && npm run smoke"
  2. README.md, "The deck updates itself"
  3. git rev-list --count main in the flightdeck repo
  4. README.md, "npm run check" (same as CI)

The problem

Running Claude Code on several projects means sessions scattered across terminals. Which one is blocked on me? Which one is burning quota? Which one finished an hour ago?

Flightdeck puts every session on one screen.

What I built

A local web app and a small Node core service.

  • A live deck. Lists every session on the machine and updates itself over server-sent events, so the page never polls. Core re-checks every session on a 10 second sweep, and the page recovers on its own when core restarts.
  • Background sessions. Start one from the deck and attach to it.
  • A real terminal in the browser. A pty on the machine, streamed to xterm.js over a WebSocket, so you can type into a Claude Code session from the page.
  • Hook and status line receivers. Endpoints that Claude Code’s hooks and status line can post to, so a blocked session shows up in about a second instead of waiting for the next sweep.

Architecture

Deck in the browserNext 16, xterm.jsCore serviceNode, 127.0.0.1 onlyClaude Code sessionsevery session on the machinepty hostnode-ptyHooks, status linePOST receivers

AI design

Flightdeck makes no model calls of its own. Its job is to supervise Claude Code, so the design questions are about control.

  • Sessions are data, not a black box. Core reads session state, quota and context percentage from what Claude Code already emits.
  • Failure shows up. Hooks pointed at a receiver that is down put an error banner in every session, so the installer refuses to run unless core is up.
  • Local by construction. The service binds to loopback and guards its HTTP surface with a token.

How it was built

Written down first: a spec, a threat model with numbered controls, a roadmap in YAML with gates, and a decisions log that points at the research behind each choice. Tasks were built on branches named after their roadmap id, and a single command runs the same checks as CI.

Tradeoffs

  • Only background sessions can be attached. Claude Code’s attach command takes background sessions only. A session you started in a terminal yourself shows on the deck read-only, with the reason on the row.
  • A web app first, a desktop shell later. The decisions log picks a local web app now and schedules a Tauri shell for when the terminal ships, because a browser eats Ctrl+W. Electron was rejected.
  • Collected but not yet drawn. The README says quota gauges, a projects map and search are not built yet.
WorkHire me