tui

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 29, 2026 License: MIT Imports: 34 Imported by: 0

Documentation

Overview

chrome.go renders the live activity band between the scrollback and the status line, and owns the redraw-ticker lifecycle that drives it. The band is always reserved one row (in App.layout) so its appearance and disappearance never reflow the viewport.

The Chrome module encapsulates: spinner phase + caption (thinking / writing / tool), the "new content below" hint, and the boolean tickActive flag that keeps the App from scheduling redundant coalescing ticks.

logo.go renders the startup wordmark in the crush style: per-letter half-block letterforms joined horizontally, flanked by diagonal ╱ fields, with a meta row (brand name + version) above and a deep→light brand gradient applied per row. Mirrors the composition of crush's internal/ui/logo but with our own DeepSeek Ocean glyphs and colors.

markdown.go renders assistant text through Glamour (markdown-aware, ANSI-styled) and reasoning / tool output through word-aware wrap.

A single Glamour renderer is cached per (style, width) tuple — the renderer constructor parses a non-trivial style JSON, so reusing across refreshes keeps the per-keystroke render cost negligible.

Package tui implements the Bubble Tea TUI for deepseekcode.

Architecture in one paragraph: a single tea.Program owns the UI state. The agent runs in a goroutine; a separate consumer goroutine reads agent.Events() and wraps each event into a single agentEventMsg that is dispatched to the tea.Program. Update folds that one message type — type-switching on the inner Event — into the App model. Permission asks travel the same channel and carry their reply chan, so the agent goroutine blocks waiting on the user without any out-of-band wiring.

Components: items.go owns rendered chat-item types; scrollback.go owns the chat buffer + stream cursors + visual selection; app.go owns the model + Update; status.go renders the status line; theme.go owns Lip Gloss styles; keymap.go owns keybindings.

pager.go implements a fullscreen overlay for viewing tool results that would otherwise dominate the chat scrollback. The pager owns its own viewport so j/k/G/gg navigate the long output independently of the chat scroll position.

Activated by `p` in Normal mode (opens the last tool result) and by future callers like /paste, /diff. Dismissed by `q` or esc, which returns the user to Insert mode with input refocused.

permission.go owns the modal permission card: pending ask state, 2×2 button grid, key resolution, and rendering. App composes one PermissionFlow instead of carrying pendingPerm + permCursor as flat fields plus a 130-line renderPermissionPrompt method.

question.go owns the modal question card: pending ask state, per-question option list, single/multi select, and rendering. Mirrors permission.go's Open/Resolve/Render pattern.

scrollback.go owns the chat history: items, in-progress stream cursors, the rendered line cache, and visual-mode selection.

Why a dedicated module. The bug-prone pattern this replaces was "raw pointers into the items slice" (streamText / streamThink), which dangled after slice realloc and needed a backward-walking rebind heuristic that itself had a cross-turn bleed bug. Indices stay valid because the items slice only ever appends: nothing reorders, nothing removes.

Selection invariant. visual-mode selection is captured at the rendered-line index space; if the underlying items mutate during a selection (e.g. streaming tokens extend the buffer), the seq number drifts and the selection auto-invalidates on the next Render. Keeps the indexing model honest without needing to re-map selections across content changes.

visual.go implements a Vim-style line-range selection mode for the scrollback. Activated by `v` in Normal mode.

Why we need this: alt-screen mode (tea.WithAltScreen) means the terminal shows only the currently rendered frame — no scrollback history above it. Terminal-native shift-drag selection therefore can't reach content that the user has scrolled past, which is exactly the case for long agent outputs that span multiple screens.

Visual mode solves that by tracking a line-space cursor that:

  • moves with j/k, ^U/^D, gg/G inside the in-app viewport
  • scrolls the viewport when it crosses the visible window
  • paints a reverse-video highlight on the [anchor, cursor] range
  • yanks the highlighted text (ANSI-stripped) on `y`

Selection survives content updates because indices live in "rendered line" space — newly appended lines extend the buffer but don't shift existing indices.

