Commit Graph

77 Commits

Author SHA1 Message Date
kami 41ed6414c6 feat(guardrails): steering channel + shell-in-file rule + capability-gap detector
Bundles three operator-reliability guardrails (Vikunja #28/#29/#30) plus the
in-flight branch WIP they were built on top of (reasoning_content capture,
operator/project profile editor, write-jail workspaceRoot fix) — the tree is
interdependent (SessionOrchestrator references reasoningArtifactId from the WIP)
and does not compile as separable subsets, so it lands as one commit.

Guardrails:
- #28 mid-stage steering: ClientMessage.SteerSession -> GlobalStreamHandler ->
  orchestrator.submitSteering, reusing SteeringNoteAddedEvent + existing context
  fold (advisory, non-authoritative; invariants #3/#7). Closes the gap where
  steering typed off an approval gate was silently dropped.
- #29 shell-in-file guardrail: ShellInFileContentRule (core:toolintent) blocks a
  file_write whose content is a bare shell command (e.g. "mkdir -p ..."); FileWriteTool
  description now advertises auto-mkdir of parent dirs. Basename-allowlist so the
  extensionless case is caught; scripts/Makefiles/multiline exempt.
- #30 pt1 capability-gap detector: deterministic CapabilityGapDetector maps stage
  intent -> implied ToolCapability, compares to granted tools, emits advisory
  CapabilityGapDetectedEvent in FreestyleDriver.lockAndRun. Recorded, never fails
  the gate and never auto-grants (invariants #3/#4/#5). Reflection rung is pt2.

Verified: ./gradlew check green (whole tree).
2026-07-07 13:27:59 +04:00
kami 527490e5df fix(tui): surface late clarifications and stop bg bleed on input + action rows
- clarDismissed was model-global but only reset when the clarification's
  session was selected on arrival, so a clarification for an unselected
  session stayed silently hidden. Reset on every arrival.
- input bar (in-session + launcher) only truncated lines, never padded to
  width, so the terminal backdrop bled through the right. Pad with padTo.
- action/narration/user rows prefixed plain (unstyled) spacers around their
  icons, leaving transparent gaps near the symbols. Use bg-styled spacers.
2026-07-03 01:00:55 +04:00
kami 5e98f6661e feat(tui): render structured artifacts as a summary, not raw JSON
Approval-card and preview JSON artifacts (the freestyle execution_plan, the analyst's analysis) rendered as a raw JSON dump. Add a JSON-aware preview: a scannable goal + numbered stage list for execution_plan, indented key/value lines for other objects, with light highlighting. Non-JSON previews fall back to the raw line view unchanged.
2026-07-02 13:26:42 +04:00
kami 5deb2f6425 fix(tui): fill the frame background so the terminal backdrop can't bleed through
The main render path never applied the Screen backdrop, so any sub-view that left a line short or a row empty showed the transparent terminal background. Pad every frame to full width×height with the theme backdrop before display; content stays top-left anchored so caret coords are unaffected.
2026-07-02 13:26:42 +04:00
kami d26f20c316 docs(dox): build out the DOX AGENTS.md hierarchy
Root Child DOX Index assembled, plus a per-module AGENTS.md across the tree
(core/*, infrastructure/*, apps/*, testing/*, and docs/examples/frontend/etc),
each following the DOX section shape: Purpose, Ownership, Local Contracts,
Work Guidance, Verification, Child DOX Index.
2026-06-28 20:43:27 +04:00
kami ccfa4b4740 feat(tui): composer soft-wrap, alt+backspace, bracketed paste, borderless input
- editorLines now soft-wraps long logical lines at the box width (caret tracked
  across wrapped rows) instead of overflowing/truncating
- alt+backspace deletes the word before the cursor (deleteWordBack)
- handle tea.PasteMsg so bracketed paste (Ctrl+V / Ctrl+Shift+V / right-click)
  drops clipboard text into the focused composer; was silently dropped
- render the composer (in-session + launcher) borderless: top rule + flush text,
  no side borders or trailing padding, so a terminal drag-copy yields just the
  typed text instead of box chrome and whitespace
2026-06-28 20:42:50 +04:00
kami 8abe7d9eb7 fix(tui): collapse multiline tool summaries so action rows don't stripe
file_read on a directory/file returns newline-bearing summaries; clip kept the
newlines, so the action row rendered multi-line with a background-filled stripe
and the listing leaking underneath. Flatten the summary to one line first.
2026-06-28 20:42:38 +04:00
kami 3e94d7fbff merge: integrate feat/backlog-burndown into master
master had advanced 16 commits past feat/backlog-burndown's base, and the two
branches independently built four of the same features. Resolved 26 conflicts.

Overlap features — kept master's implementation (more complete / production-wired /
more robust), dropped the feature branch's parallel constellation:
- llama-server health probe: kept master's event-store-backed tps probe; dropped the
  branch's LlamaLivenessClient (liveness-only, throughput unwired).
- event-store probe: kept master's EventStoreHealthProbe; dropped EventStoreLatencyProbe.
- brief echo-back gate: kept master's BriefEchoDiff (Jaccard, tolerates rewording);
  dropped the branch's exact-set-diff BriefEchoComparator/Extractor.
- static-first reviewer: kept master's command/exit-code gate (ProcessStaticAnalysisRunner,
  wired); dropped the branch's structured-finding static_check stage (no-op seam).
  Its structured-findings model is filed as a follow-up in BACKLOG.

Feature-branch net-new work brought in and kept (master had none):
- native task tracking (aggregate, agent tools wired into analyst/implementer/reviewer,
  dependency graph + gates, decompose, REST/CLI, TUI task board)
- critique-outcome producer (role-rel §6 — master had deferred it)
- stage-level plan checkpointing (C-A2, folded into runPostStageGates)
- CLAUDE.md/AGENTS.md L0 standing context
- cross-session grants + TUI (grant scopes/revoke, @ picker, session resume browser)

Verified: full Gradle compile (all modules + tests) green; tests pass for core:events,
core:kernel, infrastructure:workflow, apps:server, apps:cli, testing:integration; tui-go
go build + go test green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 11:30:41 +00:00
kami af905a6dad feat(tui): task-board preview frames (tasks / task-detail)
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>
2026-06-26 10:27:20 +00:00
kami 6f2ee1c654 feat(tui): show dependency readiness on the task board
Each task row gets a 2-col glyph — ● ready (workable now) / ○ waiting (blocked) /
blank — and the detail pane shows "ready to work" or "blocked — waiting on <ids>",
fed by the new GET /tasks ready/blockedBy fields. Makes a decomposed graph legible
at a glance; a dependency-blocked task is status TODO (not the red BLOCKED lifecycle
state), so the glyph is what distinguishes them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 11:54:24 +00:00
kami 99f781687f feat(tasks): task search across REST, tool, and TUI
Ranked exact/substring search: TaskSearch (all terms AND, ranked title >
key > goal > criteria > notes) over one project or the whole board via
TaskService.search. Surfaced as GET /tasks?q=, a read-only task_search tool
for agents, and a `/` filter in the TUI board.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 22:08:11 +00:00
kami 1d7fab4ee4 feat(tasks): TUI task board
Add a cross-project task board to the TUI (T / palette): a roster fetched
from GET /tasks with vim-style nav, refresh, and an enter-to-open detail
pane (goal, criteria, paths, links, notes) built from the list payload — no
extra fetch. Server: TaskService.listAll() and a project-optional GET /tasks
(scoped with ?project=, whole board without; recent-first).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 20:32:48 +00:00
kami 85af2c67b7 tui: real terminal cursor in composer + window title (v2)
Two v2-unlocked polish items now that the TUI is on bubbletea v2:

Native cursor — the composer caret was a frame-animated "▏" that forced the
redraw loop to keep ticking in insert mode. v2's View.Cursor lets the terminal
draw and blink a real cursor, so:
- editorLines/launcherInput drop a width-1 marker rune (U+E123, same cell width
  as "▏") at the caret; View() locates it in the fully-composited frame via
  splitCursor → (X,Y), swaps it for a space, and sets a blinking CursorBar there.
- render() (preview/golden tests) resolves the marker to the static "▏" glyph, so
  screenshots are unchanged and the marker never leaks into output.
- nil cursor in normal mode hides it (vim-correct); gated on overlay==None so an
  open palette/files modal (which keeps editMode==Insert) doesn't draw a cursor
  behind it.
- animating() no longer forces a redraw in insert mode — the terminal blinks the
  cursor itself, so an idle insert screen holds still (native text selection
  survives) and burns no frames.

Window title — View.WindowTitle names the terminal tab after the active session,
falling back to the model name then "correx".

Tests: new cursor_test.go (coord extraction, mode/overlay gating, no marker leak);
updated the animating-gate test for the new insert-mode contract. Build, vet,
gofmt, full suite green; compose preview shows the static caret with no marker leak.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 16:28:22 +00:00
kami d247b19608 tui: migrate Bubble Tea v1 → v2 (charm.land), enable Shift+Enter
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>
2026-06-22 15:59:55 +00:00
kami 6c792b83e2 feat(tui-go): in-session right panel cycles events / changes / off (d)
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>
2026-06-22 14:47:23 +00:00
kami cde0c33031 feat(tui-go): multi-line input — Ctrl+J / Alt+Enter newline, growing box (Phase 2)
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>
2026-06-22 12:03:06 +00:00
kami f2be46a743 refactor(tui-go): move session dates to the R overlay; drop dead idle panels
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>
2026-06-22 11:57:41 +00:00
kami 9d00612742 feat(tui-go): idle launcher — centered input + status/keys rail (Phase 1)
Replace the idle two-panel layout (session list + welcome, where the welcome
panel duplicated the list) with an opencode-style launcher: a compact, centered
input whose lower-right shows the launch target and model (chat by default), a
hideable right rail of status + quick keys, and no session list — it's a keypress
away via R.

- Tab (or w) cycles the launch target: chat → workflows → chat (launcherWf),
  shown at the input. Enter on chat starts a chat; on a workflow, StartSession
  with the typed text as the brief.
- Right rail (connection, session/active counts, i/Tab/R/?/p keys) hides via the
  new "rail" palette command. Drops automatically on narrow terminals.
- View() suppresses the bottom input bar on idle (the input is the launcher);
  footer + ? help updated to the launcher keys (help-coverage test extended).

The old idle-panel renderers (sessionRows/welcomeRows/workflowRows) are now
unused but kept in the tree pending Phase 2 + a decision on where the session
dates should live now that the idle list is gone. Multi-line input (Ctrl+J /
Alt+Enter newline) is Phase 2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 11:33:19 +00:00
kami 81280c5bd5 feat(tui-go): welcome "recent" list uses the local date, matching the session list
Was the UTC-then-local time-of-day; now the same "Jan 02 15:04" / "Jan 02 2006"
shortDateTime as the session list, so a previous-day session isn't shown as if
it were today.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 10:28:28 +00:00
kami 6bee3a7d31 fix(tui-go): render event/activity times in local time, not UTC
formatTime forced .UTC(), so every event-log timestamp and the welcome "recent"
times read hours off the operator's wall clock — and inconsistent with the local
date just added to the session list. time.UnixMilli is already local, so the
explicit .UTC() was the bug; drop it. No test asserts a clock string (the render
matrix only checks event type/detail), so this is display-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 10:14:51 +00:00
kami 4e4d7c186a fix(tui-go): make the write/edit output-panel number meaningful
The collapsed tool row showed "tool output (N chars)" where N was len() of the
raw unified diff — i.e. the diff blob's *byte* length (headers, @@, +/-/context
all counted), labelled "chars". That number corresponded to nothing the operator
cares about, and len() is bytes not characters.

- Summarise a write/edit by the diff's row count instead — "· diff (7 rows) —
  ^x to view" — matching exactly what the ^x modal reports. Non-diff output (the
  defensive fallback) now counts runes, not bytes, so "N chars" is truthful.
  Retire itoaLen (the byte-count-as-"len" footgun).
- Harden the (+a −b) line counts in diffSummary: guard the file header with the
  trailing space ("+++ "/"--- "), so a changed line whose content starts with
  "++"/"--" is counted instead of mistaken for a header. Apply the same guard in
  parseUnifiedDiff so such a line is rendered (and counted) rather than dropped.

tool_summary_test.go covers the diff-row summary, the rune (not byte) count, and
the ++/-- content-line edge case.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 10:09:56 +00:00
kami 035f7df704 feat(tui-go): make the ? overlay list every keybind
Audited handleNormalKey / handleApprovalKey / the OverlayDiff handler against the
help cheat-sheet and found gaps: enter, ↑↓/jk, /, s, c, a, w, q and ? itself were
bound but undocumented. Reorganise helpBody into navigate / session / overlays /
approval band / diff-viewer groups covering all of them. help_complete_test.go
asserts a row exists for every binding so the help can't silently drift from the
handlers. The longer sheet scrolls fine via the modal-scroll support.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 10:04:41 +00:00
kami 454f24fc3a feat(tui-go): show last-activity date in the starting-view session list
The idle "sessions" panel listed id/status/name/stage but no time at all. Add a
right-aligned last-activity timestamp (Session.LastEventAt) per row — "Jan 02
15:04" within the current year, "Jan 02 2006" for older — so old sessions are
distinguishable at a glance. The name is truncated first so the box's width
clamp never eats the date column. (The R resume overlay already shows a relative
age; this is the live list on the starting screen.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 10:04:41 +00:00
kami 85121cec9d fix(tui-go): event-stream badge tracks session status, not just the socket
The EVENTS panel showed "● live" off m.connected alone, so a completed/failed/
paused session kept reading "live" as long as the websocket was up — stale, since
a terminal session emits no more events. Derive the badge from the selected
session's Status (the same vocabulary the status bar uses): ✓ done, ✗ failed,
‖ paused, ● live while active, ○ idle when disconnected. event_badge_test.go
covers all five.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 09:52:37 +00:00
kami 7114bb7658 feat(tui-go): scroll tall modals instead of clipping them off-screen
The ? help and S stats modals rendered at fixed height; compositeOverlay
drops any rows past the screen, so on a 24-30 row terminal their bottom
sections — including the close hint — vanished with no way to reach them
(and the backdrop border corrupted). Recent help additions worsened it.

- Add a shared scrollable-modal renderer: helpBody()/statsBody() produce
  discrete lines, renderScrollModal() windows them by m.modalScroll (clamped
  to the viewport via modalBodyRows/scrollableModalMax) and pins the hint, so
  the box never exceeds the screen. A "(off-end/total)" indicator shows in the
  title when scrolled. Keys: ↑↓/jk line, PgUp/PgDn page, ^u/^d half, g/G ends;
  any other key still dismisses help. modal_scroll_test.go (2): clamp + no-op.
- Page the artifacts viewer (v) too: PgUp/PgDn were one line at a time — now a
  full body, with ^u/^d half and g/G ends via scrollArtifact().

Verified via cmd/preview: help/stats close hints now survive at h=20-28 (were
clipped below ~32/40) and show the scroll indicator; full at tall heights.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 09:26:21 +00:00
kami db27b3436b feat(tui-go): page-scroll the diff/preview/command fullscreen viewer
The ^x fullscreen viewer (OverlayDiff) backs every tool preview — split diff
for write/edit, the command card for shell, plain lines otherwise, plus any
transcript tool-output — but only scrolled one line per keypress, making a
long diff tedious to read.

- Add paging to the viewer, matching the OUTPUT free-scroll contract: ↑↓/jk
  by line, PgUp/PgDn by a full body, ^u/^d by half, g/G to the ends. New
  scrollDiff() clamps to [0, diffMaxScroll] so paging past either edge lands
  exactly (no dead presses). diff_scroll_test.go (2): clamp + no-op-when-fits.
- Update the modal hint to advertise paging (line / page / ends / close) and
  add a "diff / preview viewer (^x)" section to the ? help overlay. Also note
  the T3+ confirm-again behaviour in the approval-band help.

Approval ergonomics re-audit (BACKLOG §E): the spec'd band still holds —
single-key y/n/e for T0-T2, mandatory second-key confirm for T3+ (HighTier),
navigable queue with an i/n indicator, docked band never modal-stacked; all
covered green by approval_test.go. The scroll granularity above was the only
gap, now closed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 09:09:54 +00:00
kami 08b5c63a0f docs: reconcile BACKLOG against shipped work + gitignore tui preview binary
Backlog hygiene per the file's own maintenance rule — resolved items get
moved out, not left ticked-in-place.

- Cut resolved body bullets from BACKLOG §A/B/C/D/E/G/I (health probes,
  static-first infra, CritiqueFinding, architect contradiction-check, A1/A2
  gates, batch fetch-approval, source/egress events, render matrix, markdown,
  session list, idea promotion, turnId/probe/narration, .cs symbols, scene
  validator). All already archived in RETRO's burndown table.
- Narrow partially-done items to their live follow-up rather than delete:
  B5 -> static_check stage emitter only; B6/C-A3 -> CritiqueOutcomeCorrelated
  producer only; C-A1 -> planner-prompt activation only.
- Drop the stale "§E UNBLOCKED" index line (plan-comparison is BLOCKED, no
  PlanCandidate event; idea-promotion shipped f107ff5).
- Archive the operator-requested TUI wave (palette, @ picker, CLAUDE.md L0
  injection, inline action rows, free-scroll/help/filter) in RETRO 2026-06-22
  — never BACKLOG items, would otherwise be orphaned in neither file.
- gitignore apps/tui-go/preview (cmd/preview build artifact).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 08:57:12 +00:00
kami 531910d38a feat(tui-go): transcript free-scroll + help overlay + event filter
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>
2026-06-22 05:41:38 +00:00
kami f14a83c026 feat(tui-go): drop inline tool-start rows (keep results only)
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>
2026-06-21 23:24:10 +00:00
kami 6a06f2ead5 feat(tui-go): inline action rows in the OUTPUT transcript
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>
2026-06-21 23:00:18 +00:00
kami 8df0ec750c feat(tui-go): grant scope picker (A) + standing-grants viewer
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>
2026-06-21 22:35:01 +00:00
kami 54f94a549f feat(tui-go): command-palette keybinds + configurable status bar
Palette: each command now shows its bare-key shortcut in a key column (and the filter
matches on it), so the palette teaches the shortcuts instead of hiding them.

Status bar: a new "status bar" palette command opens a toggle overlay to show/hide the
non-essential segments (current stage, session status, workspace path, model, last-event
clock, resource gauge); correx/connection/session-name/spinner stay. Choices persist
TUI-local in tui-prefs.json (JSON in the config dir — display state, kept out of the
shared/replayed server config) and reload on launch.

Also trims the in-session footer (drops stats/model — both reachable via `p cmds`) so it
no longer overflows + truncates `q quit` at ~100 cols after the transcript-nav additions.
Tests cover toggle→render gating and prefs persistence; verified via cmd/preview renders.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 10:50:28 +00:00
kami 04d7e26482 feat(tui-go): @ file-reference picker
Typing `@` at a word boundary in the chat input (mid-word `@` stays literal, e.g. emails)
opens a workspace file picker: it requests file.list for the session (cached per session),
filters as you type, and enter inserts `@path ` at the cursor — back to typing. Renders as
a transparent overlay, windowed around the selection. file.list is classified non-rendering
in the render-matrix guard. Tests cover token-boundary detection, ref insertion, and filtering.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 10:24:57 +00:00
kami ba7ac06c16 feat(tui-go): transcript message-jump + OSC52 copy
ctrl+↑/↓ jumps between your sent (user) messages in the transcript: ctrl+↑ from the
bottom selects the most recent, steps to older ones (clamping); ctrl+↓ steps back and
drops to tail-follow past the last. The selected message is highlighted and scrolled
into view (routerRows tracks per-message row offsets and windows to the selection).

`y` yanks the selected message — or the latest turn when none is selected — to the
system clipboard over OSC52 (go-osc52, tmux-wrapped under $TMUX), which is SSH/Termius-
safe unlike a mouse drag. A brief "✓ copied" footer note animates out (and self-stops
the tick). esc drops the selection; switching sessions clears it. Tests cover the
user-message jump/clamp/tail and the copy-text selection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 09:59:29 +00:00
kami 26621567ac feat(tui-go): transparent modal backdrops
Modals now composite over a dimmed copy of the screen behind them instead of an opaque
scrim, so the transcript stays faintly visible around a modal (opencode-style). center()
stashes the frame's base (View sets m.lastBase), strips+dims it to a faint scrim, and
splices the modal box over it (ANSI-aware via ansi.Truncate/TruncateLeft so the side
margins keep the dimmed content). An idempotence guard passes an already-full-screen
modal through unchanged, so the existing double-center builders don't get re-dimmed.
Geometry tests assert the composite still fills the screen exactly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 09:40:52 +00:00
kami 85bfddf1c4 feat(tui-go): slash command menu in the chat input
Typing `/` as the first char of a chat line opens the command palette inline
(opencode-style), instead of typing a literal slash; mid-line `/` stays literal.
Factors openPalette() (shared with the `p` key) and advertises `/ cmds` in the
insert-mode footer when the line is empty.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 09:35:34 +00:00
kami 9143d554f1 feat(tui-go): global input history + stop idle redraws (copy-safe)
Input history is now a single global ring (recordInput) shared by every input surface —
free-chat, the workflow-intent line, and in-session chat — so ↑/↓ recalls anything you've
sent, including outside a session (previously per-session, so idle/intent had none).

Copy fix: the 120ms animation tick now only runs while something actually animates
(animating(): active session spinner, blinking caret, reconnect). Init no longer starts it
unconditionally; tickKick (re)starts it on demand and tickMsg stops it when idle. An idle
screen no longer auto-redraws, so a native terminal text selection survives instead of being
wiped every frame. Tests cover the ring (dedup/cap/cross-context walk) and the idle gate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 09:33:16 +00:00
kami d5afcd345a feat(tui-go): approval ergonomics — queue + tier-gated confirm (E §3)
- 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>
2026-06-20 16:40:49 +00:00
kami 2a99e15383 test(tui-go): per-event render-matrix golden tests (E §2)
Table-driven render assertions (37 cases over 42 protocol Type* constants), driven
through applyServer and asserted ANSI-stripped on stable defining text. Coverage
guard parses every Type* from protocol.go and fails if a kind is neither covered
nor explicitly classified non-rendering. Pins width/theme/timestamp; no behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 16:30:25 +00:00
kami 1f7a5d96a7 feat(tui-go): session list / resume view (E)
R opens a navigable overlay of recent sessions (GET /sessions: short id, status,
workflow/stage, relative age); enter resumes via POST /sessions/{id}/resume, which
replays onto the existing global /stream (no WS re-dial). Fetch/resume errors surface
in the overlay, never panic. stdlib-only (ws.Client.HTTPBase derives the base URL).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 16:22:27 +00:00
kami da72ee5b80 feat(tui-go): render markdown in chat turns (E)
renderMarkdown wraps charmbracelet/glamour (dark style, per-width memoized) and is
hooked into the router chat-turn render path; falls back to plain content on any
glamour error so the TUI never crashes or loses text. Other roles/UI untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 16:12:16 +00:00
kami 1e6699a360 fix(tui-go): go.mod directive 1.26 -> 1.24.2 (1.26 toolchain unreleased)
`go 1.26` is not a fetchable/released toolchain, so released Go cannot build the
module ("toolchain not available"). 1.24.2 is the minimum the deps require
(charmbracelet/x/ansi needs >= 1.24.2) and is a real toolchain; the directive is a
floor, so newer Go still works. Bump if a specific newer release is intended.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 16:07:36 +00:00
kami fd67a6c68d feat(tui-go): health-checks pane
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.
2026-06-14 18:57:51 +04:00
kami 02e963d6cc feat(tui-go): idea board viewer
OverlayIdeas (opened with I, global/cross-session) lists the idea board in the
artifact-viewer shell: ↑↓ to move, x/d to remove (sends DiscardIdea, optimistic
drop), esc to close. Pull-based via ListIdeas -> idea.list.
2026-06-14 14:36:13 +04:00
kami b563d9841d feat(tui-go): workflow-propose choice panel
Render a workflow.proposed frame as a single-select picker with a manual-answer
slot: ↑↓/jk to choose, enter to launch, e for custom, esc to peek away. Picking
a candidate sends StartSession (and focuses the launched session); the custom
slot continues the conversation via ChatInput. Reuses the modal helpers from the
clarification view.
2026-06-14 13:29:34 +04:00
kami 218a98034e feat(tui-go): interactive clarification question view
Render stage clarification questions as an AskUserQuestion-style form:
header chip, prompt, selectable option chips, and a free-text custom slot.
Arrow/hjkl nav, space to select, e for custom, enter to submit. Submit
guards on all-answered and sends ClarificationResponse; the parked stage
re-runs with the answers in context.
2026-06-14 13:00:59 +04:00
kami 65df3f4fad feat(tui,kernel): deterministic command-approval parse layer (§5.1)
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.
2026-06-13 15:18:27 +04:00
kami b4ca17c85f fix(tui): responsive layout for slim terminals
The TUI overflowed and became unreadable on narrow terminals: the status bar and
footer spilled far past the edge (justify never truncated), the two-column main
split shrank the events panel to ~20 cols, and the stats overlay wrapped mid-word.

- justify is now width-safe: shrinks the left segment first (the right holds the
  high-signal connection clock / active spinner), clamps the right only as a last
  resort, ANSI-aware so it never slices escape codes. No line can exceed the width.
- Main area collapses below narrowWidth (96): single full-width session list when
  idle; output stacked over the live event strip in-session (the strip is too
  valuable to drop — the constraint is width, not height).
- Compact chrome when slim: status drops workspace/gauge/provider-suffix and uses a
  short clock ("◷ 3s") + bare spinner; footer uses a reduced hint set and drops the
  "Soft·blue" badge; approval action row tightens its gaps so decision keys stay
  visible.
- Overlays get near-full-width on slim terminals; stats pane uses compact formats +
  per-line clipping so it never wraps.

Tests: no rendered line exceeds the terminal width at 40–160 cols (incl. the stats
overlay); narrow in-session main stacks output+events.
2026-06-13 11:01:16 +04:00
kami 8b896b519a feat(tui): truthful-state §2 — last-event clock + unknown-event raw fallback
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.
2026-06-13 10:46:06 +04:00
kami 4f6bfa85f7 feat(server,tui): session stats pane — metrics over the WS bus + Bubble Tea overlay
Surfaces the observability metrics (observability-spec §3-tier-2) in the Go TUI:
a session-summary pane showing duration, token throughput per provider, tool
time, approval latency per tier, failure counts, and the session wall-time
accounting split. Press `S` (or palette → "session stats").

Mirrors the artifacts/config fetch pattern exactly — a WS request/response, not a
new transport. Server-side reuses the already-tested MetricsInspectionService
(one MetricsReport definition, two wires: REST `correx stats` + WS).

Server:
- ClientMessage.GetSessionStats + ServerMessage.SessionStats(@SerialName
  session.stats), reusing MetricsReport as the nested payload.
- StreamQueries.sessionStats replays via MetricsInspectionService (pure read,
  replay-neutral); routed in GlobalStreamHandler.
- ServerMessageSerializationTest golden pins the session.stats wire format.

TUI (Go/Bubble Tea):
- protocol: TypeSessionStats + StatsDto/nested structs + GetSessionStats encoder
  + golden decode test (cross-language contract).
- OverlayStats pane (statsModal), `S` key + palette entry + footer hint,
  loading/cache-by-session state, bounded breakdown rows.
- demo `stats` preview case for serverless visual verification.
2026-06-13 10:39:52 +04:00