ui

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Sep 19, 2026 License: MIT Imports: 53 Imported by: 0

Documentation

Overview

Package ui provides the terminal user interface.

Package ui provides the terminal user interface.

Index

Constants

View Source
const (
	// Unicode icons
	IconBacklogUnicode    = "◦"
	IconInProgressUnicode = "▶"
	IconBlockedUnicode    = "⚠"
	IconDoneUnicode       = "✓"
	IconPinUnicode        = "📌"
	IconWarningUnicode    = "⚠"
	IconArrowUpUnicode    = "↑"
	IconArrowDownUnicode  = "↓"
	IconArrowLeftUnicode  = "←"
	IconArrowRightUnicode = "→"
	IconShiftUpUnicode    = "⇧↑"
	IconShiftDownUnicode  = "⇧↓"

	// ASCII fallbacks
	IconBacklogASCII      = "o"
	IconInProgressASCII   = ">"
	IconProcessingASCII   = "~"
	IconBlockedASCII      = "!"
	IconDoneASCII         = "*"
	IconPinASCII          = "P"
	IconWarningASCII      = "!"
	IconArrowUpASCII      = "^"
	IconArrowDownASCII    = "v"
	IconArrowLeftASCII    = "<"
	IconArrowRightASCII   = ">"
	IconShiftUpASCII      = "^^"
	IconShiftDownASCII    = "vv"
	IconProcessingUnicode = "⋯"
	IconDefaultUnicode    = "·"
	IconDefaultASCII      = "."
)

Icon constants - Unicode and ASCII versions

View Source
const BellDebounce = 500 * time.Millisecond

BellDebounce is the quiet period used to coalesce terminal bells.

Task transitions frequently arrive in bursts: a single poll cycle can pick up several status changes at once, and the executor's event channel delivers back-to-back events when a batch of tasks finishes. Ringing once per event turns that into a machine-gun of beeps. Instead each bell restarts this window and only the last event in the burst actually rings.

The window is deliberately shorter than the board's 1s poll interval, so genuinely separate events still get their own bell — only same-burst events collapse.

View Source
const MobileWidthThreshold = 80

MobileWidthThreshold is the minimum width for showing all columns. Below this, only the selected column is shown.

Variables

View Source
var (
	PinHook     func(taskID int64, pinned bool) error
	ArchiveHook func(taskID int64) error
	DeleteHook  func(taskID int64) error
	StatusHook  func(taskID int64, status string) error
)
View Source
var (
	// Primary colors
	ColorPrimary   = lipgloss.Color("#61AFEF") // Soft blue (OneDark default)
	ColorSecondary = lipgloss.Color("#56B6C2") // Cyan
	ColorSuccess   = lipgloss.Color("#98C379") // Green
	ColorWarning   = lipgloss.Color("#E5C07B") // Yellow
	ColorError     = lipgloss.Color("#E06C75") // Red
	ColorDangerous = lipgloss.Color("#E06C75") // Red (same as error, for dangerous mode indicator)
	ColorMuted     = lipgloss.Color("#5C6370") // Gray

	// Status colors
	ColorInProgress = lipgloss.Color("#D19A66") // Orange
	ColorDone       = lipgloss.Color("#98C379") // Green
	ColorBlocked    = lipgloss.Color("#E06C75") // Red

	// Default project colors palette - used when no color is set
	// These are distinct, visually pleasing colors for project labels
	DefaultProjectColors = []string{
		"#C678DD",
		"#61AFEF",
		"#56B6C2",
		"#98C379",
		"#E5C07B",
		"#E06C75",
		"#D19A66",
		"#ABB2BF",
	}

	// Type colors (fixed, not theme-dependent)
	ColorCode     = lipgloss.Color("#C678DD") // Purple
	ColorWriting  = lipgloss.Color("#E5C07B") // Yellow
	ColorThinking = lipgloss.Color("#56B6C2") // Cyan
)

Colors - these are updated by refreshStyles() when theme changes

View Source
var (
	// Text styles
	Bold           = lipgloss.NewStyle().Bold(true)
	Dim            = lipgloss.NewStyle().Foreground(ColorMuted)
	Title          = lipgloss.NewStyle().Bold(true).Foreground(ColorPrimary)
	Subtitle       = lipgloss.NewStyle().Foreground(ColorSecondary)
	Success        = lipgloss.NewStyle().Foreground(ColorSuccess)
	Warning        = lipgloss.NewStyle().Foreground(ColorWarning)
	Error          = lipgloss.NewStyle().Foreground(ColorError)
	AttachmentChip = lipgloss.NewStyle().
					Border(lipgloss.RoundedBorder()).
					BorderForeground(ColorMuted).
					Foreground(ColorMuted).
					Padding(0, 1)
	AttachmentChipSelected = lipgloss.NewStyle().
							Border(lipgloss.RoundedBorder()).
							BorderForeground(ColorPrimary).
							Foreground(ColorPrimary).
							Bold(true).
							Padding(0, 1)

	// Status badges
	StatusInProgress = lipgloss.NewStyle().
						Foreground(ColorInProgress).
						SetString("⋯")
	StatusDone = lipgloss.NewStyle().
				Foreground(ColorDone).
				SetString("✓")
	StatusBlocked = lipgloss.NewStyle().
					Foreground(ColorBlocked).
					SetString("!")

	// Box styles
	Box = lipgloss.NewStyle().
		Border(lipgloss.RoundedBorder()).
		BorderForeground(ColorMuted).
		Padding(0, 1)

	FocusedBox = lipgloss.NewStyle().
				Border(lipgloss.RoundedBorder()).
				BorderForeground(ColorPrimary).
				Padding(0, 1)

	// List item styles
	ListItem = lipgloss.NewStyle().
				PaddingLeft(2)

	SelectedListItem = lipgloss.NewStyle().
						PaddingLeft(2).
						Foreground(ColorPrimary).
						Bold(true)

	// Help bar
	HelpBar = lipgloss.NewStyle().
			Foreground(ColorMuted).
			Padding(1, 0)

	HelpKey = lipgloss.NewStyle().
			Foreground(ColorSecondary).
			Bold(true)

	HelpDesc = lipgloss.NewStyle().
				Foreground(ColorMuted)

	// Header
	Header = lipgloss.NewStyle().
			Bold(true).
			Foreground(ColorPrimary).
			Padding(0, 1).
			MarginBottom(1)

	// Tag styles
	ProjectTag = lipgloss.NewStyle().
				Foreground(ColorSecondary).
				Bold(true)

	TypeTag = lipgloss.NewStyle().
			Foreground(ColorMuted).
			Italic(true)
)

Base styles - these are updated by refreshStyles() when theme changes

View Source
var (
	ColorPROpen    = lipgloss.Color("#98C379") // Green - ready to merge
	ColorPRDraft   = lipgloss.Color("#5C6370") // Gray - draft
	ColorPRMerged  = lipgloss.Color("#C678DD") // Purple - merged
	ColorPRClosed  = lipgloss.Color("#E06C75") // Red - closed
	ColorPRPending = lipgloss.Color("#E5C07B") // Yellow - checks running
	ColorPRFailing = lipgloss.Color("#E06C75") // Red - checks failing
)

PR status colors

View Source
var (
	ColorDiffAdd    = lipgloss.Color("#98C379") // Green for additions (bright)
	ColorDiffDel    = lipgloss.Color("#E06C75") // Red for deletions (bright)
	ColorDiffAddDim = lipgloss.Color("#5C7A4A") // Dim green for unselected
	ColorDiffDelDim = lipgloss.Color("#8B4C52") // Dim red for unselected
)

Diff stat colors (git-style green/red)

View Source
var ActivityHook func(taskID int64) (bool, string)

ActivityHook reports whether the thread is mid-turn and what it is doing, so the view can say so instead of looking idle while bb works.

View Source
var AnswerApprovalHook func(taskID int64, approval PendingApproval, choice ApprovalChoice) error

AnswerApprovalHook delivers the chosen answer.

View Source
var ApprovalsHook func(taskID int64) ([]PendingApproval, error)

ApprovalsHook returns whatever the task's thread is waiting on.

View Source
var BuiltinThemes = map[string]Theme{
	"onedark":    OneDarkTheme,
	"default":    DefaultTheme,
	"nord":       NordTheme,
	"gruvbox":    GruvboxTheme,
	"catppuccin": CatppuccinTheme,
	"dracula":    DraculaTheme,
}

BuiltinThemes contains all built-in themes.

View Source
var CatppuccinTheme = Theme{
	Name: "catppuccin",

	Primary:   "#CBA6F7",
	Secondary: "#89DCEB",
	Muted:     "#6C7086",

	Success: "#A6E3A1",
	Warning: "#F9E2AF",
	Error:   "#F38BA8",

	InProgress: "#FAB387",
	Done:       "#A6E3A1",
	Blocked:    "#F38BA8",
	Backlog:    "#6C7086",

	CardBg:         "#313244",
	CardFg:         "#CDD6F4",
	CardBorder:     "#45475A",
	CardBorderHi:   "#CBA6F7",
	ColumnBorder:   "#45475A",
	ColumnBorderHi: "#CBA6F7",
}

CatppuccinTheme is inspired by Catppuccin Mocha - soothing pastel colors.

View Source
var ColumnTitles = [4]string{"Backlog", "In Progress", "Blocked", "Done"}

ColumnTitles labels the four board columns. They are variables because the column a status lands in is a product decision: TaskYou tracks work it queues itself, while bb threads start on creation and come back to you for answers, so bb-tui names them after what bb actually does.

View Source
var ComposerEnabled = false

ComposerEnabled turns on the message box at the bottom of the detail view. TaskYou has no composer because a task is driven by typing into its tmux pane; a bb thread is driven by sending it a message, so the detail view needs somewhere to write one.

View Source
var ContextHook func(taskID int64) ComposerContext

ContextHook supplies those chips. Without it the composer shows only the input, which is what it did before and is still correct for TaskYou.

View Source
var ConversationView = false

ConversationView renders a thread the way the app does — as a conversation rather than TaskYou's timestamped execution log. A bb thread is a conversation; a log of tool calls buries the part a person came to read.

View Source
var CreateTaskHook func(task *db.Task) (int64, error)

Write hooks. When bb-tui owns the data, a local write would be reverted by the next sync, so each of these forwards the action to bb instead. Returning an error surfaces it to the user rather than silently falling back. CreateTaskHook, when set, replaces creating a local task. bb-tui spawns a real thread instead, then reports the task id the mirror gave it so the board can select it.