welcome.go renders the startup banner. Width-adaptive: a terminal narrower than the letterform wordmark falls back to a single-line greeting so nothing wraps mid-glyph. The wide banner is composed by logo.go (letterforms + diagonal fields + brand gradient).

yank.go implements clipboard copy via OSC 52 escape sequences.

OSC 52 ("Set Clipboard") is honored by iTerm2, kitty, alacritty, WezTerm, and recent tmux/screen. It is the only portable way to write the system clipboard from inside a TUI without taking a hard dependency on platform-specific paste daemons (xclip, pbcopy, wsl). On terminals that don't support it the sequence is silently ignored — no crash, no visible noise.

We deliberately write to stderr rather than stdout. Bubble Tea's renderer owns stdout and would otherwise risk interleaving the escape mid-frame. stderr connects to the same tty in interactive sessions, so the terminal sees the sequence either way.

Index

Constants

View Source
const (
	IconCheck       = "✓"
	IconToolPending = "●"
	IconToolOk      = "✓"
	IconToolErr     = "×"
	IconArrowRight  = "→"
	IconRadioOn     = "◉"
	IconRadioOff    = "○"
	BarThick        = "▌" // 工具卡片左竖边条
	BarThin         = "│"
	SectionRule     = "─"
	ScrollbarThumb  = "┃"
	ScrollbarTrack  = "│"
	IconSkill       = "▲"
	IconModel       = "◇"
)

Variables

This section is empty.

Functions

func ApplyBoldForegroundGrad

func ApplyBoldForegroundGrad(base lipgloss.Style, input string, color1, color2 color.Color) string

ApplyBoldForegroundGrad renders a given string with a bold horizontal gradient foreground. Returns "" for empty input.

func ApplyForegroundGrad

func ApplyForegroundGrad(base lipgloss.Style, input string, color1, color2 color.Color) string

ApplyForegroundGrad renders a given string with a horizontal gradient foreground. Returns "" for empty input.

func ForegroundGrad

func ForegroundGrad(base lipgloss.Style, input string, bold bool, color1, color2 color.Color) []string

ForegroundGrad returns a slice of strings representing the input string rendered with a horizontal gradient foreground from color1 to color2. Each string in the returned slice corresponds to a grapheme cluster in the input string.

func Highlight

func Highlight(t Theme, source, lang string) string

Highlight uses chroma to syntax-highlight source by lang, returning an ANSI-colored string. Background is locked to the Ocean card body color. Returns source unchanged if lang is empty or unrecognized.

func RenderHUD

func RenderHUD(data HUDData, width int) string

RenderHUD renders a single-line status HUD with width-aware truncation. Returns an empty string when data is empty.

func RenderToolSummary

func RenderToolSummary(tool, args, result string, isError bool, width int) string

RenderToolSummary renders a one-line tool summary with width-aware truncation. Different tools get different summary formats.

Types

type App

type App struct {
	// contains filtered or unexported fields
}

App is the root Bubble Tea Model.

One App per running TUI process. The Agent runs in a goroutine owned by the App; a separate pumpEvents goroutine consumes agent.Events() and forwards each event as an agentEventMsg via App.send (the tea.Program.Send func, captured in Run).

func New

func New(cfg Config) *App

New constructs an App. The returned App is a tea.Model; pass it to tea.NewProgram and call .Run().

func (*App) Init

func (a *App) Init() tea.Cmd

Init satisfies tea.Model. The first scrollback entry is an ASCII welcome banner (whale mascot + DEEPSEEKCODE wordmark); startup notices like resume confirmations follow.

func (*App) Run

func (a *App) Run() error

Run starts the Bubble Tea program. Blocks until quit.

Mouse cell-motion is enabled so we receive click + drag + release events. We use them to drive a TUI-managed selection (handleMouse): click-drag highlights lines, dragging past the top/bottom edge auto-scrolls the viewport so the selection can span the entire scrollback, and releasing the button auto-yanks the selected text to the clipboard via OSC 52. Mouse wheel still scrolls the viewport (bubbles/viewport handles that).

