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

95 lines
7.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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-first**`packages/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.