singularity-forge/docs/dev/ADR-017-charm-tui-client.md
2026-05-07 03:33:14 +02:00

7.8 KiB
Raw Permalink Blame History

ADR-017: Charm TUI client — extracting pi-tui out of sf core

Date: 2026-04-29 Status: proposed (deferred — capture for staged execution)

Context

sf today bundles its TUI directly in core: pi-tui (~10.5k LOC of TypeScript) is loaded whenever the user interacts with sf. The TUI lives at the same architectural layer as the agent loop, the auto-loop, and the planner. This couples what sf does to how it presents.

Three forces argue for extracting the TUI:

  1. sf is becoming truly headless-firstpackages/daemon and packages/rpc-client already exist. CLI invocations talk to the daemon. SF uses MCP as a client integration surface for external tools, not as an SF workflow server. The user-facing TUI is one client; it shouldn't be baked into the engine.
  2. The Charm TUI stack is dramatically more capable than what pi-tui builds today. bubbletea + bubbles + lipgloss + glamour + huh + harmonica + x/mosaic (image rendering) + x/vcr (recording) + pony + ultraviolet (declarative markup) compose to far better UX than reproducing in TS would.
  3. Removing pi-tui from sf core deletes ~10k LOC of TS — leaner core, fewer TUI-coupled assumptions in pi-coding-agent, cleaner test surface.

This ADR plans the extraction.

Decision

  • Build a new sf-tui client in Go using the Charm stack. Talks to the sf daemon over the existing RPC (per packages/rpc-client).
  • View layer: pony (declarative TUI markup) + ultraviolet (its base). Adopted now, not deferred. Other view primitives where pony lacks coverage: bubbles components, lipgloss styling, glamour markdown, huh forms, harmonica animations, x/mosaic for inline images.
  • Two-stage replacement of pi-tui:
    • Stage 1: new sf-tui ships parallel to pi-tui. Users opt-in via sf --tui=charm. pi-tui remains the default. Both clients connect to the same daemon — they're peer clients, not replacements yet.
    • Stage 2: when sf-tui reaches parity (every screen pi-tui has, plus the new ones the Charm stack enables), flip the default. Deprecate pi-tui with a warning. After two minor releases, delete pi-tui entirely — ~10k LOC of TS dropped from sf core.
  • No migration of in-flight pi-tui work. Anything in pi-tui that hasn't shipped doesn't get backported to sf-tui. The new client is a clean slate.
  • Architecture: clean separation between view rendering and state/data layer. State models live in their own package; view components consume them. If pony proves unworkable, the swap to plain bubbletea is a view-layer-only refactor.

Alternatives Considered

  • Replace pi-tui in-place with a TS port of Bubble Tea. No mature TS port exists. Even if one were started, Charm's TUI ecosystem (Bubbles, Lipgloss, Glamour, Huh, etc.) wouldn't follow.
    • Rejected: equivalent to "rebuild the Charm stack in TS." Years of work for no advantage.
  • Embed Bubble Tea inside pi-coding-agent via cgo / WebAssembly.
    • Rejected: fragile FFI; defeats the architectural goal of separating engine from UI.
  • Keep pi-tui indefinitely; only build Charm TUI as an alternative for SSH access.
    • Rejected: leaves ~10k LOC of TS in sf core forever as a maintenance burden. The whole point is to delete it.
  • Don't build a new TUI; expose the daemon over an external API and rely on third-party clients (Claude Code, Cursor) to render.
    • Rejected: sf's user-facing surface is the TUI when working interactively. Outsourcing it removes a major UX touchpoint we own.

Consequences