View Source
var DefaultTheme = Theme{
	Name: "default",

	Primary:   "#7C3AED",
	Secondary: "#06B6D4",
	Muted:     "#6B7280",

	Success: "#10B981",
	Warning: "#F59E0B",
	Error:   "#EF4444",

	InProgress: "#9A9463",
	Done:       "#10B981",
	Blocked:    "#EF4444",
	Backlog:    "#6B7280",

	CardBg:         "#333333",
	CardFg:         "#FFFFFF",
	CardBorder:     "#6B7280",
	CardBorderHi:   "#7C3AED",
	ColumnBorder:   "#6B7280",
	ColumnBorderHi: "#7C3AED",
}

DefaultTheme is the original purple-heavy theme.

View Source
var DisableUpstreamVersionCheck = false

DisableUpstreamVersionCheck stops the TUI polling TaskYou's GitHub releases. bb-tui ships on bb's release cadence, so an upstream "ty upgrade" banner would be pointing at the wrong product.

View Source
var DraculaTheme = Theme{
	Name: "dracula",

	Primary:   "#BD93F9",
	Secondary: "#8BE9FD",
	Muted:     "#6272A4",

	Success: "#50FA7B",
	Warning: "#F1FA8C",
	Error:   "#FF5555",

	InProgress: "#FFB86C",
	Done:       "#50FA7B",
	Blocked:    "#FF5555",
	Backlog:    "#6272A4",

	CardBg:         "#44475A",
	CardFg:         "#F8F8F2",
	CardBorder:     "#6272A4",
	CardBorderHi:   "#BD93F9",
	ColumnBorder:   "#6272A4",
	ColumnBorderHi: "#BD93F9",
}

DraculaTheme is inspired by the Dracula color scheme - vibrant purples and pinks.

View Source
var GruvboxTheme = Theme{
	Name: "gruvbox",

	Primary:   "#83A598",
	Secondary: "#B8BB26",
	Muted:     "#665C54",

	Success: "#B8BB26",
	Warning: "#FABD2F",
	Error:   "#FB4934",

	InProgress: "#FE8019",
	Done:       "#B8BB26",
	Blocked:    "#FB4934",
	Backlog:    "#665C54",

	CardBg:         "#3C3836",
	CardFg:         "#EBDBB2",
	CardBorder:     "#504945",
	CardBorderHi:   "#83A598",
	ColumnBorder:   "#504945",
	ColumnBorderHi: "#83A598",
}

GruvboxTheme is inspired by the Gruvbox color scheme - retro, earthy tones.

View Source
var MenuItemsHook func(taskID int64, kind MenuKind, query string) []SlashItem

MenuItemsHook supplies completions for a trigger. The query is passed so a large project can be filtered by the server rather than shipped whole.

View Source
var NordTheme = Theme{
	Name: "nord",

	Primary:   "#88C0D0",
	Secondary: "#81A1C1",
	Muted:     "#4C566A",

	Success: "#A3BE8C",
	Warning: "#EBCB8B",
	Error:   "#BF616A",

	InProgress: "#D08770",
	Done:       "#A3BE8C",
	Blocked:    "#BF616A",
	Backlog:    "#4C566A",

	CardBg:         "#3B4252",
	CardFg:         "#ECEFF4",
	CardBorder:     "#4C566A",
	CardBorderHi:   "#88C0D0",
	ColumnBorder:   "#4C566A",
	ColumnBorderHi: "#88C0D0",
}

NordTheme is inspired by the Nord color palette - arctic, bluish tones.

View Source
var OneDarkTheme = Theme{
	Name: "onedark",

	Primary:   "#61AFEF",
	Secondary: "#56B6C2",
	Muted:     "#5C6370",

	Success: "#98C379",
	Warning: "#E5C07B",
	Error:   "#E06C75",

	InProgress: "#D19A66",
	Done:       "#98C379",
	Blocked:    "#E06C75",
	Backlog:    "#5C6370",

	CardBg:         "#3E4451",
	CardFg:         "#ABB2BF",
	CardBorder:     "#3E4451",
	CardBorderHi:   "#61AFEF",
	ColumnBorder:   "#3E4451",
	ColumnBorderHi: "#61AFEF",
}

OneDarkTheme is inspired by Atom's One Dark theme - subtle and easy on the eyes.

View Source
var RetryTurnHook func(taskID int64) error

RetryTurnHook, when set, replaces TaskYou's retry-with-feedback. bb's retry re-submits the turn whose failure put the thread in error; it takes no message, because the message it will re-send is the one that already failed.

View Source
var SendPromptHook func(taskID int64, text string, attachments []Attachment, model, permission string) error

SendPromptHook, when set, replaces the tmux/executor delivery path used to get text in front of a running agent. bb has no pane to type into: a thread is driven over the server API, so bb-tui sets this to post the message instead.

View Source
var StatusChoices func() []StatusChoice
View Source
var StopTaskHook func(taskID int64) error

StopTaskHook, when set, replaces cancelling a run. bb stops a thread through the server rather than by signalling a local process.

Functions

func ArrowPairLabel

func ArrowPairLabel() string

ArrowPairLabel is arrowPairLabel for callers outside this file.

func ClipboardImage

func ClipboardImage() (string, bool)

ClipboardImage writes any image on the system clipboard to a temporary file and returns its path. Terminals deliver a paste as text, so an image on the clipboard never reaches the program as keystrokes — it has to be asked for.

func CloseLogger

func CloseLogger()

CloseLogger closes the log file.

func FgStyle

func FgStyle(c lipgloss.Color) lipgloss.Style

FgStyle returns a cached foreground-only style for the given colour. Use this instead of lipgloss.NewStyle().Foreground(c) in per-frame render paths.

func FlushBell

func FlushBell()

FlushBell delivers a pending debounced bell right away. Call it before the process exits so a bell scheduled just before quitting still reaches the terminal.

func GetDefaultProjectColor

func GetDefaultProjectColor(index int) string

GetDefaultProjectColor returns a default color for a project based on its index. Used when a project doesn't have a color set.

func GetThemeBorderColors

func GetThemeBorderColors() (normal, highlighted lipgloss.Color)

GetThemeBorderColors returns border colors from the current theme.

func GetThemeCardColors

func GetThemeCardColors() (bg, fg lipgloss.Color)

GetThemeCardColors returns card-specific colors from the current theme.

func Hyperlink(target, label string) string

Hyperlink wraps text in an OSC 8 escape so a terminal that supports them makes it clickable, and one that does not simply prints the text.

func Icon

func Icon(unicodeIcon, asciiIcon string) string

Icon returns the appropriate icon based on terminal Unicode support.

func IconArrowDown

func IconArrowDown() string

IconArrowDown returns the down arrow.

func IconArrowLeft

func IconArrowLeft() string

IconArrowLeft returns the left arrow.

func IconArrowRight

func IconArrowRight() string

IconArrowRight returns the right arrow.

func IconArrowUp

func IconArrowUp() string

IconArrowUp returns the up arrow.

func IconBacklog

func IconBacklog() string

IconBacklog returns the backlog status icon.

func IconBlocked

func IconBlocked() string

IconBlocked returns the blocked status icon.

func IconDefault

func IconDefault() string

IconDefault returns the default/unknown status icon.

func IconDone

func IconDone() string

IconDone returns the done status icon.

func IconInProgress

func IconInProgress() string

IconInProgress returns the in-progress status icon.

func IconPin

func IconPin() string

IconPin returns the pin icon.

func IconProcessing

func IconProcessing() string

IconProcessing returns the processing status icon.

func IconShiftDown

func IconShiftDown() string

IconShiftDown returns the shift+down icon.

func IconShiftUp

func IconShiftUp() string

IconShiftUp returns the shift+up icon.

func ImageLine

func ImageLine(path string, width int) string

ImageLine is what a thread shows for an image: the picture itself where the terminal can draw one, and a labelled, clickable chip everywhere else.

func IsGlobalDangerousMode

func IsGlobalDangerousMode() bool

IsGlobalDangerousMode returns true if the system is running in global dangerous mode. This is set by the WORKTREE_DANGEROUS_MODE=1 environment variable.

func ListThemes

func ListThemes() []string

ListThemes returns the names of all built-in themes (sorted).

func LoadProjectColors

func LoadProjectColors(database *db.DB)

LoadProjectColors loads all project colors from the database into the cache.

func LoadThemeFromDB

func LoadThemeFromDB(getSetting func(string) (string, error))

LoadThemeFromDB loads and applies the saved theme from the database. If no theme is saved or the theme is invalid, OneDark is used as the default.

func LogPath

func LogPath() string

LogPath returns the path to the log file: ui.log beside the database, so an instance pointed at another database logs beside that one instead of into the live instance's file.

func PRDiffStats

func PRDiffStats(pr *github.PRInfo) string

PRDiffStats returns a formatted diff stats string like "+123 -45" with dim colors. Use this for unselected items. Returns empty string if no changes or PR is nil.

func PRDiffStatsBright

func PRDiffStatsBright(pr *github.PRInfo) string

PRDiffStatsBright returns diff stats with bright colors (for selected items).

func PRDiffStatsPlain

func PRDiffStatsPlain(pr *github.PRInfo) string

PRDiffStatsPlain returns diff stats without color (for selected items with inverted bg).

func PRStatusBadge

func PRStatusBadge(pr *github.PRInfo) string

PRStatusBadge returns a styled badge for a PR status.

func PRStatusDescription

func PRStatusDescription(pr *github.PRInfo) string

PRStatusDescription returns a human-readable description of PR status.

func PRStatusIcon

func PRStatusIcon(pr *github.PRInfo) string

PRStatusIcon returns just the icon character for a PR status (without styling). This is useful for creating consistently-sized badges with different color schemes.

func PrettyPermission

func PrettyPermission(mode string) string

prettyPermission uses bb's own wording for its permission modes. PrettyPermission exposes bb's own wording for a permission mode.

func ProjectColor

func ProjectColor(project string) lipgloss.Color

ProjectColor returns the color for a project. It uses the cached color from the database, or falls back to a default color.

func RenderConversationForTest

func RenderConversationForTest(logs []*db.TaskLog, width int, focused bool) string

RenderConversationForTest exposes the renderer so its output can be inspected without standing up a terminal.

func RenderConversationWithMarkdownForTest

func RenderConversationWithMarkdownForTest(
	logs []*db.TaskLog,
	width int,
	focused bool,
	markdown func(string) (string, error),
) string

RenderConversationWithMarkdownForTest renders with a supplied markdown renderer so the formatting can be inspected without a terminal.

func RenderInlineImage

func RenderInlineImage(path string, cellWidth int) string

RenderInlineImage returns the escape sequence that draws an image, or "" when the terminal cannot draw one.

func RingBell

func RingBell()

RingBell asks the terminal for an audible bell, debounced by BellDebounce so a burst of task events makes one sound rather than one per event.

func SetProjectColor

func SetProjectColor(project, color string)

SetProjectColor sets the color for a single project.

func SetProjectColors

func SetProjectColors(colors map[string]string)

SetProjectColors updates the project color cache with colors from the database. This should be called when projects are loaded.

func SetTheme

