TUI bus & surface review — disposition¶
Status: disposition recorded (2026-07-18, sprint 2026-07-18 Phase 4) · closes DEBT-61
Scope: internal/tui/ (model ~1.6k LOC), cmd/cards/tui.go, the bare-cards guard in cmd/cards/main.go, and the in-process bus subscription the TUI makes on core.Service.
References: DEBT-61 (debt-ledger.md), TUI landing 0421efd, bus internal/core/bus.go.
DEBT-61 was filed as a review gap (fresh surface, no debt-ledger coverage), with four sub-questions and one escalation question. Findings and dispositions below.
Sub-questions¶
1. Dependency health (bubbletea v2 / lipgloss v2 / glamour) — sound¶
No nolint exclusions anywhere in internal/tui/ or cmd/cards/tui.go. The
dependency set is the deliberate price of a terminal UI (landing commit
0421efd); binary size (~30 MB) is consistent with the rest of the single
binary. No action.
2. Live-refresh subscription teardown — sound (verified)¶
tui.go unsubscribes on model teardown
(defer svc.Bus().Unsubscribe(m.sub.ID), which also releases any blocked
waitEvent goroutine). Subscriptions are registered lazily on first board
view and released on quit paths. No leak found. No action.
3. Actor resolution matches the rest of the surfaces — sound¶
cmd/cards/tui.go resolves actor exactly like the CLI: --as → CARDS_USER
→ USER → workspace default_user, and all service writes go through
core.WithActor(ctx, actor) (m.ctx()), the same seam the HTTP transport
uses via X-Work-Cards-Actor. No action.
4. Markdown detail pane — one real defect found and fixed¶
The review exposed a genuine bug: inbound links silently never rendered.
syncExtras queried inbound cards (LinkTarget) without eager-loading
their link sets, but the markdown renderer reads the relation type from each
inbound card's own links — so the ← <type> row could never appear. It went
unnoticed because the seeded board's most-recent card previously had no
inbound links; a sprint's board mutations moved a card with inbound links
into the selected slot and TestCardMarkdownSections went red.
- Fix (landed):
syncExtrasnow lists withInclude: ["links"](internal/tui/tui.go). - Test hardening (landed):
openDemonow snapshots the demo DB likeopenDemoCopy— render tests were reading the livework-cards.db, so any dogfooding mutation (comments, status flips) could flip content assertions. Hermetic now.
Non-TTY / script safety — regression test landed¶
The interactive() guard (both streams TTYs, no --json/--jsonl) had no
test. cmd/cards/interactive_test.go now pins: piped stdin → never
interactive; bare run() with piped stdin prints usage. `cards