Positive

  • sf core gets ~10k LOC leaner after Stage 2.
  • Charm stack quality comes for free — animations (harmonica), inline images (x/mosaic), markdown (glamour), forms (huh), recording (x/vcr).
  • Headless / API-first architecture is cleanly visible: daemon + RPC + clients, with MCP client integration for external tools. No TUI coupled to engine.
  • Remote TUI for free — once the client is Wish-served (could be a v3.x extension), tailscale ssh aidev sf opens a full TUI session over SSH. Today's pi-tui is local-process only.
  • Recordings of TUI sessions — flight recorder (ADR-015) integrates with the Charm TUI naturally; pi-tui would need separate work to support this.

Negative

  • Two-language UI work during Stage 1 — bug fixes touching both pi-tui (TS) and sf-tui (Go). Bounded duration; one client retires at Stage 2.
  • Pony is pre-1.0 — API churn during the build. Acceptable per the "view layer swappable" architecture.
  • User-facing transition — users have to relearn keybindings or layouts if sf-tui differs from pi-tui. Mitigated by explicit parity gate: sf-tui must match pi-tui's primary views before Stage 2 flip.
  • Daemon RPC contract becomes load-bearing — what was previously an in-process call (TS → TS) is now a cross-process call (Go → TS via RPC). Requires the RPC contract to be stable and complete; missing methods become blockers. Acceptable; this is the right architectural pressure.

Risks and mitigations

  • Risk: parity gate is moved unilaterally (Stage 2 flips default before parity is real).
    • Mitigation: parity defined explicitly as a checklist of pi-tui screens with their sf-tui equivalents and end-to-end tests passing. CI gate.
  • Risk: pony proves unstable; we hit the swap-to-bubbletea fallback halfway.
    • Mitigation: view layer is architected to be swappable (pony components implement an interface; bubbletea components implement the same interface). Swap is a refactor, not a rewrite.
  • Risk: Daemon RPC has gaps that pi-tui papers over via in-process state access.
    • Mitigation: audit pi-tui's direct daemon-state access at the start of Stage 1; promote any in-process patterns to RPC methods.
  • Risk: User keybindings / muscle memory breaks.
    • Mitigation: sf-tui mirrors pi-tui's keybindings 1:1 for the parity surface; new keybindings only for new features.

Out of Scope

  • Web-based UI. Could be a separate v4 project.
  • Multi-user TUI sessions (two operators watching the same auto-loop).
  • Theme customisation. v1 ships one theme; user theming is later.
  • Internationalisation. v1 is English only; same posture as today.

Sequencing

Stage Action Cost Result
Pre-stage Audit pi-tui screens; produce a parity checklist. 1 week List of screens + features sf-tui must cover.
Stage 1 Build sf-tui parallel to pi-tui. View on pony+ultraviolet+bubbles, state separate. Daemon RPC fills any gaps. Ships as opt-in via sf --tui=charm. ~610 weeks Two TUIs coexist. Users pick.
Stage 1.5 Parity verification — every checklist item works in sf-tui; CI gate. 2 weeks sf-tui ready to flip default.
Stage 2 Flip default to sf-tui. Deprecate pi-tui with warning on use. 1 week + soak sf-tui is canonical; pi-tui is legacy.
Stage 3 Delete pi-tui after two minor releases. 1 week cleanup sf core sheds ~10k LOC of TS.

Total: ~1216 weeks across stages.

References

  • packages/daemon, packages/rpc-client — already exist; this ADR makes them load-bearing for clients.
  • packages/pi-tui — the existing TUI being deprecated.
  • ADR-013 — Network: future SSH-served TUI via wish rides the same substrate.
  • ADR-015 — Flight recorder: sf-tui records its sessions naturally.
  • ADR-016 — Charm AI stack adoption (this is one of its concrete arms).
  • charmbracelet/bubbletea, charmbracelet/bubbles, charmbracelet/lipgloss, charmbracelet/glamour, charmbracelet/huh, charmbracelet/harmonica.
  • charmbracelet/x/mosaic, charmbracelet/x/vcr, charmbracelet/x/editor, charmbracelet/x/input.
  • charmbracelet/pony + charmbracelet/ultraviolet — adopted as the view-layer foundation.