func SetTheme(name string) error

SetTheme sets the current theme by name.

func SetThemeFromJSON

func SetThemeFromJSON(data string) error

SetThemeFromJSON sets a custom theme from JSON.

func ShortProjectName

func ShortProjectName(project string) string

ShortProjectName abbreviates a project name for a card or a list row.

A hyphenated or spaced name becomes its initials ("my-side-project" → "msp"); a single long word is truncated ("a-very-long-name" → truncated). Names within the limit are returned unchanged, so most projects read normally.

func StatusColor

func StatusColor(status string) lipgloss.Color

StatusColor returns the background color for a status.

func StatusIcon

func StatusIcon(status string) string

StatusIcon returns the icon for a given status.

func StatusStyle

func StatusStyle(status string) lipgloss.Style

StatusStyle returns the style for a given status.

func StyleGeneration

func StyleGeneration() uint64

StyleGeneration returns the current style generation counter. Callers that cache rendered output should include this in their cache key.

func SupportsUnicode

func SupportsUnicode() bool

SupportsUnicode returns true if the terminal likely supports Unicode characters. It checks LANG, LC_ALL, and LC_CTYPE environment variables for UTF-8 indicators.

func ThemeToJSON

func ThemeToJSON(theme Theme) (string, error)

ThemeToJSON serializes a theme to JSON.

Types

type ActionPickerModel

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

ActionPickerModel is a modal list of plugin actions for the current task. It mirrors CommandPaletteModel: a self-contained sub-model with its own View, switched to via a dedicated View constant, so it never touches the huh form-router path.

func NewActionPickerModel

func NewActionPickerModel(taskTitle string, items []PluginActionItem, width, height int) *ActionPickerModel

NewActionPickerModel creates an action picker for the given items.

func (*ActionPickerModel) Init

func (m *ActionPickerModel) Init() tea.Cmd

Init implements tea.Model.

func (*ActionPickerModel) IsCancelled

func (m *ActionPickerModel) IsCancelled() bool

IsCancelled reports whether the user dismissed the picker.

func (*ActionPickerModel) Selected

func (m *ActionPickerModel) Selected() *PluginActionItem

Selected returns the chosen item, or nil if none was chosen yet.

func (*ActionPickerModel) SetSize

func (m *ActionPickerModel) SetSize(width, height int)

SetSize updates dimensions.

func (*ActionPickerModel) Update

func (m *ActionPickerModel) Update(msg tea.Msg) (*ActionPickerModel, tea.Cmd)

Update handles key input. It returns the model and never a command; the parent reads Selected()/IsCancelled() after each update.

func (*ActionPickerModel) View

func (m *ActionPickerModel) View() string

View renders the modal.

type AppModel

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

AppModel is the main application model.

func NewAppModel

func NewAppModel(database *db.DB, exec *executor.Executor, workingDir string, version ...string) *AppModel

NewAppModel creates a new application model.

func (*AppModel) ClearTerminalTask

func (m *AppModel) ClearTerminalTask()

ClearTerminalTask blanks the published variables so a tab title does not keep naming a task after ty exits.

func (*AppModel) DebugDetailLogCount

func (m *AppModel) DebugDetailLogCount() int

DebugDetailLogCount reports how much of the conversation has arrived.

func (*AppModel) DebugReloadDetail

func (m *AppModel) DebugReloadDetail() tea.Cmd

DebugReloadDetail re-reads the open thread. The harness needs it because a conversation is fetched in the background: the first paint after opening a thread legitimately has no logs, so a check that scrolls would be scrolling nothing.

func (*AppModel) DumpDebugStateToFile

func (m *AppModel) DumpDebugStateToFile(path string) error

DumpDebugStateToFile writes the current debug state to a file.

func (*AppModel) EnableReload

func (m *AppModel) EnableReload(token string)

EnableReload is called only by the local process that can re-exec itself.

func (*AppModel) EnableTerminalTaskReport

func (m *AppModel) EnableTerminalTaskReport()

EnableTerminalTaskReport makes the TUI publish its focused task as iTerm2 user variables. Local TUI only: over SSH, /dev/tty is the server's terminal, not the viewer's.

func (*AppModel) FocusTaskOnLoad

func (m *AppModel) FocusTaskOnLoad(taskID int64)

FocusTaskOnLoad selects taskID once the board finishes loading.

Used by `ty --task <id>` so a session can be reopened where it left off, rather than on whatever the board happens to sort first.

func (*AppModel) GenerateDebugState

func (m *AppModel) GenerateDebugState() DebugState

GenerateDebugState creates a snapshot of the current application state.

func (*AppModel) Init

func (m *AppModel) Init() tea.Cmd

Init initializes the model.

func (*AppModel) OpenPaletteOnLoad

func (m *AppModel) OpenPaletteOnLoad(query string)

OpenPaletteOnLoad opens the go-to-task palette with query typed in once the board has loaded (`ty open <search>`), leaving the pick to the user.

func (*AppModel) OpenTaskOnLoad

func (m *AppModel) OpenTaskOnLoad(taskID int64)

OpenTaskOnLoad opens a task's detail view once the board has loaded (`ty open`). It seeds the restore path a reloaded TUI uses. When this process is itself a reload, the RestoreReloadState that follows replaces it, so a restart returns to where the user was, not to the task on the original command line.

func (*AppModel) ReloadState

func (m *AppModel) ReloadState() (ReloadState, bool)

func (*AppModel) RestoreReloadState

func (m *AppModel) RestoreReloadState(state ReloadState)

func (*AppModel) SetDebugStatePath

func (m *AppModel) SetDebugStatePath(path string)

SetDebugStatePath sets the path for dumping debug state.

func (*AppModel) SetMouseCaptured

func (m *AppModel) SetMouseCaptured(captured bool)

SetMouseCaptured records whether the program started with mouse reporting on, so the toggle knows which way to go first.

func (*AppModel) SetTasks

func (m *AppModel) SetTasks(tasks []*db.Task)

SetTasks sets the tasks for the model (useful for debugging/testing).

func (*AppModel) ShowStartupNotice

func (m *AppModel) ShowStartupNotice(text string, d time.Duration)

ShowStartupNotice seeds the board's notification banner before the program starts, for something the user needs to see that happened before the TUI did — chiefly a daemon running a different build (see internal/handshake). stderr is not an option there: the alt screen wipes it on the first frame.

func (*AppModel) Update

func (m *AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd)

Update handles messages.

func (*AppModel) View

func (m *AppModel) View() string

View renders the current view.

type ApprovalChoice

type ApprovalChoice struct {
	Label       string
	Description string
	// Decision is set for tool approvals: allow_once, allow_for_session, deny.
	Decision string
	// QuestionID and OptionValue are set for user questions.
	QuestionID  string
	OptionValue string
}

ApprovalChoice is one answer the user can give to a pending interaction.

type ApprovalsModel

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

ApprovalsModel is a modal that answers whatever a thread is blocked on. It follows ActionPickerModel: a self-contained sub-model with its own View, reached through a dedicated View constant so it never touches the huh router.

func NewApprovalsModel

func NewApprovalsModel(taskTitle string, approvals []PendingApproval, width, height int) *ApprovalsModel

func (*ApprovalsModel) Answered

func (m *ApprovalsModel) Answered() (*PendingApproval, *ApprovalChoice)

func (*ApprovalsModel) ClearAnswer

func (m *ApprovalsModel) ClearAnswer(message string)

func (*ApprovalsModel) Init

func (m *ApprovalsModel) Init() tea.Cmd

func (*ApprovalsModel) IsCancelled

func (m *ApprovalsModel) IsCancelled() bool

func (*ApprovalsModel) SetSize

func (m *ApprovalsModel) SetSize(width, height int)

func (*ApprovalsModel) Update

func (m *ApprovalsModel) Update(msg tea.Msg) (*ApprovalsModel, tea.Cmd)

func (*ApprovalsModel) View

func (m *ApprovalsModel) View() string

type Attachment

type Attachment struct {
	Path  string
	Name  string
	Image bool
}

Attachment is a file the composer will send alongside the message.

func ExtractAttachments

func ExtractAttachments(text string, startIndex int) (string, []Attachment)

ExtractAttachments pulls dropped file paths out of a message and returns the text with each one replaced by a short placeholder. A path is only taken when it names a file that exists, so prose containing a slash is left alone.

type AttachmentsModel

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

AttachmentsModel displays and manages task attachments.

func NewAttachmentsModel

func NewAttachmentsModel(task *db.Task, database *db.DB, width, height int) *AttachmentsModel

NewAttachmentsModel creates a new attachments view.

func (*AttachmentsModel) AddFile

func (m *AttachmentsModel) AddFile(path string) tea.Cmd

AddFile adds a file to the task's attachments.

func (*AttachmentsModel) Init

func (m *AttachmentsModel) Init() tea.Cmd

Init initializes the model.

func (*AttachmentsModel) Update

func (m *AttachmentsModel) Update(msg tea.Msg) (*AttachmentsModel, tea.Cmd)

Update handles messages.

func (*AttachmentsModel) View

func (m *AttachmentsModel) View() string

View renders the attachments view.

type CommandPaletteModel

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

CommandPaletteModel represents the Command+P task switcher and AI command input.

func NewCommandPaletteModel

func NewCommandPaletteModel(database *db.DB, tasks []*db.Task, width, height int) *CommandPaletteModel

NewCommandPaletteModel creates a new command palette model.

func (*CommandPaletteModel) Init

func (m *CommandPaletteModel) Init() tea.Cmd

Init initializes the command palette.

func (*CommandPaletteModel) IsAICommandRequest

func (m *CommandPaletteModel) IsAICommandRequest() bool

IsAICommandRequest returns true if the user pressed Enter with text that should go to AI.

func (*CommandPaletteModel) IsCancelled

func (m *CommandPaletteModel) IsCancelled() bool

IsCancelled returns true if the user cancelled the palette.

func (*CommandPaletteModel) Projects

func (m *CommandPaletteModel) Projects() []*db.Project

Projects returns the available projects for AI context.

func (*CommandPaletteModel) RawInput

func (m *CommandPaletteModel) RawInput() string

RawInput returns the raw input text for AI command processing.

func (*CommandPaletteModel) SelectedAction

func (m *CommandPaletteModel) SelectedAction() *PluginActionItem

SelectedAction returns the chosen plugin action (action mode), or nil.

func (*CommandPaletteModel) SelectedTask

func (m *CommandPaletteModel) SelectedTask() *db.Task

SelectedTask returns the selected task, or nil if cancelled.

func (*CommandPaletteModel) SetQuery

func (m *CommandPaletteModel) SetQuery(query string)

SetQuery types query into the search box, as if the user had.

func (*CommandPaletteModel) SetSize

func (m *CommandPaletteModel) SetSize(width, height int)

SetSize updates the command palette dimensions.

