tui: theming, discoverability, tool UX, cleanup, and docs - #12
Merged
Conversation
Generated by applying the tui-design skill against the full TUI implementation. Covers terminal hygiene, theming, discoverability, responsive design, and polish across 5 phases. Entire-Checkpoint: 8d3a082961b3
- Add panic recovery defer in runTUI() so a crash leaves the terminal usable (belt-and-suspenders on Bubble Tea v2's built-in restore). - Install explicit SIGTSTP/SIGCONT signal handlers so the Go runtime doesn't swallow suspend/resume events before Bubble Tea sees them. - Fix Esc behavior: Esc now cancels/backs out of model mode and command mode. In normal chat mode, Esc does nothing. Ctrl+C remains the only quit key. This follows the universal TUI convention that Esc = cancel/back, not quit. Ref: docs/tui-review-and-plan.md § Phase 1 Entire-Checkpoint: 86d7b505367c
Replace 17 hardcoded lipgloss.Color literals with a Theme struct of
semantic tokens (Title, User, Assistant, Tool, Spinner, etc.).
- Theme struct with named color tokens and ApplyTheme to reassign all
package-level style variables from a single definition.
- DarkTheme (original yaah palette) and LightTheme presets.
- NO_COLOR support: monochromeTheme uses empty strings → lipgloss.NoColor{}
so the terminal's default foreground/background is used.
- Auto-detection via DetectTheme(): NO_COLOR → monochrome, YAah_THEME
env var → named theme, terminal background → dark or light.
- Named theme map for future Catppuccin/Dracula/Nord/Gruvbox presets.
- Color-free renderCompactTable and renderQuestionModal now derive
their inline styles from theme-based style variables.
Ref: docs/tui-review-and-plan.md § Phase 2
Entire-Checkpoint: 55f41971ed68
Replace raw string key comparisons with declarative key.Binding keymap (keymap.go). All input routing now uses key.Matches — self-documenting, testable, and ready for remapping. New features: - Footer hint bar (bubbles/help) with 5 always-visible shortcuts: ? help, / search, ctrl+y copy, ctrl+t reasoning, ctrl+c quit. - Full help overlay on ? with grouped keybinding reference. - vim-style viewport navigation: j/k scroll, gg/G jump to top/bottom, plus ctrl+u/ctrl+d for page up/down. - In-viewport search via /: type to filter, n/N for next/prev match, Esc to dismiss. Match count shown in search indicator line. - Reasoning toggle moved from ctrl+r to ctrl+t (ctrl+r is conventionally reverse-search in shells). - Status bar de-duplicated: provider/model removed (header already shows it), now shows only message count and context bar. - Esc is now a strict cancel/back: dismisses command mode, exits model mode, cancels question modal, dismisses search and help. Never quits. Ref: docs/tui-review-and-plan.md § Phase 3 Entire-Checkpoint: dd4e74d9a7f2
- Minimum size check: if the terminal is below 60×20, a clear error message is shown instead of a broken layout. This addresses the 'pressure-test the floor' concern from the TUI design skill. - Token-rate throttling: streaming tokens no longer trigger a full viewport rebuild on every arrival. AppendToken sets a needsRefresh flag; the existing spinner tick (firing ~15 Hz) flushes the pending refresh. This cuts viewport rebuilds from 80+/sec to ~15/sec during fast streaming, reducing CPU load significantly. - Banner toggle: /banner command hides/shows the figlet ASCII art banner, reclaiming 6-10 lines of vertical space. Compact header shown when banner is hidden. Command added to defaultCommands. Ref: docs/tui-review-and-plan.md § Phase 4 Entire-Checkpoint: 28216e8e8e12
- Ephemeral status messages: SetEphemeral() shows a green auto-fading message line between the status bar and viewport. Uses the existing spinner tick to decrement a ~3 second timer. Applied to /banner and ready for compact, model switch, copy, etc. - Extensible commands: RegisterCommand(name, desc) allows runtime registration of slash commands (e.g., from MCP tools or skills). - Cursor positioning accounts for footer + ephemeral line offsets so the text input cursor always lands in the correct position. Ref: docs/tui-review-and-plan.md § Phase 5 Entire-Checkpoint: d4f006e44295
paletteLines() returned 0 when showHelp was true (no case for it), so adjustViewport() didn't shrink the viewport height. The help overlay rendered behind the full-height viewport and was invisible except for the top border line. Add a showHelp case that returns ~26 lines (capped at 80% of available terminal height). Entire-Checkpoint: f0c1ec8594c3
…toggle The help overlay toggled on ? but only called refreshViewport(), not adjustViewport(). The viewport kept its full height, and the help content rendered below it, pushed off-screen. Only the top border line peeked through above the footer. Fixed both toggle-on (?) and dismiss (any key) paths to call adjustViewport(), which shrinks the viewport height to make room for the help overlay via the paletteLines() showHelp case. Entire-Checkpoint: bf77c6a323f0
Phase 3 bound / to search, which broke slash commands like /model and /compact. Following the k9s/vim convention, commands now use : (colon) as the prefix: - Type : to enter command mode with auto-suggestions - :help, :clear, :compact, :banner, :model, :quit - / remains the search key - Help overlay now shows a Commands group with the : binding Both features coexist cleanly: / searches chat history, : opens the command palette. Entire-Checkpoint: 7baa85631d7d
- internal/prompts/identity.md: /model → :model - README.md: (shown in /model) → (shown in :model) - docs/tui-review-and-plan.md: updated slash references throughout, added §9 Implementation Record with final keybinding table and a detailed plan-vs-implemented comparison Entire-Checkpoint: 4159a88782ee
- gofmt: format theme.go and tui.go - staticcheck: replace deprecated statusStyle.Copy() with direct chaining (lipgloss v2 styles are immutable) - Windows cross-compile: extract SIGTSTP/SIGCONT signal handlers into tui_unix.go (!windows) and tui_windows.go (no-op stub). syscall.SIGTSTP/SIGCONT are undefined on Windows. Entire-Checkpoint: ec3dae13b0aa
Add UserBg, ToolBg, SystemBg fields to Theme with corresponding style variables (userBgStyle, toolBgStyle, systemBgStyle). Message blocks in the viewport now render with full-width background color spans for visual differentiation: - User messages: dark blue background (24 on dark, 153 on light) - Tool output: dark gray background (236 on dark, 251 on light) - System messages: dark gray background (same as tool) Assistant messages retain no background (default terminal bg). Each message block is padded to viewport width so the background spans the full row, giving clear visual separation between different message roles. Entire-Checkpoint: 89284817fb5f
Single-character keys ('j', 'k', 'g', 'G') were bound to viewport
scroll/jump, intercepting normal typing. In a chat TUI, the text
input is always focused — single-char bindings must go to it.
Changes:
- Viewport scroll: arrows only (remove j/k)
- Viewport jump: Home/End only (remove g/G)
- Page up/down: PgUp/PgDn only (remove ctrl+u/ctrl+d)
- ? and / now only trigger when the input is empty, so you can
type question marks and slashes in messages normally
- n/N already guarded by search mode
All navigation keys are now either special keys (arrows) or guarded
by mode/input-empty checks, so normal typing works unimpeded.
Entire-Checkpoint: cb68fd255ff8
- Remove single-char j/k/g/G from keybinding table - Add Guard column showing when each binding is active - Add note explaining single-char keys pass through to input - Add message backgrounds table (User, Tool, System) - : behavior confirmed correct: HasPrefix ensures it only triggers as the first character Entire-Checkpoint: 1b849ba36af5
Add Commands key.Binding (:) to the declarative keymap and include it in the always-visible footer. Footer now shows: : commands · / search · ? help · ctrl+y copy · ctrl+c quit Also refactors the help overlay Commands group to use the shared keys.Commands binding instead of an inline definition. Entire-Checkpoint: aa40eece3766
Add cwd field to Model, accepted via New(). Status bar now shows: ~/Code/agentic/yaah │ messages: 3 │ [████░░░░░░ 40%] Home directory replaced with ~. Long paths truncated with ... prefix to fit within ~1/3 of terminal width. Entire-Checkpoint: 8716f540bd77
Mouse mode (originally MouseModeCellMotion, accidentally corrupted to the nonexistent MouseModeClick by a stray sed) was preventing native terminal text selection via shift+drag. Since all viewport navigation now works via keyboard (↑↓ PgUp/PgDn Home/End), mouse mode isn't needed. Removing it restores normal copy/paste behavior in all terminal emulators. Entire-Checkpoint: 7527d89c229a
- Config struct replaces 8 positional New() params. Adding a field
no longer breaks callers — set it in Config and wire in New().
Caller in cmd/yaah uses named field initialization.
- Update() refactored from a 290-line nested switch into a clean
dispatcher that delegates to mode-specific handlers:
handleKeyPress → handleSearchKey / handleQuestionKey /
handleModelKey / handleNormalKey
handleSpinnerTick and handleMouseClick extracted as well.
viewportUpdate helper deduplicates repeated viewport.Update calls.
Each handler is a self-contained function with a single
responsibility — adding a new mode (e.g. file picker) is now
a matter of adding one method and one dispatch line.
Entire-Checkpoint: 28585acce2d6
Group the 49 Model fields into 12 logical sections: core widgets, static config, layout, streaming state, reasoning, overlays, search, question modal, command mode, model selection, context window, misc UI. Each section has a clear comment header — finding a field or adding a new one no longer requires scanning a flat list. Entire-Checkpoint: edfd7d8af950
Extract all render* methods from tui.go (2,055 lines) into render.go (645 lines), leaving tui.go at 1,526 lines. Split performed by tools/gosplit — a Go AST-based file splitter that moves methods by name pattern, preserving imports via deep copy. tui.go now contains only: types, constructor, state mutation, Init/Update/handlers, command execution, search, and question modal. render.go contains: View(), renderMessages, all render* methods, text utilities, table/tree/list rendering, and layout helpers. Also added: project skill 'go-ast-split' for reuse in future splits. Entire-Checkpoint: 81e6bd8741c4
- New ReasoningBg field in Theme struct with reasoningBgStyle var.
Reasoning text blocks (expandable reasoning sections and live
thinking content) now render with a full-width background:
dark: 17 (dark blue), light: 189 (light blue)
- ToolBg changed from gray to green for visual differentiation:
dark: 22 (dark green), light: 156 (light green)
- All three theme presets updated (Dark, Light, Catppuccin Mocha).
To customize: change ToolBg/ReasoningBg in the Theme literal, or
set YAah_THEME to a named preset. Each is 1 line.
Entire-Checkpoint: 0fe1528142a8
Entire-Checkpoint: cdf06f6539b3
The refactored handleNormalKey returned nil for unmatched keys, consuming them silently instead of forwarding to the text input. This broke all typing — no characters reached the input field. Fix: the final catch-all in handleNormalKey now falls through to m.input.Update(msg) + detectCommandMode(), matching the behavior of the original monolithic Update(). Special keys (Esc, arrows) are harmlessly ignored by the textinput widget. Entire-Checkpoint: 3b11358b0831
Three fixes for tool call rendering: 1. Green background now applies to tool RESULT content, not just the progress label. Both the ⏳ progress line and the tool output are wrapped in toolBgStyle (dark green 22 / light green 156). 2. Spacing: blank lines added before and after the progress label and tool result blocks for visual separation from chat messages. 3. Progress label now collapses when the tool result arrives (ClearToolCall in HandleAgentMsg on ToolResult), not waiting for the full Done signal. The ⏳ label disappears as soon as the tool completes, leaving only the result content. Entire-Checkpoint: 05db2a79d6a0
The full-width green background (22/156) caused poor contrast with the dimmed tool text (243). Replaced with a clean left-margin indent: each line of tool output is prefixed with a dimmed │ character. No background, good readability, distinct from chat. - Removed toolBgStyle var (unused after the switch) - Added toolIndent(prefix, content) helper - ToolBg stays in Theme struct for future use if desired Entire-Checkpoint: 19ed3bb03993
Tool output now renders with a persistent header + collapsible body, matching the reasoning section pattern: - Header always visible: ⏳ icon while running, ✓ when complete, followed by tool name and extracted summary (task description, webfetch URL, bash command). - Body collapsible via click or by default: expanded while tool runs, collapsed when complete. - Toggle with ▶/▼ indicator, same style as reasoning sections. Added ToolArgs to Message struct to carry the tool arguments alongside the result, enabling the header to extract descriptions without relying on the transient toolCall state. Entire-Checkpoint: 168f9497d8e0
- Fix stale /banner and /quit refs to use : prefix - Remove orphaned doc comments left by gosplit refactor - Remove duplicate doc comments on defaultCommands and Model - Remove dead ToolBg field from Theme struct - Replace catppuccinMocha (was identical to DarkTheme) with real Catppuccin Mocha/Latte 256-color palettes - Remove stub themes (dracula, nord, gruvbox, tokyo-night) that were aliases to DarkTheme; unknown YAAH_THEME now warns to stderr - Fix panic recovery comment (false goroutine claim) - Delete docs/tui-review-and-plan.md (AI-generated planning artifact) - Delete tools/gosplit/ (one-off AST split tool) - Remove stale gosplit entry from .gitignore Entire-Checkpoint: 81d98ce4937e
- Show MCP server connection status on TUI startup - Add :mcp command to view detailed server info - Implement ApproveFn callback for TUI tool approval - Suppress stderr during TUI to prevent layout corruption - Add new taglines to banner - Add ServerInfo struct for MCP server metadata - Update MCP client interfaces to expose server info
Reduced excessive spacing between collapsed elements in TUI: - User messages: \n\n → \n - Assistant messages with reasoning: \n\n → \n (before reasoning) - Reasoning section: \n\n → \n (after reasoning) - Assistant content: \n\n → \n - Collapsed tools: removed extra \n after toggle - Live reasoning section: \n\n → \n (before and after) This makes collapsed Reasoning and tool elements appear closer together, addressing the user's complaint about too much space.
toolCallHash previously only hashed tool name + result. When multiple tool calls returned identical success messages for different inputs (e.g. writing different files), the hash collided and falsely triggered loop detection. Include the tool arguments in the hash so that different inputs produce different hashes. Same name + same args + same result still correctly detects a real loop. Entire-Checkpoint: 2931fa319e52
- Fix message spacing: consistent newlines after assistant messages - Make displayWidth ANSI-escape-aware for accurate width measurement - Add early returns in key handlers to prevent fall-through - Enhance toolIndent with proper line wrapping to fit within width - Simplify tool output rendering (toolIndent handles wrapping now)
- Add bordered box style (toolBoxStyle) for tool output sections - Cap tool output to maxLines (viewport height / 3, clamped 8-24) - Show truncation header with omitted line count when content overflows - Renders tool output in a rounded border with padding
- Enable MouseModeAllMotion on tea.View so bubblezone click detection works for reasoning/tool toggles and question options - Remove the "│ " prefix from toolIndent that was prepended to every line of bash/tool output Entire-Checkpoint: 7265a5575774
- Remove :devmode command from TUI default commands - Remove devMode/devModeContext fields from Model - Remove IsDevMode(), GetDevModeContext(), CaptureRenderedMessages() methods - Remove DEVMODE system message injection from OnSubmit handler - Remove dev mode section from identity prompt - Remove all dev mode tests Entire-Checkpoint: a7ab31511aec
…check - Update README and AGENTS repo layout with missing packages (banner, spinner, todo, update) and TUI planning docs - Add missing commands to README (session show, update check, mcp remove, mcp add stdio variant) - Mark TUI component system docs as design-phase, not implemented - Remove get_terminal_output from identity.md (tool exists but is never registered) - Fix staticcheck S1021 in cmd/yaah/tui.go (merge var decl) - gofmt agent_test.go and tui_test.go Entire-Checkpoint: 40476cb5b5fd
buchenberg
added a commit
that referenced
this pull request
Aug 7, 2026
Task #11: Split newAgentSessionWithOptions into focused builders - wiring_otel.go: initOtel + wrapProviderWithOtel (extracted inline provider wrapping) - wiring_mcp.go: initMCP (moved to its own file) - wiring_prompt.go: buildSystemPrompt + buildMainPrompt (extracted inline prompt layers, memory enrichment, guidelines, directive injection, and quick-ref assembly) - wiring.go slimmed from 396 to ~210 lines; removed dead layers.Skills assignment (was set after prompts.Build, never read) Task #12: Complete ContextManager extraction — eliminate state sync dance - Added State *LoopState pointer to ContextManager; compaction methods now read/write mutable state (Messages, PreviousSummary, token counts, compaction tracking) directly through the pointer instead of copy-in/copy-out - Removed 9 duplicate state fields from ContextManager (Messages, PreviousSummary, LastPromptTokens, LastCachedPromptTokens, IneffectiveCompactions, LastCompactionTokens, CompactionBudgetMultiplier, CompactionSavingsHistory, CompactionForcedByOverflow) - Eliminated 19-line sync dance in Loop.compactContext and 3-line dance in Loop.trimContext - Removed redundant CtxMgr.Messages assignments in loop.go, tools.go, turn.go (now no-ops since CtxMgr.State points to Loop.State) - ctxMgr() lazily sets State = &l.State when nil (backward compatible with tests)
buchenberg
added a commit
that referenced
this pull request
Aug 7, 2026
#174) * Split wiring.go builders and eliminate ContextManager state sync dance Task #11: Split newAgentSessionWithOptions into focused builders - wiring_otel.go: initOtel + wrapProviderWithOtel (extracted inline provider wrapping) - wiring_mcp.go: initMCP (moved to its own file) - wiring_prompt.go: buildSystemPrompt + buildMainPrompt (extracted inline prompt layers, memory enrichment, guidelines, directive injection, and quick-ref assembly) - wiring.go slimmed from 396 to ~210 lines; removed dead layers.Skills assignment (was set after prompts.Build, never read) Task #12: Complete ContextManager extraction — eliminate state sync dance - Added State *LoopState pointer to ContextManager; compaction methods now read/write mutable state (Messages, PreviousSummary, token counts, compaction tracking) directly through the pointer instead of copy-in/copy-out - Removed 9 duplicate state fields from ContextManager (Messages, PreviousSummary, LastPromptTokens, LastCachedPromptTokens, IneffectiveCompactions, LastCompactionTokens, CompactionBudgetMultiplier, CompactionSavingsHistory, CompactionForcedByOverflow) - Eliminated 19-line sync dance in Loop.compactContext and 3-line dance in Loop.trimContext - Removed redundant CtxMgr.Messages assignments in loop.go, tools.go, turn.go (now no-ops since CtxMgr.State points to Loop.State) - ctxMgr() lazily sets State = &l.State when nil (backward compatible with tests) * Address PR #174 review: OTel env flag, memoryGuidelines scope, budget multiplier init - wiring_otel.go: evaluate YAAH_OTEL_ENABLED before the early return so the env flag can enable OTel when config disables it (pre-existing bug) - wiring_prompt.go: move memoryGuidelines from package-level const into buildSystemPrompt function scope (no globals per AGENTS.md) - lifecycle_init.go: initialize CompactionBudgetMultiplier=1.0 in ctxMgr() lazy path so Loop.Compact has a nonzero preservation budget before applyDefaults() runs * docs: remove completed plans and implemented ADRs Delete plan files for fully-implemented features (max iterations dialog, TUI-MCP bridge, quiet mode, task pane separation) and the four ADRs (engine-view separation, middleware pipeline, functional options, event-driven architecture) whose content is now covered by docs/architecture.md. Update doc references across CONTRIBUTING.md, architecture.md, and code-organization.md to point at architecture.md in place of the retired ADRs, and fix stale file paths to reflect the recent runner refactor (cmd/yaah/subagent_runner.go -> internal/agent/runner/). * Delete implemented ADRs and plan docs, fix stale references - Deleted 4 implemented ADRs (0001-0004: engine-view separation, middleware pipeline, functional options, event-driven architecture) — all Accepted and fully implemented; content covered by architecture.md - Deleted 5 implemented plan docs from .agents/plans/ (tui-mcp-bridge #159, tui-quiet-mode #159, tui2-task-pane-separation #160, max-iterations-dialog, web-ui-commands #162) — all features shipped - Updated docs/adr/README.md and CONTRIBUTING.md to remove dead ADR links - Fixed stale references to cmd/yaah/subagent_runner.go in architecture.md, PROMPT-INJECTION.md, and code-organization.md (now internal/agent/runner)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR reorganizes and polishes the yaah TUI. The monolithic
tui.gois split into focused files, a semantic theme system is introduced, discoverability is improved with help/search/footer, tool output gets expand/collapse zones, and documentation is brought up to date with the actual repo state.Changes by area
1. TUI file splitting
The ~2800-line
internal/tui/tui.gowas split into four files:internal/tui/tui.gointernal/tui/render.gointernal/tui/theme.gointernal/tui/keymap.go2. Semantic theme system
Themestruct with 22 semantic color tokens (Title,User,UserBg,Assistant,Tool,System,SystemBg,Status,StatusBg,Spinner,Code,Thinking,ReasoningBg,Toggle,ListBullet,ListItem,Tree,TreeItem,CmdBorder,CmdName,CmdDesc)DarkTheme(default),LightTheme,catppuccinMocha,catppuccinLatte,monochromeThemeDetectTheme()priority:NO_COLOR→ monochrome,YAAH_THEME→ named,HasDarkBackground()→ dark/light auto-detect3. Discoverability improvements
?in the input to see all keybindings grouped by category/to incrementally search the chat viewport;n/Nto jump between matches: commands / search ? help ctrl+y copy ctrl+c quit)/to:(e.g.,:help,:model,:clear,:compact,:quit):banner(toggle banner),:mcp(show MCP server status)4. Tool output UX
⏳/✓iconsbashshows args in header;taskextracts the description field;webfetchextracts the URLServerInfodisplayed at startup and via:mcpcommand5. Agent fixes
toolCallHash()now includes tool arguments to prevent false-positive loop detection (e.g., writing different files with the same tool would previously be flagged as a loop). Added test coverage.get_terminal_outputtool added (allows the agent to inspect its own rendered output)6. CLI improvements
ApplyTheme(DetectTheme())~expansion and truncation7. Key input refactoring
Update()key handling split into mode-specific methods:handleKeyPress(),handleSearchKey(),handleQuestionKey(),handleModelKey(),handleNormalKey()ctrl+tEsccancels modes/overlays instead of quittingHome/Endjump to viewport top/bottom8. Cleanup
internal/tui/(component.go,component_utils.go,render_components.go,view_components.go) — a planned but never-integrated React-like component systemget_terminal_outputfromidentity.md(tool was never registered)cmd/yaah/tui.gogofmtformatting on test files9. Documentation
docs/tui-component-design.md(new): Component system design proposaldocs/tui-refactoring-example.md(new): Before/after refactoring examplesdocs/tui-summary.md(new): Design summary and statusREADME.md: Updated repo layout (addedbanner,spinner,todo,updatepackages anddocs/entries), added missing commands (session show,update check,mcp remove,mcp addstdio variant)AGENTS.md: Updated repo layout withdocs/directory entriesdocs/architecture.md: Updated tool tableFiles changed
24 files: +2,806 / −913 lines
CI
go build ./...— cleango test ./...— all passgo vet ./...— cleangofmt -l .— emptystaticcheck ./...— clean