ui

package
v0.3.31 Latest Latest
Warning

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

Go to latest
Published: Sep 17, 2026 License: MIT Imports: 51 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 (
	// 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 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 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 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 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.

Functions

func CloseLogger

func CloseLogger()

CloseLogger closes the log file.

func FgStyle added in v0.3.2

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 added in v0.3.29

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 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 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 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 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 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 added in v0.3.2

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 added in v0.3.18

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 added in v0.3.18

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

NewActionPickerModel creates an action picker for the given items.

func (*ActionPickerModel) Init added in v0.3.18

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

Init implements tea.Model.

func (*ActionPickerModel) IsCancelled added in v0.3.18

func (m *ActionPickerModel) IsCancelled() bool

IsCancelled reports whether the user dismissed the picker.

func (*ActionPickerModel) Selected added in v0.3.18

func (m *ActionPickerModel) Selected() *PluginActionItem

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

func (*ActionPickerModel) SetSize added in v0.3.18

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

SetSize updates dimensions.

func (*ActionPickerModel) Update added in v0.3.18

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 added in v0.3.18

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 added in v0.3.30

func (m *AppModel) ClearTerminalTask()

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

func (*AppModel) DumpDebugStateToFile

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

DumpDebugStateToFile writes the current debug state to a file.

func (*AppModel) EnableReload added in v0.3.28

func (m *AppModel) EnableReload(token string)

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

func (*AppModel) EnableTerminalTaskReport added in v0.3.30

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 added in v0.3.28

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 added in v0.3.30

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 added in v0.3.30

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 added in v0.3.28

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

func (*AppModel) RestoreReloadState added in v0.3.28

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) SetTasks

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

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

func (*AppModel) ShowStartupNotice added in v0.3.31

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 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 added in v0.3.18

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 added in v0.3.30

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 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"`
	// 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) 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) ClearPaneHalt added in v0.3.30

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) 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) PaneSetupHalted added in v0.3.30

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) 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 added in v0.3.31

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) SessionClosed added in v0.3.31

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) 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 added in v0.2.30

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) Task

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

Task returns the current task.

func (*DetailModel) ToggleHelpExpanded added in v0.3.16

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) 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 added in v0.3.30

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 added in v0.3.31

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 added in v0.3.31

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 added in v0.3.0

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 added in v0.3.0

func NewFolderPickerModel(width, height int) *FolderPickerModel

NewFolderPickerModel seeds the picker from common project roots.

func (*FolderPickerModel) Init added in v0.3.0

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

func (*FolderPickerModel) SetSize added in v0.3.0

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

func (*FolderPickerModel) Update added in v0.3.0

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

func (*FolderPickerModel) View added in v0.3.0

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 added in v0.3.2

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 added in v0.3.16

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 added in v0.3.31

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 added in v0.3.31

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 added in v0.3.31

func (k *KanbanBoard) ListOptions() ListOptions

ListOptions returns the current arrangement.

func (*KanbanBoard) ListTasks added in v0.3.31

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 added in v0.3.2

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 added in v0.3.2

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 added in v0.3.2

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 added in v0.3.31

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 added in v0.3.31

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 added in v0.3.31

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 added in v0.3.16

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
	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
}

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 added in v0.3.31

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 added in v0.3.31

type ListOptions struct {
	GroupBy ListGroupBy
	Sort    ListSort
}

ListOptions is the arrangement of the list view.

func DefaultListOptions added in v0.3.31

func DefaultListOptions() ListOptions

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

func LoadListOptions added in v0.3.31

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

LoadListOptions reads the arrangement from settings.

func (ListOptions) Arrange added in v0.3.31

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

Arrange returns the tasks in display order for these options.

func (ListOptions) Normalize added in v0.3.31

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 added in v0.3.31

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 added in v0.3.31

func (o ListOptions) Summary() string

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

type ListOptionsModel added in v0.3.31

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 added in v0.3.31

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

NewListOptionsModel opens the widget on the current arrangement.

func (*ListOptionsModel) Init added in v0.3.31

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

Init implements tea.Model.

func (*ListOptionsModel) IsCancelled added in v0.3.31

func (m *ListOptionsModel) IsCancelled() bool

IsCancelled reports whether the user backed out.

func (*ListOptionsModel) IsDone added in v0.3.31

func (m *ListOptionsModel) IsDone() bool

IsDone reports whether the user committed.

func (*ListOptionsModel) Options added in v0.3.31

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 added in v0.3.31

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

SetSize updates dimensions.

func (*ListOptionsModel) Update added in v0.3.31

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

Update handles key input.

func (*ListOptionsModel) View added in v0.3.31

func (m *ListOptionsModel) View() string

View renders the modal.

type ListSort added in v0.3.31

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 PluginActionItem added in v0.3.18

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

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

type ReloadState added in v0.3.28

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 added in v0.3.25

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 added in v0.3.25

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

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

func (*RepoCloneModel) Cancel added in v0.3.25

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 added in v0.3.25

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

func (*RepoCloneModel) SetSize added in v0.3.25

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

func (*RepoCloneModel) Update added in v0.3.25

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

func (*RepoCloneModel) View added in v0.3.25

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 added in v0.2.33

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 added in v0.3.7

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 added in v0.3.7

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

NewRoutinesModel loads routine definitions and their latest runs.

func (*RoutinesModel) Init added in v0.3.7

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

Init implements tea.Model.

func (*RoutinesModel) SetSize added in v0.3.7

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

SetSize updates the view dimensions.

func (*RoutinesModel) Update added in v0.3.7

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

Update implements tea.Model.

func (*RoutinesModel) View added in v0.3.7

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 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 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
	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 added in v0.3.31

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 added in v0.3.31

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 added in v0.3.31

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

Applied returns the view the user chose, or nil.

func (*ViewPickerModel) ClearRequested added in v0.3.31

func (m *ViewPickerModel) ClearRequested() bool

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

func (*ViewPickerModel) Init added in v0.3.31

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

Init implements tea.Model.

func (*ViewPickerModel) IsCancelled added in v0.3.31

func (m *ViewPickerModel) IsCancelled() bool

IsCancelled reports whether the user dismissed the picker.

func (*ViewPickerModel) SetSize added in v0.3.31

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

SetSize updates dimensions.

func (*ViewPickerModel) Update added in v0.3.31

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 added in v0.3.31

func (m *ViewPickerModel) View() string

View renders the modal.

type WelcomeModel added in v0.3.0

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 added in v0.3.0

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

func (*WelcomeModel) Choice added in v0.3.0

func (m *WelcomeModel) Choice() welcomeChoice

func (*WelcomeModel) MoveLeft added in v0.3.0

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 added in v0.3.0

func (m *WelcomeModel) MoveRight()

func (*WelcomeModel) SetSize added in v0.3.0

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

func (*WelcomeModel) View added in v0.3.0

func (m *WelcomeModel) View() string

Jump to

Keyboard shortcuts

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