func (*CommandPaletteModel) Update

Update handles messages.

func (*CommandPaletteModel) View

func (m *CommandPaletteModel) View() string

View renders the command palette.

type ComposerContext

type ComposerContext struct {
	Project     string
	Environment string
	Provider    string
	Model       string
	Reasoning   string
	Permission  string
}

ComposerContext mirrors the row of chips the app shows around its composer.

type DebugColumn

type DebugColumn struct {
	Name   string      `json:"name"`
	Tasks  []DebugTask `json:"tasks"`
	Focus  bool        `json:"is_focused"`
	Status string      `json:"status"`
}

type DebugDashboard

type DebugDashboard struct {
	Columns       []DebugColumn `json:"columns"`
	FocusedColumn int           `json:"focused_column"`
	SelectedTask  int64         `json:"selected_task_id,omitempty"`
}

type DebugDetail

type DebugDetail struct {
	TaskID   int64  `json:"task_id"`
	Title    string `json:"title"`
	Status   string `json:"status"`
	Project  string `json:"project"`
	Focused  bool   `json:"is_focused"`
	Logs     int    `json:"log_count"`
	HasPanes bool   `json:"has_panes"`
	// Scroll state, so thread navigation can be checked without a terminal.
	ScrollOffset   int  `json:"scroll_offset"`
	ScrollTotal    int  `json:"scroll_total"`
	AtBottom       bool `json:"at_bottom"`
	StickyBottom   bool `json:"sticky_bottom"`
	MessageAnchors int  `json:"message_anchors"`
	ComposerFocus  bool `json:"composer_focused"`
	// PaneError is the user-visible pane message (empty when panes are healthy).
	PaneError string `json:"pane_error,omitempty"`
}

type DebugFilter

type DebugFilter struct {
	Active bool   `json:"active"`
	Text   string `json:"text"`
}

type DebugForm

type DebugForm struct {
	Title        string            `json:"title"`
	Fields       map[string]string `json:"fields"`
	FocusedField string            `json:"focused_field"`
	Project      string            `json:"project"`
	Type         string            `json:"type"`
	Executor     string            `json:"executor"`
}

type DebugModals

type DebugModals struct {
	ActiveConfirm string `json:"active_confirm,omitempty"` // "delete", "close", "archive", "quit", etc.
}

type DebugNotification

type DebugNotification struct {
	Message string `json:"message"`
	TaskID  int64  `json:"task_id,omitempty"`
}

type DebugSize

type DebugSize struct {
	Width  int `json:"width"`
	Height int `json:"height"`
}

type DebugState

type DebugState struct {
	View         string             `json:"view"`
	Size         DebugSize          `json:"size"`
	Dashboard    *DebugDashboard    `json:"dashboard,omitempty"`
	Detail       *DebugDetail       `json:"detail,omitempty"`
	Form         *DebugForm         `json:"form,omitempty"`
	Modals       *DebugModals       `json:"modals,omitempty"`
	Filter       *DebugFilter       `json:"filter,omitempty"`
	Notification *DebugNotification `json:"notification,omitempty"`
	Timestamp    time.Time          `json:"timestamp"`
}

DebugState represents the application state as a "Text DOM" for AI agents.

type DebugTask

type DebugTask struct {
	ID       int64  `json:"id"`
	Title    string `json:"title"`
	Status   string `json:"status"`
	Project  string `json:"project"`
	Selected bool   `json:"is_selected"`
	Pinned   bool   `json:"is_pinned,omitempty"`
}

type DetailModel

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

DetailModel represents the task detail view.

func NewDetailModel

func NewDetailModel(t *db.Task, database *db.DB, exec *executor.Executor, width, height int, focusExecutor bool) (*DetailModel, tea.Cmd)

NewDetailModel creates a new detail model. Returns the model and an optional command for async pane setup. If focusExecutor is true, the executor pane will be focused after panes are joined.

func (*DetailModel) AcceptComposerMenu

func (m *DetailModel) AcceptComposerMenu() bool

AcceptComposerMenu inserts the highlighted completion.

func (*DetailModel) AttachFile

func (m *DetailModel) AttachFile(path string)

AttachFile stages a file that did not come from the message text, such as an image pasted from the clipboard.

func (*DetailModel) AwayFromBottom

func (m *DetailModel) AwayFromBottom() bool

AwayFromBottom reports whether the reader has scrolled back, which is when the jump-to-latest affordance is worth showing.

func (*DetailModel) BlurComposer

func (m *DetailModel) BlurComposer()

BlurComposer returns to single-key browsing.

func (*DetailModel) ClaudePaneID

func (m *DetailModel) ClaudePaneID() string

ClaudePaneID returns the tmux pane ID where Claude is running.

func (*DetailModel) Cleanup

func (m *DetailModel) Cleanup()

Cleanup should be called when leaving detail view. It saves the layout as the new default and closes the view; the task's panes stay in the daemon.

func (*DetailModel) CleanupWithoutSaving

func (m *DetailModel) CleanupWithoutSaving()

CleanupWithoutSaving cleans up panes without saving the height. Use this during task transitions (prev/next) to avoid rounding errors that accumulate with each transition and cause the pane to shrink.

func (*DetailModel) ClearComposerAttachments

func (m *DetailModel) ClearComposerAttachments()

ClearComposerAttachments drops staged files without sending them.

func (*DetailModel) ClearPaneHalt

func (m *DetailModel) ClearPaneHalt()

ClearPaneHalt lifts a halt after the user has fixed what caused it, and lets the task spawn again. Only user recovery calls this: nothing in the automatic paths may clear a halt, or the loop comes back.

func (*DetailModel) ClearPaneState

func (m *DetailModel) ClearPaneState()

ClearPaneState clears the cached pane state without breaking panes. Use this when the tmux window has been recreated externally (e.g., dangerous mode toggle).

func (*DetailModel) CloseComposerMenu

func (m *DetailModel) CloseComposerMenu()

CloseComposerMenu dismisses the completions without inserting one.

func (*DetailModel) ComposerFocused

func (m *DetailModel) ComposerFocused() bool

ComposerFocused reports whether keystrokes belong to the message box rather than to the board's single-key actions.

func (*DetailModel) ComposerHeight

func (m *DetailModel) ComposerHeight() int

ComposerHeight is what the composer currently occupies, so the view above it can be sized and a click can be attributed to it.

func (*DetailModel) ComposerMenuOpen

func (m *DetailModel) ComposerMenuOpen() bool

ComposerMenuOpen reports whether the slash menu is showing, which changes what enter and the arrow keys mean.

func (*DetailModel) ComposerPickerOpen

func (m *DetailModel) ComposerPickerOpen() bool

ComposerPickerOpen reports whether the open menu changes the composer rather than the message.

func (*DetailModel) ComposerSelection

func (m *DetailModel) ComposerSelection() (string, string)

ComposerSelection reports the overrides chosen for the next message.

func (*DetailModel) FocusComposer

func (m *DetailModel) FocusComposer()

FocusComposer puts the cursor in the message box.

func (*DetailModel) GetServerURL

func (m *DetailModel) GetServerURL() string

GetServerURL returns the server URL if a server is listening on the task's port.

func (*DetailModel) HandleLogsLoaded

func (m *DetailModel) HandleLogsLoaded(msg logsLoadedMsg)

HandleLogsLoaded processes the result of async log loading.

func (*DetailModel) HasRunningShellProcess

func (m *DetailModel) HasRunningShellProcess() bool

HasRunningShellProcess returns true if the shell pane has a running process. This shells out to tmux, so it must NOT be called from the per-frame render path. Refresh() polls it on a throttle and caches the result in hasRunningShellProc, which renderHeader() reads instead.

func (*DetailModel) InFeedbackMode

func (m *DetailModel) InFeedbackMode() bool

InFeedbackMode returns false - use tmux pane for interaction.

func (*DetailModel) IsFocused

func (m *DetailModel) IsFocused() bool

IsFocused returns true if the detail pane is the active tmux pane.

func (*DetailModel) IsShellPaneHidden

func (m *DetailModel) IsShellPaneHidden() bool

IsShellPaneHidden returns true if the shell pane is currently hidden.

func (*DetailModel) JumpMessage

func (m *DetailModel) JumpMessage(direction int)

JumpMessage moves the viewport to the previous or next message boundary, which is what a reader means by "back one" in a conversation — a line at a time is the wrong unit when a single reply can be forty lines.

func (*DetailModel) MessageAtViewport

func (m *DetailModel) MessageAtViewport() (*db.TaskLog, bool)

MessageAtViewport is the message the reader is looking at: the last one that starts at or above the top of the pane.

func (*DetailModel) MoveComposerMenu

func (m *DetailModel) MoveComposerMenu(delta int)

MoveComposerMenu changes the highlighted completion.

func (*DetailModel) OpenComposerPicker

func (m *DetailModel) OpenComposerPicker(kind MenuKind, items []SlashItem, current string)

OpenComposerPicker shows the model or permission chooser.

func (*DetailModel) PaneSetupHalted

func (m *DetailModel) PaneSetupHalted() string

PaneSetupHalted returns the reason pane setup stopped and is not retrying, or "" when the view is operating normally.

func (*DetailModel) QuoteIntoComposer

func (m *DetailModel) QuoteIntoComposer(log *db.TaskLog)

QuoteIntoComposer puts a message into the message box as a quote, which is what the app's "add to chat" does.

func (*DetailModel) Refresh

func (m *DetailModel) Refresh() tea.Cmd

Refresh reloads task and logs from database. Returns a tea.Cmd if async work (like log loading) needs to happen.

func (*DetailModel) RefreshPanesCmd

func (m *DetailModel) RefreshPanesCmd() tea.Cmd

RefreshPanesCmd returns a command to refresh the tmux panes. Use this after ClearPaneState() to rejoin panes to a recreated window.

func (*DetailModel) ResumeSession

func (m *DetailModel) ResumeSession() tea.Cmd

ResumeSession relaunches the closed session (with --resume) and joins its panes, as reopening the task would. The caller restarts the idle clock so the sweep does not suspend it again a minute later.

func (*DetailModel) ScrollLines

func (m *DetailModel) ScrollLines(delta int)

ScrollLines moves the conversation by lines and updates whether it is still following new messages.

func (*DetailModel) ScrollToBottom

func (m *DetailModel) ScrollToBottom()

ScrollToBottom jumps to the newest message and re-arms following it.

func (*DetailModel) SessionClosed

func (m *DetailModel) SessionClosed() bool

SessionClosed reports whether a blocked task's session closed while this view was open — usually the idle sweep — which is when enter resumes it.

func (*DetailModel) SetComposerError

func (m *DetailModel) SetComposerError(message string)

SetComposerError shows why a send failed, in place of the hint.

func (*DetailModel) SetComposerOverride

func (m *DetailModel) SetComposerOverride(model, permission string)