Why not native terminal drag-select: alt-screen mode means the terminal has no scrollback above our rendered frame, so native drag tops out at the visible window. TUI-managed selection is the only way to extend across content the user has scrolled past.

If the user wants native terminal behavior (mouse drag-select + terminal's own scrollback), `P` in Normal mode (or /export) opens the full session in $PAGER (default `less -R`) which owns the TTY completely while running.

func (*App) Update

func (a *App) Update(msg tea.Msg) (tea.Model, tea.Cmd)

Update folds incoming messages into the model.

func (*App) View

func (a *App) View() tea.View

View renders the whole UI.

type Chrome

type Chrome struct {
	// contains filtered or unexported fields
}

Chrome owns the live activity band — spinner, phase caption, and the tick-active flag that the App uses to avoid scheduling redundant redraw timers.

func NewChrome

func NewChrome() *Chrome

NewChrome returns an idle Chrome with no scheduled tick.

func (*Chrome) Active

func (c *Chrome) Active() bool

Active reports whether an activity is in progress (spinner shown).

func (*Chrome) AdvanceFrame

func (c *Chrome) AdvanceFrame()

AdvanceFrame moves the spinner one tick forward.

func (*Chrome) BeginThinking

func (c *Chrome) BeginThinking()

BeginThinking transitions the band to the "thinking…" caption.

func (*Chrome) BeginTool

func (c *Chrome) BeginTool(name string)

BeginTool transitions to the tool caption. It is called once per tool call; a turn's parallel tool calls each call it. The first call of a fresh batch (phase was not already tool) resets the batch counters and the elapsed clock; subsequent calls in the same batch only bump the started count so the caption can render "N ready" against the batch size.

func (*Chrome) BeginWriting

func (c *Chrome) BeginWriting()

BeginWriting transitions to the "writing…" caption.

func (*Chrome) EndToolBatch

func (c *Chrome) EndToolBatch()

EndToolBatch marks the current tool batch closed at a step boundary (called on EventStepFinish, once per step). The counters are NOT zeroed here — that is deferred to the next BeginTool so the completed "N ready" stays visible through the inter-step gap rather than flashing "0 ready".

func (*Chrome) MarkTickStarted

func (c *Chrome) MarkTickStarted()

MarkTickStarted / MarkTickStopped flip the tick-scheduled flag. Idempotent — handlers call MarkTickStarted defensively.

func (*Chrome) MarkTickStopped

func (c *Chrome) MarkTickStopped()

MarkTickStopped is called when the redraw tick concludes its follow-up scheduling — agent idle AND chrome idle.

func (*Chrome) Render

func (c *Chrome) Render(t Theme, showNewBelow bool) string

Render returns the one-line band content. When idle and the user has scrolled away from the bottom, it surfaces the "new content below" indicator instead. Returns "" when there's nothing to say — the reserved row renders as blank.

func (*Chrome) Reset

func (c *Chrome) Reset()

Reset returns the band to idle — called on agent done.

func (*Chrome) SetFirstTokenTimeout

func (c *Chrome) SetFirstTokenTimeout(d time.Duration)

SetFirstTokenTimeout records the agent's real first-token timeout so the cold-start caption can quote it. Called once at App construction.

func (*Chrome) TickActive

func (c *Chrome) TickActive() bool

TickActive reports whether a redraw ticker is currently scheduled.

func (*Chrome) ToolReady

func (c *Chrome) ToolReady()

ToolReady records that one tool call in the current batch has produced a result. Clamped to the started count. No-op outside a tool batch.

func (*Chrome) UpdateTokens

func (c *Chrome) UpdateTokens(n int)

UpdateTokens refreshes the rolling token estimate shown in the caption.

type Config

type Config struct {
	Agent    *agent.Agent
	Model    string
	Thinking bool
	Theme    string
	Cwd      string

	// Optional persistence integration. Provide all three or none.
	SessionID    string
	UndoFn       func(n int) (int, error)
	ListSessions func() ([]session.Session, error)
	SetModelFn   func(model string) error

	// StartupNotices are shown as info chat items at TUI start. Used for
	// resume confirmations, warnings about degraded persistence, etc. —
	// anything the caller would have written to stderr in CLI mode.
	StartupNotices []string

	// CompactionCount initialises the status-line compaction counter from
	// a resumed session's history. Populate from sess.CompactionCount.
	CompactionCount int

	// Commands are user-defined slash commands loaded from .deepseek/command/*.md.
	Commands map[string]commands.Command
}

Config bundles construction params for New.

type HUDData

type HUDData struct {
	Model           string
	Effort          string
	ContextTokens   int
	ContextLimit    int
	CacheHitRatio   float64
	InputHitTokens  int
	InputMissTokens int
	SavedCNY        float64
	OutputTokens    int
	ReasoningTokens int
	StepCNY         float64
	SessionCNY      float64
	ActiveAgent     string
	RunningJobs     int
}

HUDData holds the data for rendering the status HUD.

type Overlay

type Overlay struct {
	// contains filtered or unexported fields
}

Overlay owns the modal-picker state — which picker is up, where the cursor sits, and the picker-specific data rows. App composes one of these instead of carrying overlay/overlayCursor/models/ sessionsRows as four flat fields.

func NewOverlay

func NewOverlay() *Overlay

NewOverlay returns an idle Overlay (mode = chat, nothing open).

func (*Overlay) Close

func (o *Overlay) Close()

Close returns to the chat view.

func (*Overlay) Cursor

func (o *Overlay) Cursor() int

Cursor returns the picker's current cursor index.

func (*Overlay) IsOpen

func (o *Overlay) IsOpen() bool

IsOpen reports whether a picker is currently visible.

func (*Overlay) Mode

func (o *Overlay) Mode() overlayMode

Mode returns the active overlay mode (modeChat when none is open).

func (*Overlay) Models

func (o *Overlay) Models() []modelOption

Models / SessionsRows return the picker-specific row slices for rendering. Returned slices are read-only.

func (*Overlay) MoveDown

func (o *Overlay) MoveDown()

MoveDown / MoveUp advance the cursor inside the picker.

func (*Overlay) MoveUp

func (o *Overlay) MoveUp()

func (*Overlay) OpenModels

func (o *Overlay) OpenModels(activeID string)

OpenModels switches to the /models picker. Cursor lands on the row that matches activeID, falling back to 0 if no match.

func (*Overlay) OpenSessions

func (o *Overlay) OpenSessions(rows []sessionRow)

OpenSessions switches to the /sessions picker with the supplied rows.

func (*Overlay) OpenTape

func (o *Overlay) OpenTape()

OpenTape switches to the /tape view with the cursor at the top.

func (*Overlay) SelectedModelID

func (o *Overlay) SelectedModelID() string

SelectedModelID returns the model id under the cursor, or "" when the cursor is out of range.

func (*Overlay) SelectedSessionID

func (o *Overlay) SelectedSessionID() string

SelectedSessionID returns the session id under the cursor, or "" when the cursor is out of range.

func (*Overlay) SessionsRows

func (o *Overlay) SessionsRows() []sessionRow

type PermissionFlow

type PermissionFlow struct {
	// contains filtered or unexported fields
}

PermissionFlow encapsulates the modal permission card. Mutations only through methods.

func NewPermissionFlow

func NewPermissionFlow() *PermissionFlow

NewPermissionFlow returns an inactive flow (no pending ask).

func (*PermissionFlow) Active

func (p *PermissionFlow) Active() bool

Active reports whether a permission ask is pending user input.

func (*PermissionFlow) MoveDown

func (p *PermissionFlow) MoveDown()

func (*PermissionFlow) MoveLeft

func (p *PermissionFlow) MoveLeft()

func (*PermissionFlow) MoveRight

func (p *PermissionFlow) MoveRight()

func (*PermissionFlow) MoveUp

func (p *PermissionFlow) MoveUp()

Nav — 2×2 grid keyed as:

0 1
2 3

func (*PermissionFlow) Open

Open stores the ask and resets the cursor to the safe default (allow once). Caller is responsible for switching App.mode to modePermission and re-laying out the screen.

func (*PermissionFlow) Render

func (p *PermissionFlow) Render(t Theme, width int) string

Render returns the modal permission card. Returns "" when there's nothing pending.

func (*PermissionFlow) Resolve

func (p *PermissionFlow) Resolve(key string) (resp agent.PermissionResponse, reply chan<- agent.PermissionResponse, check permissions.Check, ok bool)

Resolve maps a key to a button decision. Returns (resp, reply, check, ok=true) when the key resolved a button; ok=false means the key wasn't a button (caller should swallow). On ok=true the flow is cleared (Active becomes false) — the caller is responsible for sending resp on reply, restoring mode, and re-layouting.

func (*PermissionFlow) ShiftTab

func (p *PermissionFlow) ShiftTab()

func (*PermissionFlow) Tab

func (p *PermissionFlow) Tab()

func (*PermissionFlow) Tool

func (p *PermissionFlow) Tool() string

Tool returns the tool name behind the pending ask, or "" when no ask is pending.

type QuestionFlow

type QuestionFlow struct {
	// contains filtered or unexported fields
}

QuestionFlow encapsulates the modal question card.

func NewQuestionFlow

func NewQuestionFlow() *QuestionFlow

NewQuestionFlow returns an inactive flow.

func (*QuestionFlow) Active

func (q *QuestionFlow) Active() bool

Active reports whether a question ask is pending.

func (*QuestionFlow) Cancel

func (q *QuestionFlow) Cancel() chan<- tools.QuestionResponse

Cancel clears the pending flow and returns the reply channel so the caller can send an empty response to unblock the agent.

func (*QuestionFlow) MoveDelta

func (q *QuestionFlow) MoveDelta(delta int)

MoveDelta moves the option cursor up (negative) or down (positive).

func (*QuestionFlow) Next

func (q *QuestionFlow) Next() (done bool, resp tools.QuestionResponse, reply chan<- tools.QuestionResponse)

Next advances to the next question or finishes. Returns done=true when all questions are answered, along with the full response and the reply channel.

func (*QuestionFlow) Open

func (q *QuestionFlow) Open(ev agent.EventQuestionAsk)

Open stores the ask and resets state. Caller switches to modeQuestion.

func (*QuestionFlow) Render

func (q *QuestionFlow) Render(t Theme, width int) string

Render returns the modal question card.

func (*QuestionFlow) Toggle

func (q *QuestionFlow) Toggle()

Toggle flips the selection of the current option (multi-select).

type RenderCache

type RenderCache struct {
	// contains filtered or unexported fields
}

func NewRenderCache

func NewRenderCache(max int) *RenderCache

func (*RenderCache) Get

func (c *RenderCache) Get(key string) (string, bool)

func (*RenderCache) Put

func (c *RenderCache) Put(key, value string)

type Scrollback

type Scrollback struct {
	// contains filtered or unexported fields
}

Scrollback is the deep module that owns the chat scrollback. Mutations only through methods. Indices into items are stable — callers may take them away and reuse them later.

func NewScrollback

func NewScrollback() *Scrollback

NewScrollback returns an empty Scrollback with no in-progress streams and no selection.

func (*Scrollback) AppendError

func (s *Scrollback) AppendError(text string)

AppendError adds an error line and closes any in-progress stream.

func (*Scrollback) AppendHookFired

func (s *Scrollback) AppendHookFired(hookName, event, decision, reason string, dur time.Duration)

AppendHookFired adds a hook-execution line. Deny decisions are rendered in red; other decisions are informational.

func (*Scrollback) AppendInfo

func (s *Scrollback) AppendInfo(text string)

AppendInfo adds an out-of-band notice. Closes streams: an info line interleaved with streaming would visually split the stream.

func (*Scrollback) AppendReasoning

func (s *Scrollback) AppendReasoning(delta string) int

AppendReasoning extends the in-progress reasoning block. Defends against deltas without a prior Start by lazily starting one. Returns the rough token estimate (chars/4) for chrome counters.

func (*Scrollback) AppendRepair

func (s *Scrollback) AppendRepair(kind, tool, message string)

AppendRepair adds a repair receipt line. Kind is one of args_completed, args_invalid, recovered, suppressed, schema_complex.

func (*Scrollback) AppendStepFinish

func (s *Scrollback) AppendStepFinish(stopReason string, usage llm.Usage, model string)

AppendStepFinish closes the step's footer line with usage and stop-reason. Ends any in-progress stream.

func (*Scrollback) AppendText

func (s *Scrollback) AppendText(delta string) (created bool, tokens int)

AppendText extends the in-progress assistant-text block; creates one if none is in progress. Returns (createdNewBlock, runningTokens). Callers (chrome) flip to "writing" phase when createdNewBlock is true.

func (*Scrollback) AppendToolCall

func (s *Scrollback) AppendToolCall(callID, tool, args string)

AppendToolCall records a tool invocation and closes any in-progress text stream — tool calls partition assistant text into segments.

func (*Scrollback) AppendToolResult

func (s *Scrollback) AppendToolResult(callID string, result tools.Result, dur time.Duration)

AppendToolResult records the matching result for callID. Walks items backward to recover the tool name and args from the prior toolCall.

func (*Scrollback) AppendUser

func (s *Scrollback) AppendUser(text string)

AppendUser closes any in-progress stream (turn boundary) and adds the user prompt line.

func (*Scrollback) AppendWelcome

func (s *Scrollback) AppendWelcome()

AppendWelcome adds the startup banner. Does not touch streams (no stream is in progress at startup).

func (*Scrollback) BeginSelection

func (s *Scrollback) BeginSelection(line int)

BeginSelection starts a visual-mode selection anchored at line. Captures the current seq; subsequent index-space mutations invalidate the selection on the next Render.

func (*Scrollback) CancelSelection

func (s *Scrollback) CancelSelection()

CancelSelection drops any active selection without yanking.

func (*Scrollback) Clear

func (s *Scrollback) Clear()

Clear empties the scrollback and resets stream cursors + selection.

func (*Scrollback) EndReasoning

func (s *Scrollback) EndReasoning()

EndReasoning closes the in-progress reasoning block. No-op if none.

func (*Scrollback) EndSelection

func (s *Scrollback) EndSelection() string

EndSelection returns the selected text (ANSI-stripped) and clears the selection. Returns "" if nothing was selected.

func (*Scrollback) EndStreams

func (s *Scrollback) EndStreams()

EndStreams closes any in-progress stream cursors. Idempotent — safe to call at turn boundaries (agent done) and from every one-shot appender as a defensive reset.

func (*Scrollback) ExpandLastResult

func (s *Scrollback) ExpandLastResult() bool

ExpandLastResult expands the most recent collapsed (and currently truncated) tool result. Returns true if it found one.

func (*Scrollback) ExtendSelection

func (s *Scrollback) ExtendSelection(line int)

ExtendSelection moves the selection cursor to line. No-op if no selection is active.

func (*Scrollback) FullLines

func (s *Scrollback) FullLines() []string

FullLines returns the line split from the last Render call. Returns nil if Render hasn't been called yet. Used by mouse/visual cursor math to clamp into a valid index range.

func (*Scrollback) Items

func (s *Scrollback) Items() []chatItem

Items returns a read-only snapshot of the items slice. The slice header may be retained for the caller's read but must not be mutated; mutations during streaming would race.

func (*Scrollback) LastAssistantText

func (s *Scrollback) LastAssistantText() string

LastAssistantText returns the text of the most recent assistant text item, or "" when none.

func (*Scrollback) LastToolResult

func (s *Scrollback) LastToolResult() (tool, content string, ok bool)

LastToolResult returns the (tool, content) of the most recent non-empty tool result, or ("","",false) when none exists. Used by the pager overlay.

func (*Scrollback) Len

func (s *Scrollback) Len() int

Len returns the number of items.

func (*Scrollback) Render

func (s *Scrollback) Render(t Theme, width int) string

Render returns the ANSI-styled scrollback at the given width. The underlying line-split is cached on (width, seq); selection highlight is re-applied on every call since cursor moves don't bump seq.

Side-effect: if the active selection's seq has drifted (the items changed underneath it), the selection clears here. See file header.

func (*Scrollback) SelectionActive

func (s *Scrollback) SelectionActive() bool

SelectionActive reports whether a selection is in progress.

func (*Scrollback) SelectionCursor

func (s *Scrollback) SelectionCursor() int

SelectionCursor returns the current cursor line, or -1 if no selection is active. Used to drive viewport auto-scroll.

func (*Scrollback) SelectionRange

func (s *Scrollback) SelectionRange() (lo, hi int, active bool)

SelectionRange returns (lo, hi, active). lo ≤ hi.

func (*Scrollback) Seq

func (s *Scrollback) Seq() uint64

Seq returns the mutation counter. Bumped on any state change that affects rendering. Useful for tests and for dirty-detection.

func (*Scrollback) StartReasoning

func (s *Scrollback) StartReasoning()

StartReasoning marks the start of a reasoning block. The next AppendReasoning calls extend that block until EndReasoning.

func (*Scrollback) SwapSelectionEnds

func (s *Scrollback) SwapSelectionEnds()

SwapSelectionEnds swaps anchor and cursor, mirroring Vim's `o`.

func (*Scrollback) ToggleAllReasoning

func (s *Scrollback) ToggleAllReasoning()

ToggleAllReasoning flips every reasoning block to a single target state — collapse if any are open, else expand.

func (*Scrollback) ToggleLastReasoning

func (s *Scrollback) ToggleLastReasoning()

ToggleLastReasoning expands/collapses the most recent reasoning block. No-op if none.

type Theme

type Theme struct {
	Name string

	UserPrompt    lipgloss.Style
	AssistantText lipgloss.Style
	Reasoning     lipgloss.Style
	ReasoningFold lipgloss.Style

	ToolCall lipgloss.Style
	ToolOk   lipgloss.Style
	ToolErr  lipgloss.Style
	ToolBody lipgloss.Style

	HookInfo lipgloss.Style
	HookDeny lipgloss.Style

	Repair lipgloss.Style

	Status      lipgloss.Style
	StatusModel lipgloss.Style
	StatusGood  lipgloss.Style
	StatusBad   lipgloss.Style

	Info  lipgloss.Style
	Error lipgloss.Style
	Hint  lipgloss.Style

	InputBorder    lipgloss.Style
	InputBorderDim lipgloss.Style // Normal mode: dim border around textarea
	PermPrompt     lipgloss.Style
	PermFocus      lipgloss.Style // focused permission-card button (inverted)
	PermButton     lipgloss.Style // unfocused permission-card button

	// Ocean visual identity — brand gradient endpoints + card styles.
	BrandDeep  color.Color    // #1D4ED8
	BrandLight color.Color    // #7DD3FC
	CardBar    lipgloss.Style // left sidebar bar (foreground BrandDeep)
	CardHeader lipgloss.Style
	CardBody   lipgloss.Style
}

Theme holds the lipgloss styles used across the TUI. We ship a dark theme as default and a light alt. Theme is selected at startup from config; mid-session switching is a v0.2 feature.

func DarkTheme

func DarkTheme() Theme

DarkTheme returns the default dark theme (DeepSeek Ocean).

func LightTheme

func LightTheme() Theme

LightTheme returns the light alt. Same semantics, inverted contrast.

func PickTheme

func PickTheme(name string) Theme

PickTheme returns the configured theme by name; defaults to dark on unknown names.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL