Add "tasks" and "task-detail" kinds to PreviewFrame, backed by a
sampleTaskBoard work graph that exercises every readiness glyph and
status color (epic blocked on children, a ready child, in-flight, done).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the Go TUI from github.com/charmbracelet/{bubbletea,lipgloss} to the v2
ecosystem at charm.land/{bubbletea,lipgloss}/v2 (Go 1.25). v2's kitty keyboard
disambiguation is on by default, so Shift+Enter now reliably inserts a newline
in the composer (Ctrl+J / Alt+Enter kept as fallbacks for non-kitty terminals).
Approach: rather than rewrite ~190 key-match sites to v2's (Code, Mod) idiom, a
small shim (internal/app/key.go) converts a v2 KeyPressMsg into the v1-shaped
keyMsg the handlers already expect, at the single Update boundary. The rest is
mechanical:
- key constants tea.Key* → shim consts; tea.KeyMsg → keyMsg.
- lipgloss.Color is now a func returning color.Color, not a type → fields/params
retyped to image/color.Color (theme/diff/overlays/view).
- v2 View() returns tea.View: render() builds the string, View() wraps it and
carries AltScreen (alt-screen is a per-frame View field now, not a program opt).
- WithWhitespaceBackground/Foreground → WithWhitespaceStyle(Style).
- preview: drop lipgloss.SetColorProfile (v2 renders truecolor by default).
Build, vet, gofmt, and the full test suite are green; preview renders truecolor
across kinds.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Press d (or the "panel" palette command) in-session to cycle the right panel:
- events — the live event stream (default, unchanged)
- changes — a token-usage line (tokens + turns, summed from the transcript's
TurnMetrics) over a git-status-style list of files the session has
written, each with summed +/− from its diff. ^x still opens the full
diff.
- off — hide the panel; the output transcript takes the full width.
changesRows derives everything from data already in the model (router-turn
metrics + the tool entries' unified diffs), so no new protocol. Footer + ? help
updated; "changes" preview kind added.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Enter still submits; Ctrl+J (always) and Alt+Enter (most terminals) insert a
newline at the cursor. True Shift+Enter isn't distinguishable from Enter in
bubbletea v1.3.10 (terminals send the same byte, no kitty-protocol parsing), so
these are the working stand-ins — noted in the input hint.
- editorLines() renders the buffer as styled rows with the caret at the cursor,
splitting on \n and capping at maxInputLines (last rows kept). Both the idle
launcher input and the in-session bottom bar use it and grow to fit; View()
sizes the bottom bar via inputBarHeight() instead of the fixed inputH.
- Footer gains a "^J newline" hint in insert mode (non-filter); the launcher
sub-line shows "⌥↵/^J newline". New "compose" preview kind + editor_lines_test
(split + height cap).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The launcher removed the idle session list, so the per-row date moves to the R
resume overlay — now the sole session list. sessionListRow shows an absolute
local date (absDateISO → shortDateTime) alongside the relative age:
"Jun 22 14:30 · 5m ago", with the year shown for older-than-this-year rows.
Delete the now-unused idle-panel renderers (sessionRows / welcomeRows /
workflowRows) and renderMainNarrow's dead idle branch (renderLauncher owns idle,
narrow included). Add a "resume" preview kind to screenshot the R overlay.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three TUI-polish items.
- OUTPUT free-scroll: PgUp/PgDn (and ^u/^d half-page) scroll the transcript
back through history; esc snaps to tail-follow. outputScroll is clamped
against outputViewport(), which mirrors View/renderMain geometry, so the
edges land exactly (no dead presses unwinding an overshoot). routerRows
split into buildTranscriptRows (full render) + windowing so the handler
measures the same content. Tests cover the clamp + the fits-in-panel no-op.
- Help overlay: `?` (or the "help" palette command) opens a keybinding
cheat-sheet grouped by context (session / overlays / approval band) — the
non-palette keys especially. Any key dismisses it.
- Event-inspector filter: `/` filters the event list by a case-insensitive
substring of type/detail (currentEvents applies it); `c` clears, esc stops
editing then closes. Fresh per open. EVENTS side panel stays unfiltered.
go build / vet / test green; rendered help + filtered inspector via
cmd/preview (help, events-filter kinds).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The ⏵ tool-start row doubled up with the ✓/✎ result row in OUTPUT. Drop it
inline — tool starts still show in the tool list + EVENTS panel. The inline
transcript now carries only outcomes: ✎ writes, ✓/✗ tool results, ⌘/✕
approvals, ⊞/⊟ grants.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Surface side-effecting ("external feedback") events in the conversation
flow, opencode-style, instead of only in the EVENTS side log.
- New RouterEntry role "action" (glyph + text) appended at arrival so it
interleaves with chat turns by order. Rendered dim with an accent gutter
glyph; the EVENTS panel still carries the full stream.
- Surfaced: tool start (⏵), tool done (✓ / ✎ wrote <path> (+a −b) for a
diff), tool failed (✗), rejected (✕ blocked), approval resolved
(⌘ approved / ✕ rejected, noting "· via grant" on a grant auto-approve),
and grant create/revoke (⊞ / ⊟ · scope). ApprovalResolved names no tool
on the wire, so the tool is recovered from the pending queue before the
gate is dropped.
- Toggle: "inline actions" palette command flips actionsHidden (rows are
always recorded, gated at render, so toggling reveals history); persisted
in tui-prefs.json. prefs.go refactored to load/mutate/save so the new
field and statusbarHidden no longer clobber each other.
- demo.go: "actions" preview kind.
go build / vet / test green; rendered the flow via cmd/preview.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Surface the new cross-session grant scopes in the TUI.
- Approve-always (A) now opens a scope picker instead of immediately
creating a SESSION grant: choose this session / this project / everywhere.
SESSION stays the default (A then enter/s = old behaviour); p and g
create PROJECT / GLOBAL grants. The grant + approval are sent on confirm
(esc cancels, leaving the call pending).
- New "grants" palette command + G shortcut open a standing-grants viewer
(OverlayGrants) listing the active PROJECT/GLOBAL grants — scope, tool,
permitted tiers, project path — with x/enter to revoke. The server's
RevokeGrant reply (a fresh grant.list) refreshes it in place.
- protocol.go: TypeGrantList + GrantDto; RevokeGrant/ListGrants encoders;
CreateGrant now documents the wider scopes. server.go decodes grant.list
into m.grants and treats it as a non-session global reply.
- render-matrix coverage: grant.list classified as a non-rendering query
reply (populates the overlay, not the transcript).
- demo.go: grants + grant-scope preview kinds.
go build / vet / test green; rendered both overlays via cmd/preview.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- pending approvals are a navigable queue (PendingQueue/PendingIdx; ↑↓/jk, count
shown), no modal stacking; resolve removes the specific gate by request id
- y/n/e keys (approve/reject/steer); T0–T2 act on one key, T3+ requires arm+confirm
("press y again"), any other key disarms; reject/steer/auto never gated
- snapshot restore now rehydrates the full pending list, not just the first
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Surface the health subsystem (llama/event-store/disk probes) in the TUI,
mirroring the session-stats pane: ClientMessage.GetHealthChecks ->
ServerMessage.HealthChecks (health.checks, reuses HealthReport) ->
StreamQueries.healthChecks via HealthInspectionService; Go OverlayHealth on
key H + palette 'health checks', pull-based fetch, per-subject status with
degraded loud. Empty/disabled -> visible fallback (no blank/lie). Go+Kotlin
golden tests pin the wire contract field-for-field. Observability spec §4/§3.
Layer 1 of tui-requirements §5: a no-LLM analyzer that shell-lexes a
proposed command, classifies binaries against a known-command table, and
mechanically flags the constructs that widen blast radius — sudo/doas,
recursive delete, pipe-to-shell (skipping wrappers so `| sudo sh` counts),
redirects outside the workspace, network binaries, base64/eval, and
unparseable input (itself a red flag). Pure + replay-stable: same preview
+ workspaceRoot → same analysis, no environment reads.
The approval band renders command approvals as a card — worst-first flag
chips over the reconstructed command line, each token colored by severity
(T0–T4 ramp). ^x opens a scrollable fullscreen view so a long command is
never truncated. Diff and plain-preview paths are unchanged.
Server: computeToolPreview now renders the shell tool's argv as a full,
shell-quoted command line instead of the 200-char JSON-args fallback,
honouring §3's "full untruncated command". The TUI parser accepts both
the rendered line and the older `{"argv":[...]}` shape for replay.
Layer 2 (model annotation) deliberately deferred per the spec.
Implements the load-bearing TUI-requirements §2 hard requirements:
- Last-event clock: "last event Ns ago" always visible in the status bar while
in-session, updating every frame tick. Turns warn-colored when an active session
has gone quiet past the stale threshold — the "thinking vs hung" tell. The
WebSocket connection indicator (●/◌/○) already covers immediate drop visibility.
- Unknown-event raw fallback: applyServer now surfaces any unrecognized,
session-scoped event type as a raw row in the event stream instead of silently
dropping it (the frontend must not lie by omission). Recognized-but-unrendered
types (router.response, protocol_error) get an explicit no-op so the default
branch means genuinely unknown.
Tests: agoLabel formatting, unknown-event surfaces-as-row, known-ignored
no-spurious-row. Preview "session" frame exercises the clock.
Deferred: the full Go↔Kotlin per-event-type render matrix (§2 last bullet) — a
larger fixture effort than the core behaviors landed here.
SessionWorkspaceBoundEvent already recorded the bound workspace root but
it was never surfaced. Map it to a new session.workspace_bound frame
(sealed ServerMessage variant, auto-registered; re-emitted on reconnect
via the snapshot replay), carry it on Session.WorkspaceRoot, and show it
home-abbreviated in the status bar (⌂ ~/Programs/correx). Golden test
pins the decode.
The steer note replaced the action row while editing and vanished once
you esc'd out — so you couldn't see the note or the approve/reject keys,
and it was unclear the note still applied (it did: decide() reads the
buffer). Now the note is a persistent line above the action row (editable
with a caret while typing, a dim reminder once set), and the action row
stays visible. Editing hints clarify enter=approve+send, esc=keep note.
Three QA-found issues in the approval gate:
- displayState gated the in-session/approval surfaces on hasApproval
before sessionEntered, so moving the list cursor onto a session with a
pending gate auto-opened it, and `l` back-to-list couldn't escape
(the gate re-popped). Require sessionEntered first.
- The band ran every preview through the two-column diff renderer, so a
shell tool's argv JSON rendered as an identical-column "diff". Detect
real unified diffs (isUnifiedDiff); render other previews as plain
text, and drop the ^x fullscreen hint when there's nothing to expand.
- The footer duplicated the band's approve/reject/steer/diff keys. It now
shows navigation (l back / e events / q quit) instead.
The plane-2 tool-call assessor records verified preconditions
("[PATH_OUTSIDE_WORKSPACE] …") and the server already ships them in
RiskSummaryDto.rationale, but the Go TUI's RiskSummaryDto had no
Rationale field — so the justification was silently dropped at decode
and the gate showed only an opaque tier.
Decode the rationale, carry it on Approval, and render it under the
header in the approval band (warn-marked, above the diff). This closes
the last deferred item of the plane-2 slice-1 plan: the assessment
surfaced to the approval UX. Golden test pins the rationale decode.