SetComposerOverride records what the next message will use, and shows it on the chip so the choice is visible before it is sent.

func (*DetailModel) SetPRInfo

func (m *DetailModel) SetPRInfo(prInfo *github.PRInfo)

SetPRInfo sets the PR info for this task. Every task reload hands the stored state back in, so an unchanged value is a no-op rather than a re-render.

func (*DetailModel) SetPosition

func (m *DetailModel) SetPosition(position, total int)

SetPosition updates the task's position in its column.

func (*DetailModel) SetSize

func (m *DetailModel) SetSize(width, height int)

SetSize updates the viewport size.

func (*DetailModel) StartRelatedTasksLoad

func (m *DetailModel) StartRelatedTasksLoad() tea.Cmd

StartRelatedTasksLoad starts loading related tasks from QMD if available. Returns a tea.Cmd that can be batched with other commands.

func (*DetailModel) StartTmuxTicker

func (m *DetailModel) StartTmuxTicker() tea.Cmd

StartTmuxTicker is no longer needed - real tmux pane handles display.

func (*DetailModel) TakeComposerAttachments

func (m *DetailModel) TakeComposerAttachments() []Attachment

TakeComposerAttachments returns and clears the files staged for sending.

func (*DetailModel) TakeComposerMessage

func (m *DetailModel) TakeComposerMessage() string

TakeComposerMessage returns and clears whatever has been typed.

func (*DetailModel) TakeComposerPick

func (m *DetailModel) TakeComposerPick() (MenuKind, string, bool)

TakeComposerPick returns what the picker chose.

func (*DetailModel) Task

func (m *DetailModel) Task() *db.Task

Task returns the current task.

func (*DetailModel) ToggleHelpExpanded

func (m *DetailModel) ToggleHelpExpanded()

ToggleHelpExpanded flips the footer help row between the collapsed (primary actions + '?') and expanded (all actions) states.

func (*DetailModel) ToggleShellPane

func (m *DetailModel) ToggleShellPane() tea.Cmd

ToggleShellPane toggles the visibility of the shell pane. When hidden, the shell pane is moved to the daemon window and Claude expands to full width. When shown, the shell pane is rejoined from the daemon or a new one is created.

func (*DetailModel) Update

func (m *DetailModel) Update(msg tea.Msg) (*DetailModel, tea.Cmd)

Update handles messages.

func (*DetailModel) UpdateComposer

func (m *DetailModel) UpdateComposer(msg tea.Msg) tea.Cmd

UpdateComposer feeds a keystroke to the message box.

func (*DetailModel) UpdateTask

func (m *DetailModel) UpdateTask(t *db.Task) tea.Cmd

UpdateTask updates the task and refreshes the view. Returns a tea.Cmd if the executor changed and a switch is needed.

func (*DetailModel) View

func (m *DetailModel) View() string

View renders the detail view.

View runs on every Bubble Tea update while the detail view is open. The header and help are cheap (~12µs) and have many volatile inputs (spinner frame, PR info, badges, theme colours), so we render them every frame and fold their output into the cache signature — that captures every input they read without having to enumerate each one. The expensive parts — viewport.View() and the bordered box.Render(), together ~2ms and ~2.7MB per call — are skipped entirely when the signature is unchanged.

func (*DetailModel) WorktreeMissing

func (m *DetailModel) WorktreeMissing() bool

WorktreeMissing reports whether this view is halted because the task's recorded worktree is gone — the one halt with a one-key recovery.

type FileBrowserModel

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

FileBrowserModel is a directory browser for selecting paths.

func NewFileBrowserModel

func NewFileBrowserModel(startPath string, width, height int) *FileBrowserModel

NewFileBrowserModel creates a new file browser starting at the given path.

func (*FileBrowserModel) CurrentDir

func (m *FileBrowserModel) CurrentDir() string

CurrentDir returns the currently displayed directory.

func (*FileBrowserModel) Init

func (m *FileBrowserModel) Init() tea.Cmd

Init initializes the model.

func (*FileBrowserModel) OnCancel

func (m *FileBrowserModel) OnCancel(fn func())

OnCancel sets the callback for when browsing is cancelled.

func (*FileBrowserModel) OnSelect

func (m *FileBrowserModel) OnSelect(fn func(path string))

OnSelect sets the callback for when a path is selected.

func (*FileBrowserModel) SetSize

func (m *FileBrowserModel) SetSize(width, height int)

SetSize updates the browser size.

func (*FileBrowserModel) Update

func (m *FileBrowserModel) Update(msg tea.Msg) (*FileBrowserModel, tea.Cmd)

Update handles messages.

func (*FileBrowserModel) View

func (m *FileBrowserModel) View() string

View renders the file browser.

type FilterAutocompleteModel

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

FilterAutocompleteModel provides project name and host name autocomplete for the filter.

func NewFilterAutocompleteModel

func NewFilterAutocompleteModel(database *db.DB) *FilterAutocompleteModel

NewFilterAutocompleteModel creates a new filter autocomplete model.

func (*FilterAutocompleteModel) HasResults

func (m *FilterAutocompleteModel) HasResults() bool

func (*FilterAutocompleteModel) IsHostMode

func (m *FilterAutocompleteModel) IsHostMode() bool

IsHostMode reports whether the dropdown is completing a host rather than a project, so the caller knows which chip syntax to write back.

func (*FilterAutocompleteModel) MoveDown

func (m *FilterAutocompleteModel) MoveDown()

func (*FilterAutocompleteModel) MoveUp

func (m *FilterAutocompleteModel) MoveUp()

func (*FilterAutocompleteModel) Reset

func (m *FilterAutocompleteModel) Reset()

func (*FilterAutocompleteModel) Select

func (m *FilterAutocompleteModel) Select() string

Select returns the selected project or host name.

func (*FilterAutocompleteModel) SetHostQuery

func (m *FilterAutocompleteModel) SetHostQuery(query string)

SetHostQuery filters host names for the "@host" chip and resets selection.

The fleet has no central registry — a placement hook names whatever machines it likes — so the candidates are the hosts tasks have actually run on, plus "local" for the ones that ran here.

func (*FilterAutocompleteModel) SetQuery

func (m *FilterAutocompleteModel) SetQuery(query string)

SetQuery filters projects based on query and resets selection.

func (*FilterAutocompleteModel) View

func (m *FilterAutocompleteModel) View() string

View renders the dropdown.

type FolderPickerModel

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

FolderPickerModel is a fuzzy, type-to-search folder picker built on bubbles/list. It seeds the list with likely project roots; typing drives the list's built-in fuzzy filter (matched runes are underlined), → descends into the selected folder, and enter picks it.

func NewFolderPickerModel

func NewFolderPickerModel(width, height int) *FolderPickerModel

NewFolderPickerModel seeds the picker from common project roots.

func (*FolderPickerModel) Init

func (m *FolderPickerModel) Init() tea.Cmd

func (*FolderPickerModel) SetSize

func (m *FolderPickerModel) SetSize(w, h int)

func (*FolderPickerModel) Update

func (m *FolderPickerModel) Update(msg tea.Msg) (*FolderPickerModel, tea.Cmd)

func (*FolderPickerModel) View

func (m *FolderPickerModel) View() string

type FormField

type FormField int

FormField represents the currently focused field.

const (
	FieldProject FormField = iota // Project first for immediate context
	FieldTitle
	FieldBody
	FieldAttachments // Moved after body for proximity - drag works from any field
	FieldType
	FieldExecutor
	FieldHost // Only shown when a placement plugin offers a choice of machines
	FieldEffort
	FieldModel
	FieldPermission
	FieldCount
)

type FormModel

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

FormModel represents the new task form.

func NewEditFormModel

func NewEditFormModel(database *db.DB, task *db.Task, width, height int, availableExecutors []string) *FormModel

NewEditFormModel creates a form model pre-populated with an existing task's data for editing.

func NewFormModel

func NewFormModel(database *db.DB, width, height int, workingDir string, availableExecutors []string) *FormModel

NewFormModel creates a new form model.

func (*FormModel) ApplyTo

func (m *FormModel) ApplyTo(task *db.Task)

ApplyTo overlays the form's editable fields onto task, leaving every other persisted column untouched. Editing a task in place uses this so that saving never resets fields the form doesn't expose (session IDs, pin state, tags, source branch, PR info, ...). See issue #560. Permission mode IS exposed by the form, so it is applied here (and kept in sync with DangerousMode).

func (*FormModel) GetAttachment

func (m *FormModel) GetAttachment() string

GetAttachment returns the first attachment for backwards compatibility.

func (*FormModel) GetAttachments

func (m *FormModel) GetAttachments() []string

GetAttachments returns the parsed attachment file paths.

func (*FormModel) GetDBTask

func (m *FormModel) GetDBTask() *db.Task

GetDBTask returns a db.Task from the form values.

func (*FormModel) Init

func (m *FormModel) Init() tea.Cmd

Init initializes the form.

func (*FormModel) OriginalProject

func (m *FormModel) OriginalProject() string

OriginalProject returns the original project when editing.

func (*FormModel) Pipeline

func (m *FormModel) Pipeline() string

Pipeline returns the selected kind's name when it is a workflow (has steps), or "" when the selected kind is a plain type — i.e. an ordinary single task. When non-empty, the caller builds a workflow (see internal/pipeline) using the form's task as the goal carrier rather than creating a single task.

func (*FormModel) PlacementChoice

func (m *FormModel) PlacementChoice() (target, workDir string)

PlacementChoice returns the host the user picked for a new task: the target to record ("" = leave it to the resolver, "local" = this machine) and that project's directory on it.

func (*FormModel) ProjectChanged

func (m *FormModel) ProjectChanged() bool

ProjectChanged returns true if the project was changed during editing. Only relevant when isEdit is true.

func (*FormModel) SetQueue

func (m *FormModel) SetQueue(queue bool)

SetQueue sets whether to queue the task.

func (*FormModel) SetSize

func (m *FormModel) SetSize(width, height int)

SetSize updates the form dimensions for window resize handling.

func (*FormModel) Update

func (m *FormModel) Update(msg tea.Msg) (tea.Model, tea.Cmd)

Update handles messages.

func (*FormModel) View

func (m *FormModel) View() string

View renders the form.

type KanbanBoard

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

KanbanBoard manages the kanban board state.

func NewKanbanBoard

func NewKanbanBoard(width, height int) *KanbanBoard

NewKanbanBoard creates a new kanban board.

func (*KanbanBoard) ClearOriginColumn

func (k *KanbanBoard) ClearOriginColumn()

ClearOriginColumn clears the origin column, returning to normal navigation.

func (*KanbanBoard) ColumnCount

func (k *KanbanBoard) ColumnCount() int

ColumnCount returns the number of columns.

func (*KanbanBoard) FocusColumn

func (k *KanbanBoard) FocusColumn(colIdx int)

FocusColumn moves selection to a specific column by index. If the column is collapsed, it will be uncollapsed first.

func (*KanbanBoard) GetOpenBlockerCount

func (k *KanbanBoard) GetOpenBlockerCount(taskID int64) int

GetOpenBlockerCount returns the number of open blockers for a task. Returns 0 if the task has no blockers.

func (*KanbanBoard) GetPRInfo

func (k *KanbanBoard) GetPRInfo(taskID int64) *github.PRInfo

GetPRInfo returns the PR info for a task.

func (*KanbanBoard) GetTaskPosition

func (k *KanbanBoard) GetTaskPosition() (int, int)

GetTaskPosition returns the position of the currently selected task in its column. Returns (position, total) where position is 1-indexed, or (0, 0) if no task is selected.

func (*KanbanBoard) HandleClick

func (k *KanbanBoard) HandleClick(x, y int) *db.Task

HandleClick handles a mouse click at the given coordinates. Returns the clicked task if a task card was clicked, nil otherwise. Also updates the selection to the clicked task.

func (*KanbanBoard) HasNextTask

func (k *KanbanBoard) HasNextTask() bool

HasNextTask returns true if there is a next task in the current column. Returns false if already at the last task or no tasks exist.

func (*KanbanBoard) HasOriginColumn

func (k *KanbanBoard) HasOriginColumn() bool

HasOriginColumn returns true if an origin column is set.

func (*KanbanBoard) HasPrevTask

func (k *KanbanBoard) HasPrevTask() bool

HasPrevTask returns true if there is a previous task in the current column. Returns false if already at the first task or no tasks exist.

func (*KanbanBoard) HasRunningProcess

func (k *KanbanBoard) HasRunningProcess(taskID int64) bool

HasRunningProcess returns true if the task has a running shell process.

func (*KanbanBoard) IsBlockedByDeps

func (k *KanbanBoard) IsBlockedByDeps(taskID int64) bool

IsBlockedByDeps returns true if the task is blocked by dependencies.

func (*KanbanBoard) IsColumnCollapsed

func (k *KanbanBoard) IsColumnCollapsed(colIdx int) bool

IsColumnCollapsed returns true if the column at the given index is collapsed.

func (*KanbanBoard) IsEmpty

func (k *KanbanBoard) IsEmpty() bool

IsEmpty returns true if all columns have no tasks.

func (*KanbanBoard) IsListMode

func (k *KanbanBoard) IsListMode() bool

IsListMode reports whether the board renders as a flat list.

func (*KanbanBoard) IsMobileMode

func (k *KanbanBoard) IsMobileMode() bool

IsMobileMode returns true if the board should show single-column mode.

func (*KanbanBoard) JumpToPinned

func (k *KanbanBoard) JumpToPinned()

JumpToPinned moves selection to the first pinned task in the current column. If there are no pinned tasks, moves to the top of the column.

func (*KanbanBoard) JumpToUnpinned

func (k *KanbanBoard) JumpToUnpinned()

JumpToUnpinned moves selection to the first unpinned task in the current column. If all tasks are pinned or there are no tasks, stays at current position.

func (*KanbanBoard) ListOptions

func (k *KanbanBoard) ListOptions() ListOptions

ListOptions returns the current arrangement.

func (*KanbanBoard) ListTasks

func (k *KanbanBoard) ListTasks() []*db.Task

ListTasks returns the tasks in list order. Exported for tests and for the header counts.

func (*KanbanBoard) MoveDown

func (k *KanbanBoard) MoveDown()

MoveDown moves selection down within the current column. If at the bottom, wraps around to the top.

func (*KanbanBoard) MoveLeft

func (k *KanbanBoard) MoveLeft()

MoveLeft moves selection to the left column, skipping collapsed columns.

func (*KanbanBoard) MoveRight

func (k *KanbanBoard) MoveRight()

MoveRight moves selection to the right column, skipping collapsed columns.

func (*KanbanBoard) MoveUp

func (k *KanbanBoard) MoveUp()

MoveUp moves selection up within the current column. If at the top, wraps around to the bottom.

func (*KanbanBoard) NeedsInput

func (k *KanbanBoard) NeedsInput(taskID int64) bool

NeedsInput returns true if the task has an active input notification.

func (*KanbanBoard) NeedsInputCount

func (k *KanbanBoard) NeedsInputCount() int

NeedsInputCount returns how many tasks are waiting on the user.

func (*KanbanBoard) RefreshTheme

func (k *KanbanBoard) RefreshTheme()

RefreshTheme updates column colors after a theme change.

func (*KanbanBoard) RunningTaskCount

func (k *KanbanBoard) RunningTaskCount() int

RunningTaskCount returns how many tasks are currently processing.

func (*KanbanBoard) SelectTask

func (k *KanbanBoard) SelectTask(id int64) bool

SelectTask selects a task by ID.

func (*KanbanBoard) SelectedTask

func (k *KanbanBoard) SelectedTask() *db.Task

SelectedTask returns the currently selected task.

func (*KanbanBoard) SetBlockedByDeps

func (k *KanbanBoard) SetBlockedByDeps(blockedByDeps map[int64]int)

func (*KanbanBoard) SetHiddenDoneCount

func (k *KanbanBoard) SetHiddenDoneCount(count int)

SetHiddenDoneCount sets the number of done tasks not shown in the kanban.

func (*KanbanBoard) SetLatestActivity

func (k *KanbanBoard) SetLatestActivity(activity map[int64]*db.TaskLog)

SetLatestActivity updates the most-recent-log-per-task map used to render activity lines.

func (*KanbanBoard) SetListMode

func (k *KanbanBoard) SetListMode(on bool)

SetListMode switches between the kanban and the flat list, keeping the selected task selected across the switch.

func (*KanbanBoard) SetListOptions

func (k *KanbanBoard) SetListOptions(opts ListOptions)

SetListOptions changes how the list is arranged and re-sorts it, keeping the selected task selected across the rearrangement.

func (*KanbanBoard) SetListTitle

func (k *KanbanBoard) SetListTitle(title string)

SetListTitle sets the label shown in the list header — the active saved view or filter, so the list always says what it is a list of.

func (*KanbanBoard) SetOriginColumn

func (k *KanbanBoard) SetOriginColumn()

SetOriginColumn sets the origin column for detail view navigation. This preserves the column context even if the task moves to a different column.

func (*KanbanBoard) SetPRInfo

func (k *KanbanBoard) SetPRInfo(taskID int64, info *github.PRInfo)

SetPRInfo updates the PR info for a task.

func (*KanbanBoard) SetRunningProcesses

func (k *KanbanBoard) SetRunningProcesses(running map[int64]bool)

SetRunningProcesses updates the map of tasks with running shell processes.

func (*KanbanBoard) SetSize

func (k *KanbanBoard) SetSize(width, height int)

SetSize updates the board dimensions.

func (*KanbanBoard) SetTasks

func (k *KanbanBoard) SetTasks(tasks []*db.Task)

SetTasks updates the tasks in the kanban board.

func (*KanbanBoard) SetTasksNeedingInput

func (k *KanbanBoard) SetTasksNeedingInput(needsInput map[int64]bool)

SetTasksNeedingInput updates the map of tasks waiting for user input.

func (*KanbanBoard) SetWorkflowGroups

func (k *KanbanBoard) SetWorkflowGroups(groups map[int64]*pipeline.Group)

SetBlockedByDeps updates the map of tasks blocked by dependencies. The map contains task ID -> number of open blockers. SetWorkflowGroups records the collapsed workflow groups, keyed by the lead task whose card represents the whole workflow on the board.

func (*KanbanBoard) ToggleColumnCollapse

func (k *KanbanBoard) ToggleColumnCollapse(colIdx int)

ToggleColumnCollapse toggles the collapsed state of a column. Cannot collapse the currently selected column.

func (*KanbanBoard) TotalTaskCount

func (k *KanbanBoard) TotalTaskCount() int

TotalTaskCount returns the total number of tasks across all columns.

func (*KanbanBoard) View

func (k *KanbanBoard) View() string

View renders the kanban board.

View is called on every Bubble Tea Update — including periodic ticks, mouse motion, and task events that don't change the board — so it is the single hottest path in the TUI. We compute a signature over every input that affects the rendered output and return the previously rendered string when nothing has changed, turning idle re-renders into a cheap hash instead of a full lipgloss layout pass.

type KanbanColumn

type KanbanColumn struct {
	Title  string
	Status string // The status this column represents
	Tasks  []*db.Task
	Color  lipgloss.Color
	Icon   string // Visual icon for the column
}

KanbanColumn represents a column in the kanban board.

type KeyMap

type KeyMap struct {
	Left               key.Binding
	Right              key.Binding
	Up                 key.Binding
	Down               key.Binding
	Enter              key.Binding
	Back               key.Binding
	New                key.Binding
	Edit               key.Binding
	Queue              key.Binding
	Retry              key.Binding
	Close              key.Binding
	Archive            key.Binding
	Delete             key.Binding
	Refresh            key.Binding
	Settings           key.Binding
	Routines           key.Binding
	Plugins            key.Binding
	Help               key.Binding
	Quit               key.Binding
	ChangeStatus       key.Binding
	PlaceTask          key.Binding
	CommandPalette     key.Binding
	ToggleDangerous    key.Binding
	QueueDangerous     key.Binding
	TogglePin          key.Binding
	Filter             key.Binding
	OpenWorktree       key.Binding
	ToggleShellPane    key.Binding
	JumpToNotification key.Binding
	Actions            key.Binding
	// Column focus shortcuts
	FocusBacklog    key.Binding
	FocusInProgress key.Binding
	FocusBlocked    key.Binding
	FocusDone       key.Binding
	// Jump to pinned/unpinned tasks
	JumpToPinned   key.Binding
	JumpToUnpinned key.Binding
	// Column collapse
	CollapseBacklog key.Binding
	CollapseDone    key.Binding
	// Open browser
	OpenBrowser key.Binding
	// Open PR
	OpenPR key.Binding
	// Rebuild a task's missing worktree (detail view recovery)
	RecreateWorktree key.Binding
	ResumeSession    key.Binding
	// Board display: kanban columns vs a flat list, and the saved-view picker
	ToggleListView key.Binding
	SavedViews     key.Binding
	// How the list is arranged: group by, sort
	ListOptions key.Binding
	// Answer whatever the selected task is waiting on
	Approvals key.Binding
	// Put the cursor in the thread's message box
	FocusComposer key.Binding
}

KeyMap defines key bindings.

func ApplyKeybindingsConfig

func ApplyKeybindingsConfig(km KeyMap, cfg *config.KeybindingsConfig) KeyMap

ApplyKeybindingsConfig applies custom keybindings from a config to the KeyMap. Only non-nil config values override the defaults.

func DefaultKeyMap

func DefaultKeyMap() KeyMap

DefaultKeyMap returns the default key bindings.

func LoadKeyMap

func LoadKeyMap() KeyMap

LoadKeyMap loads the KeyMap from the config file, falling back to defaults.

func (KeyMap) FullHelp

func (k KeyMap) FullHelp() [][]key.Binding

FullHelp returns keybindings for the expanded help view.

func (KeyMap) ShortHelp

func (k KeyMap) ShortHelp() []key.Binding

ShortHelp returns key bindings to show in the mini help.

type ListGroupBy

type ListGroupBy string

ListGroupBy is the field the list breaks into sections on.

const (
	GroupByStatus  ListGroupBy = "status"
	GroupByProject ListGroupBy = "project"
	GroupByNone    ListGroupBy = "none"
)

type ListOptions

type ListOptions struct {
	GroupBy ListGroupBy
	Sort    ListSort
}

ListOptions is the arrangement of the list view.

func DefaultListOptions

func DefaultListOptions() ListOptions

DefaultListOptions is what a board that has never been configured uses.

func LoadListOptions

func LoadListOptions(get func(string) (string, error)) ListOptions

LoadListOptions reads the arrangement from settings.

func (ListOptions) Arrange

func (o ListOptions) Arrange(tasks []*db.Task) []*db.Task

Arrange returns the tasks in display order for these options.

func (ListOptions) Normalize

func (o ListOptions) Normalize() ListOptions

Normalize replaces unknown values with the defaults, so a hand-edited setting can never leave the list in a state with no renderer.

func (ListOptions) Save

func (o ListOptions) Save(set func(string, string) error)

Save writes the arrangement back. Errors are ignored: a lost settings write costs a preference next launch, never the current session.

func (ListOptions) Summary

func (o ListOptions) Summary() string

Summary is the one-line widget shown under the list header.

type ListOptionsModel

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

ListOptionsModel is the modal behind `O`: three rows, left/right cycles the value under the cursor, and the list re-renders live so the effect of a choice is visible before it is committed.

func NewListOptionsModel

func NewListOptionsModel(opts ListOptions, width, height int) *ListOptionsModel

NewListOptionsModel opens the widget on the current arrangement.

func (*ListOptionsModel) Init

func (m *ListOptionsModel) Init() tea.Cmd

Init implements tea.Model.

func (*ListOptionsModel) IsCancelled

func (m *ListOptionsModel) IsCancelled() bool

IsCancelled reports whether the user backed out.

func (*ListOptionsModel) IsDone

func (m *ListOptionsModel) IsDone() bool

IsDone reports whether the user committed.

func (*ListOptionsModel) Options

func (m *ListOptionsModel) Options() ListOptions

Options returns the arrangement as it currently stands, including while the user is still cycling — the caller renders the list behind the modal with it.

func (*ListOptionsModel) SetSize

func (m *ListOptionsModel) SetSize(width, height int)

SetSize updates dimensions.

func (*ListOptionsModel) Update

func (m *ListOptionsModel) Update(msg tea.Msg) (*ListOptionsModel, tea.Cmd)

Update handles key input.

func (*ListOptionsModel) View

func (m *ListOptionsModel) View() string

View renders the modal.

type ListSort

type ListSort string

ListSort orders tasks within each section.

const (
	// SortUrgency is "what needs me first": running, blocked, queued, backlog,
	// done — then most recently touched inside each of those.
	SortUrgency ListSort = "urgency"
	SortUpdated ListSort = "updated"
	SortCreated ListSort = "created"
	SortTitle   ListSort = "title"
)
type MenuKind string

MenuKind is which trigger opened the completion menu.

const (
	// MenuSkills is "/", which bb declares in each provider's composerActions.
	MenuSkills MenuKind = "skills"
	// MenuMentions is "@", which the app offers for files, folders, sections
	// and threads.
	MenuMentions MenuKind = "mentions"
	// MenuModels picks which model answers the next message.
	MenuModels MenuKind = "models"
	// MenuPermissions picks how much that model is allowed to do.
	MenuPermissions MenuKind = "permissions"
)

type MessageAnchor

type MessageAnchor struct {
	Line     int
	LogIndex int
}

renderConversation turns the thread's rows into something readable: what was said is given room and colour, what was done is collapsed to one dim line each, so a long run of tool calls never hides the reply after it. MessageAnchor is where a message starts in the rendered conversation, and which log row it came from, so a key can act on the message a reader is looking at rather than on a line number.

type PendingApproval

type PendingApproval struct {
	ID      string
	Kind    string
	Summary string
	Prompt  string
	Choices []ApprovalChoice
}

PendingApproval is one thing a thread is waiting on, flattened for display.

type PluginActionItem

type PluginActionItem struct {
	Plugin hooks.Plugin
	Action hooks.Action
}

PluginActionItem pairs a plugin with one of its actions for display/execution.

type PluginBrowserModel

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

PluginBrowserModel is the searchable plugin catalog view.

func NewPluginBrowserModel

func NewPluginBrowserModel(width, height int) *PluginBrowserModel

NewPluginBrowserModel creates the browser and starts the catalog load.

func (*PluginBrowserModel) Done

func (m *PluginBrowserModel) Done() bool

Done reports whether the view asked to close.

func (*PluginBrowserModel) Init

func (m *PluginBrowserModel) Init() tea.Cmd

Init starts the first catalog load.

func (*PluginBrowserModel) Rows

func (m *PluginBrowserModel) Rows() []PluginRow

Rows returns the currently filtered rows (exported for tests).

func (*PluginBrowserModel) Selected

func (m *PluginBrowserModel) Selected() (PluginRow, bool)

Selected returns the highlighted row.

func (*PluginBrowserModel) SetQuery

func (m *PluginBrowserModel) SetQuery(q string)

SetQuery sets the search text and refilters — used when the view is opened with a query already in hand (e.g. from the command palette).

func (*PluginBrowserModel) SetSize

func (m *PluginBrowserModel) SetSize(width, height int)

SetSize updates the view dimensions.

func (*PluginBrowserModel) Update

func (m *PluginBrowserModel) Update(msg tea.Msg) (*PluginBrowserModel, tea.Cmd)

Update handles input and the async load/install/remove results.

func (*PluginBrowserModel) View

func (m *PluginBrowserModel) View() string

View renders the browser: search box, scope tabs, the result list, a detail panel for the highlighted row, and the key hints.

type PluginRow

type PluginRow struct {
	Entry     *registry.Entry
	Installed *hooks.Plugin
	// Dir is the installed directory's base name, when installed.
	Dir string
}

PluginRow is one line in the browser: a catalog entry, an installed plugin, or both when an installed plugin is also catalogued.

func (PluginRow) Category

func (r PluginRow) Category() string

Category is the catalog category, or "installed" for an uncatalogued plugin.

func (PluginRow) Description

func (r PluginRow) Description() string

Description prefers the catalog copy (written for a reader deciding whether to install) over the manifest's, which is often a note to the plugin's own author.

func (PluginRow) ID

func (r PluginRow) ID() string

ID is the row's stable handle: the catalog ID when there is one, else the installed plugin's manifest name.

func (PluginRow) IsInstalled

func (r PluginRow) IsInstalled() bool

IsInstalled reports whether the row is on disk.

func (PluginRow) Title

func (r PluginRow) Title() string

Title is what the row shows as its name.

type PluginScope

type PluginScope int

PluginScope is which slice of the list is shown.

const (
	// ScopeAll shows the catalog plus anything installed from outside it.
	ScopeAll PluginScope = iota
	// ScopeInstalled shows only what is on disk.
	ScopeInstalled
	// ScopeAvailable shows only catalog entries that are not installed yet.
	ScopeAvailable
)

func (PluginScope) String

func (s PluginScope) String() string

type ReloadState

type ReloadState struct {
	TaskID int64  `json:"task_id"`
	Detail bool   `json:"detail"`
	Filter string `json:"filter"`
}

ReloadState contains navigation state only. Forms are never interrupted.

type RepoCloneModel

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

RepoCloneModel confirms where a pasted repo URL should land, clones it, and reports the local path. It never creates a project itself — that stays with the folder-picked path.

func NewRepoCloneModel

func NewRepoCloneModel(ref github.RepoRef, width, height int) *RepoCloneModel

NewRepoCloneModel prepares a clone of ref into the default clone root.

func (*RepoCloneModel) Cancel

func (m *RepoCloneModel) Cancel() bool

Cancel stops an in-flight clone. It reports whether there was one — the app uses that to decide whether esc means "stop cloning" or "go back".

func (*RepoCloneModel) Init

func (m *RepoCloneModel) Init() tea.Cmd

func (*RepoCloneModel) SetSize

func (m *RepoCloneModel) SetSize(w, h int)

func (*RepoCloneModel) Update

func (m *RepoCloneModel) Update(msg tea.Msg) (*RepoCloneModel, tea.Cmd)

func (*RepoCloneModel) View

func (m *RepoCloneModel) View() string

type RetryModel

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

RetryModel handles retrying a blocked/failed task with optional feedback.

func NewRetryModel

func NewRetryModel(task *db.Task, database *db.DB, width, height int) *RetryModel

NewRetryModel creates a new retry model.

func (*RetryModel) GetAttachment

func (m *RetryModel) GetAttachment() string

GetAttachment returns the first attachment path.

func (*RetryModel) GetAttachments

func (m *RetryModel) GetAttachments() []string

GetAttachments returns all attachment paths.

func (*RetryModel) GetFeedback

func (m *RetryModel) GetFeedback() string

GetFeedback returns the feedback text.

func (*RetryModel) Init

func (m *RetryModel) Init() tea.Cmd

Init initializes the model.

func (*RetryModel) IsDangerous

func (m *RetryModel) IsDangerous() bool

IsDangerous returns whether the retry should execute in dangerous mode.

func (*RetryModel) SetSize

func (m *RetryModel) SetSize(width, height int)

SetSize updates the size.

func (*RetryModel) Update

func (m *RetryModel) Update(msg tea.Msg) (*RetryModel, tea.Cmd)

Update handles messages.

func (*RetryModel) View

func (m *RetryModel) View() string

View renders the retry view.

type RoutinesModel

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

RoutinesModel is a read-only fleet-health view over all routines: name, target project, model, enabled state, and last run outcome. Routines are a global namespace (a routine *targets* a project, it doesn't belong to one), so this view is global too — a dead routine must be visible even when its project hasn't been opened in weeks.

func NewRoutinesModel

func NewRoutinesModel(database *db.DB, width, height int) *RoutinesModel

NewRoutinesModel loads routine definitions and their latest runs.

func (*RoutinesModel) Init

func (m *RoutinesModel) Init() tea.Cmd

Init implements tea.Model.

func (*RoutinesModel) SetSize

func (m *RoutinesModel) SetSize(width, height int)

SetSize updates the view dimensions.

func (*RoutinesModel) Update

func (m *RoutinesModel) Update(msg tea.Msg) (*RoutinesModel, tea.Cmd)

Update implements tea.Model.

func (*RoutinesModel) View

func (m *RoutinesModel) View() string

View implements tea.Model.

type SettingsModel

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

SettingsModel represents the settings view.

func NewSettingsModel

func NewSettingsModel(database *db.DB, width, height int) *SettingsModel

NewSettingsModel creates a new settings model.

func (*SettingsModel) Init

func (m *SettingsModel) Init() tea.Cmd

Init initializes the model.

func (*SettingsModel) SetSize

func (m *SettingsModel) SetSize(width, height int)

SetSize updates the view size.

func (*SettingsModel) Update

func (m *SettingsModel) Update(msg tea.Msg) (*SettingsModel, tea.Cmd)

Update handles messages.

func (*SettingsModel) View

func (m *SettingsModel) View() string

View renders the settings view.

type SlashItem

type SlashItem struct {
	Name        string
	Description string
	Scope       string
}

SlashItem is one thing "/" can insert.

type StatusChoice

type StatusChoice struct {
	Value string
	Label string
}

StatusChoices, when set, replaces the list offered by the status picker. The four TaskYou statuses are states a queue moves work through; a bb thread moves itself, so the only ones a person can pick are the two that are really actions: stop it, or archive it.

type TaskRefAutocompleteModel

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

TaskRefAutocompleteModel provides inline autocomplete for task references (#123). It appears as a floating dropdown when the user types '#' in a text field.

func NewTaskRefAutocompleteModel

func NewTaskRefAutocompleteModel(database *db.DB, width int) *TaskRefAutocompleteModel

NewTaskRefAutocompleteModel creates a new task reference autocomplete model.

func (*TaskRefAutocompleteModel) Dismiss

func (m *TaskRefAutocompleteModel) Dismiss()

Dismiss closes the autocomplete without selecting.

func (*TaskRefAutocompleteModel) GetCursorPos

func (m *TaskRefAutocompleteModel) GetCursorPos() int

GetCursorPos returns the position where '#' was typed.

func (*TaskRefAutocompleteModel) GetQueryLength

func (m *TaskRefAutocompleteModel) GetQueryLength() int

GetQueryLength returns the length of the query (including #).

func (*TaskRefAutocompleteModel) GetReference

func (m *TaskRefAutocompleteModel) GetReference() string

GetReference returns the task reference string (e.g., "#123 (Task Title)") for the selected task.

func (*TaskRefAutocompleteModel) HasResults

func (m *TaskRefAutocompleteModel) HasResults() bool

HasResults returns true if there are matching tasks.

func (*TaskRefAutocompleteModel) IsDismissed

func (m *TaskRefAutocompleteModel) IsDismissed() bool

IsDismissed returns true if the autocomplete was dismissed.

func (*TaskRefAutocompleteModel) MoveDown

func (m *TaskRefAutocompleteModel) MoveDown()

MoveDown moves selection down.

func (*TaskRefAutocompleteModel) MoveUp

func (m *TaskRefAutocompleteModel) MoveUp()

MoveUp moves selection up.

func (*TaskRefAutocompleteModel) Reset

func (m *TaskRefAutocompleteModel) Reset()

Reset resets the autocomplete state.

func (*TaskRefAutocompleteModel) Select

func (m *TaskRefAutocompleteModel) Select()

Select confirms the current selection.

func (*TaskRefAutocompleteModel) SelectedTask

func (m *TaskRefAutocompleteModel) SelectedTask() *db.Task

SelectedTask returns the selected task, or nil if none selected.

func (*TaskRefAutocompleteModel) SetQuery

func (m *TaskRefAutocompleteModel) SetQuery(query string, cursorPos int)

SetQuery sets the search query and filters tasks.

func (*TaskRefAutocompleteModel) View

func (m *TaskRefAutocompleteModel) View() string

View renders the autocomplete dropdown.

type TerminalImageSupport

type TerminalImageSupport int

TerminalImageSupport is which inline-image protocol this terminal speaks, if any. Terminals disagree completely here, so it is detected rather than assumed: iTerm2 has its own OSC 1337, Kitty and Ghostty share a different one, and everything else can only be given a link.

const (
	ImagesUnsupported TerminalImageSupport = iota
	ImagesITerm2
	ImagesKitty
)

func DetectImageSupport

func DetectImageSupport() TerminalImageSupport

DetectImageSupport reports what the current terminal can draw. tmux blocks the passthrough these protocols need unless it is configured for it, so a tmux session is treated as unsupported rather than drawing garbage.

type Theme

type Theme struct {
	Name string `json:"name"`

	// Core colors
	Primary   string `json:"primary"`   // Main accent color (selections, highlights)
	Secondary string `json:"secondary"` // Secondary accent (links, tags)
	Muted     string `json:"muted"`     // Dimmed text, borders

	// Semantic colors
	Success string `json:"success"` // Success states, done
	Warning string `json:"warning"` // Warning states
	Error   string `json:"error"`   // Error states, blocked

	// Status colors
	InProgress string `json:"in_progress"` // Queued/processing tasks
	Done       string `json:"done"`        // Completed tasks
	Blocked    string `json:"blocked"`     // Blocked tasks
	Backlog    string `json:"backlog"`     // Backlog tasks

	// Card colors
	CardBg         string `json:"card_bg"`          // Selected card background
	CardFg         string `json:"card_fg"`          // Selected card foreground
	CardBorder     string `json:"card_border"`      // Card border
	CardBorderHi   string `json:"card_border_hi"`   // Highlighted card border
	ColumnBorder   string `json:"column_border"`    // Column border
	ColumnBorderHi string `json:"column_border_hi"` // Selected column border
}

Theme defines all colors used in the UI.

func CurrentTheme

func CurrentTheme() Theme

CurrentTheme returns the current theme.

type UILogger

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

UILogger provides file-based logging for the UI package. The TUI draws on stdout, so diagnostics cannot go there; they go to ui.log beside the database.

Three properties this file depends on, each learned the hard way:

  • It sits next to the database, so an isolated instance (a QA harness with its own WORKTREE_DB_PATH) writes to its own log. Sharing one file made a QA run's errors look like they came from the live TUI.
  • Every line carries the pid. Several ty processes append concurrently — TUIs, the daemon — and without it there is no way to tell who wrote what.
  • It rotates. Unrotated, this file reached 205MB and eight months of history.

func GetLogger

func GetLogger() *UILogger

GetLogger returns the singleton UI logger instance. Call CloseLogger() when the application exits.

func (*UILogger) Debug

func (l *UILogger) Debug(format string, args ...interface{})

Debug logs a debug message.

func (*UILogger) Error

func (l *UILogger) Error(format string, args ...interface{})

Error logs an error message.

func (*UILogger) Info

func (l *UILogger) Info(format string, args ...interface{})

Info logs an info message.

func (*UILogger) Warn

func (l *UILogger) Warn(format string, args ...interface{})

Warn logs a warning message.

type View

type View int

View represents the current view.

const (
	ViewDashboard View = iota
	ViewDetail
	ViewNewTask
	ViewNewTaskConfirm
	ViewEditTask
	ViewProjectChangeConfirm // Confirmation when changing a task's project
	ViewDeleteConfirm
	ViewCloseConfirm
	ViewArchiveConfirm
	ViewQuitConfirm
	ViewSettings
	ViewRetry
	ViewAttachments
	ViewChangeStatus
	ViewPlacement
	ViewCommandPalette
	ViewProjectDetectConfirm // Offer to create a project for the current git repo
	ViewWelcome              // first-run fork: set up a project vs start a task
	ViewFolderPicker         // fuzzy folder picker for "set up a project"
	ViewRoutines             // global routines fleet-health view
	ViewActionPicker         // modal list of plugin actions for the current task
	ViewApprovals            // modal answering what a thread is waiting on
	ViewPluginBrowser        // searchable plugin catalog: browse, install, remove
	ViewRepoClone            // clone a pasted repo URL, then continue as a folder
	ViewSavedViews           // modal list of saved filter views
	ViewListOptions          // modal for the list's grouping and sort
)

type ViewPickerModel

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

ViewPickerModel is the modal behind `V`: the saved views, plus the two operations that keep them useful — save the filter you are looking at, and delete one you no longer want.

A saved view is only ever its query string (see db.SavedView), so applying one is exactly "type this into the filter bar". That is why the picker hands a query back to the app instead of filtering anything itself.

func NewViewPickerModel

func NewViewPickerModel(database *db.DB, currentFilter string, width, height int) *ViewPickerModel

NewViewPickerModel builds the picker over the views stored in the database.

func (*ViewPickerModel) Applied

func (m *ViewPickerModel) Applied() *db.SavedView

Applied returns the view the user chose, or nil.

func (*ViewPickerModel) ClearRequested

func (m *ViewPickerModel) ClearRequested() bool

ClearRequested reports whether the user asked to drop the filter entirely.

func (*ViewPickerModel) Init

func (m *ViewPickerModel) Init() tea.Cmd

Init implements tea.Model.

func (*ViewPickerModel) IsCancelled

func (m *ViewPickerModel) IsCancelled() bool

IsCancelled reports whether the user dismissed the picker.

func (*ViewPickerModel) SetSize

func (m *ViewPickerModel) SetSize(width, height int)

SetSize updates dimensions.

func (*ViewPickerModel) Update

func (m *ViewPickerModel) Update(msg tea.Msg) (*ViewPickerModel, tea.Cmd)

Update handles key input. The parent reads Applied()/ClearRequested()/ IsCancelled() after each update, matching ActionPickerModel.

func (*ViewPickerModel) View

func (m *ViewPickerModel) View() string

View renders the modal.

type WelcomeModel

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

WelcomeModel is the first-run fork shown when there's no project to suggest: "Set up a project" vs "Just start a task" (in the personal project). It also surfaces a machine-readiness status: detected agent CLIs and any missing prerequisites (non-blocking — detection does the work, nothing gates the fork).

func NewWelcomeModel

func NewWelcomeModel(width, height int, detectedAgents []string, tmuxFound bool) *WelcomeModel

func (*WelcomeModel) Choice

func (m *WelcomeModel) Choice() welcomeChoice

func (*WelcomeModel) MoveLeft

func (m *WelcomeModel) MoveLeft()

MoveLeft/MoveRight/Choice drive selection; key handling lives in app.go so it composes with the global update loop (mirrors viewProjectDetectConfirm).

func (*WelcomeModel) MoveRight

func (m *WelcomeModel) MoveRight()

func (*WelcomeModel) SetSize

func (m *WelcomeModel) SetSize(w, h int)

func (*WelcomeModel) View

func (m *WelcomeModel) View() string

Jump to

Keyboard shortcuts

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