workspace

package
v0.2.3 Latest Latest
Warning

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

Go to latest
Published: Mar 4, 2026 License: MIT Imports: 24 Imported by: 0

Documentation

Overview

Package workspace provides the persistent TUI application.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ApplyOverrides

func ApplyOverrides(km *GlobalKeyMap, overrides map[string]string)

ApplyOverrides remaps keybindings in km according to the overrides map. Keys are action names (e.g. "hey"), values are key strings (e.g. "ctrl+h"). Unknown actions are silently ignored.

func LoadKeyOverrides

func LoadKeyOverrides(path string) (map[string]string, error)

LoadKeyOverrides reads keybinding overrides from a JSON file. Returns an empty map (not an error) if the file doesn't exist.

func Navigate(target ViewTarget, scope Scope) tea.Cmd

Navigate returns a command that sends a NavigateMsg.

func NavigateBack() tea.Cmd

NavigateBack returns a command that sends a NavigateBackMsg.

func OpenURL

func OpenURL(url string) tea.Cmd

OpenURL opens the given URL in the default browser.

func ReportError

func ReportError(err error, context string) tea.Cmd

ReportError returns a command that sends an ErrorMsg.

func SetStatus

func SetStatus(text string, isError bool) tea.Cmd

SetStatus returns a command that sets a status message.

Types

type AccountInfo

type AccountInfo struct {
	ID   string
	Name string
}

AccountInfo represents a discovered Basecamp account.

type AccountNameMsg

type AccountNameMsg struct {
	AccountID string // which account this name belongs to
	Name      string
	Err       error
}

AccountNameMsg is sent when the account name is resolved.

type AccountsDiscoveredMsg

type AccountsDiscoveredMsg struct {
	Accounts []AccountInfo
	Err      error
}

AccountsDiscoveredMsg is sent when multi-account discovery completes.

type Action

type Action struct {
	Name        string
	Aliases     []string
	Description string
	Category    string           // "navigation", "project", "mutation", etc.
	Scope       ScopeRequirement // what scope context is needed
	Available   func(Scope) bool // optional; narrows scope check further
	Execute     func(session *Session) tea.Cmd
}

Action represents a registered command/action in the workspace.

type ActivityEntryInfo

type ActivityEntryInfo = data.ActivityEntryInfo

ActivityEntryInfo is a type alias for data.ActivityEntryInfo.

type AssignmentInfo

type AssignmentInfo = data.AssignmentInfo

AssignmentInfo is a type alias for data.AssignmentInfo.

type BlurMsg

type BlurMsg struct{}

BlurMsg indicates a view lost focus.

type BoostCreatedMsg

type BoostCreatedMsg struct {
	Target BoostTarget
	Emoji  string
}

BoostCreatedMsg is sent when a boost has been successfully created. Views can use this to optimistically update their local item counts.

type BoostPicker

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

func NewBoostPicker

func NewBoostPicker(styles *tui.Styles) *BoostPicker

func (*BoostPicker) Blur

func (p *BoostPicker) Blur()

func (*BoostPicker) Focus

func (p *BoostPicker) Focus()

func (*BoostPicker) SetSize

func (p *BoostPicker) SetSize(width, height int)

func (*BoostPicker) Update

func (p *BoostPicker) Update(msg tea.Msg) (*BoostPicker, tea.Cmd)

func (*BoostPicker) View

func (p *BoostPicker) View() string

type BoostSelectedMsg

type BoostSelectedMsg struct {
	Emoji string
}

type BoostTarget

type BoostTarget struct {
	ProjectID   int64
	RecordingID int64
	AccountID   string
	Title       string // brief context for the picker UI
}

BoostTarget defines the context needed to apply a boost.

type CampfireLineInfo

type CampfireLineInfo = data.CampfireLineInfo

CampfireLineInfo is a type alias for data.CampfireLineInfo.

type CampfireLineSentMsg

type CampfireLineSentMsg struct {
	Err error
}

CampfireLineSentMsg is sent after posting a line.

type CampfireLinesLoadedMsg

type CampfireLinesLoadedMsg struct {
	Lines      []CampfireLineInfo
	TotalCount int  // total lines available from X-Total-Count
	Prepend    bool // true when loading older messages (prepend to existing)
	Err        error
}

CampfireLinesLoadedMsg is sent when campfire lines are fetched.

type CardColumnInfo

type CardColumnInfo = data.CardColumnInfo

CardColumnInfo is a type alias for data.CardColumnInfo.

type CardInfo

type CardInfo = data.CardInfo

CardInfo is a type alias for data.CardInfo.

type CheckinQuestionInfo

type CheckinQuestionInfo = data.CheckinQuestionInfo

CheckinQuestionInfo is a type alias for data.CheckinQuestionInfo.

type ChromeSyncMsg

type ChromeSyncMsg struct{}

ChromeSyncMsg signals the workspace to re-sync chrome (breadcrumb, hints). Emitted by views when their Title() changes dynamically (e.g., folder navigation).

type CommentCreatedMsg

type CommentCreatedMsg struct {
	RecordingID int64
	Err         error
}

CommentCreatedMsg is sent after a comment is successfully posted.

type ComposeType

type ComposeType int

ComposeType identifies what kind of content is being composed.

const (
	ComposeMessage ComposeType = iota
)

type DockLoadedMsg

type DockLoadedMsg struct {
	Project basecamp.Project
	Err     error
}

DockLoadedMsg is sent when a project's dock is loaded.

type DocsFilesItemInfo

type DocsFilesItemInfo = data.DocsFilesItemInfo

DocsFilesItemInfo is a type alias for data.DocsFilesItemInfo.

type EpochMsg

type EpochMsg struct {
	Epoch uint64
	Inner tea.Msg
}

EpochMsg wraps an async result with the session epoch at Cmd creation time. The workspace drops EpochMsgs whose epoch differs from the current session epoch, preventing stale results from a previous account from reaching the active view after an account switch.

type ErrorMsg

type ErrorMsg struct {
	Err     error
	Context string // what was being attempted
}

ErrorMsg wraps an error for display.

type Filterable

type Filterable interface {
	StartFilter()
}

Filterable is an optional interface for views with lists that support interactive filtering. When the workspace receives "/" it calls StartFilter on the current view instead of navigating to global search.

type FocusMsg

type FocusMsg struct{}

FocusMsg indicates a view gained focus.

type FocusedItemScope

type FocusedItemScope struct {
	AccountID   string
	ProjectID   int64
	RecordingID int64
}

FocusedItemScope holds the account/project/recording context of the currently focused list item. Zero-valued fields mean "unknown/same as the session scope".

type FocusedRecording

type FocusedRecording interface {
	FocusedItem() FocusedItemScope
}

FocusedRecording is an optional interface for views that can identify the scope of the currently focused item. Used by open-in-browser to route through the correct account/project.

type GlobalKeyMap

type GlobalKeyMap struct {
	Quit          key.Binding
	Help          key.Binding
	Back          key.Binding
	Search        key.Binding
	Palette       key.Binding
	AccountSwitch key.Binding
	Hey           key.Binding
	MyStuff       key.Binding
	Activity      key.Binding
	Sidebar       key.Binding
	SidebarFocus  key.Binding
	Refresh       key.Binding
	Open          key.Binding
	Jump          key.Binding
	Metrics       key.Binding
}

GlobalKeyMap defines keybindings that work in every context.

func DefaultGlobalKeyMap

func DefaultGlobalKeyMap() GlobalKeyMap

DefaultGlobalKeyMap returns the default global keybindings.

func (GlobalKeyMap) FullHelp

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

FullHelp returns all global key bindings for the help overlay.

func (GlobalKeyMap) ShortHelp

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

ShortHelp returns the global key bindings for the status bar. The budget-aware renderer in the status bar shows as many as fit.

type HeyEntryInfo

type HeyEntryInfo = data.HeyEntryInfo

HeyEntryInfo is a type alias for data.HeyEntryInfo.

type InputCapturer

type InputCapturer interface {
	InputActive() bool
}

InputCapturer is an optional interface views can implement to signal they are in text input mode. When InputActive returns true, the workspace will skip global single-key bindings (q, r, 1-9, etc.) and forward all keys directly to the view.

type ListKeyMap

type ListKeyMap struct {
	Up       key.Binding
	Down     key.Binding
	Top      key.Binding
	Bottom   key.Binding
	PageDown key.Binding
	PageUp   key.Binding
	Open     key.Binding
	Filter   key.Binding
}

ListKeyMap defines keybindings for list navigation.

func DefaultListKeyMap

func DefaultListKeyMap() ListKeyMap

DefaultListKeyMap returns the default list navigation keybindings.

type MessageCreatedMsg

type MessageCreatedMsg struct {
	Message MessageInfo
	Err     error
}

MessageCreatedMsg is sent after a message is successfully posted.

type MessageDetailLoadedMsg

type MessageDetailLoadedMsg struct {
	MessageID int64
	Subject   string
	Creator   string
	CreatedAt string
	Category  string
	Content   string // HTML body
	Err       error
}

MessageDetailLoadedMsg is sent when a single message's full content is fetched.

type MessageInfo

type MessageInfo = data.MessageInfo

MessageInfo is a type alias for data.MessageInfo.

type ModalActive

type ModalActive interface {
	IsModal() bool
}

ModalActive is an optional interface views can implement to signal they have an active modal state (e.g., cards move mode, search results focus). When ModalActive returns true, Esc is forwarded to the view instead of triggering global back navigation.

type NavigateBackMsg struct{}

NavigateBackMsg requests navigation to the previous view.

type NavigateMsg struct {
	Target ViewTarget
	Scope  Scope
}

NavigateMsg requests navigation to a new view.

type NavigateToDepthMsg struct {
	Depth int
}

NavigateToDepthMsg jumps to a specific breadcrumb depth.

type OpenBoostPickerMsg

type OpenBoostPickerMsg struct {
	Target BoostTarget
}

OpenBoostPickerMsg signals the workspace to open the boost emoji picker for the given target.

type PersonInfo

type PersonInfo = data.PersonInfo

PersonInfo is a type alias for data.PersonInfo.

type PingRoomInfo

type PingRoomInfo = data.PingRoomInfo

PingRoomInfo is a type alias for data.PingRoomInfo.

type ProjectBookmarkedMsg

type ProjectBookmarkedMsg struct {
	ProjectID  int64
	Bookmarked bool
	Err        error
}

ProjectBookmarkedMsg is sent after toggling a project bookmark.

type RefreshMsg

type RefreshMsg struct{}

RefreshMsg requests a data refresh for the current view.

type Registry

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

Registry holds all registered actions.

func DefaultActions

func DefaultActions() *Registry

DefaultActions returns a registry pre-populated with the standard navigation actions.

func NewRegistry

func NewRegistry() *Registry

NewRegistry creates an empty action registry.

func (*Registry) All

func (r *Registry) All() []Action

All returns every registered action.

func (*Registry) ForScope

func (r *Registry) ForScope(scope Scope) []Action

ForScope returns actions available for the given scope. Available refines the scope check but does not bypass it.

func (*Registry) Register

func (r *Registry) Register(action Action)

Register adds an action to the registry.

func (*Registry) Search

func (r *Registry) Search(query string) []Action

Search returns actions whose name, aliases, or description match the query. An empty query returns all actions.

type Router

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

Router manages the navigation stack with state preservation.

func NewRouter

func NewRouter() *Router

NewRouter creates an empty router.

func (*Router) Breadcrumbs

func (r *Router) Breadcrumbs() []string

Breadcrumbs returns the title chain for all views in the stack.

func (*Router) CanGoBack

func (r *Router) CanGoBack() bool

CanGoBack returns true if there is a previous view to return to.

func (*Router) Current

func (r *Router) Current() View

Current returns the current (top) view, or nil if empty.

func (*Router) CurrentScope

func (r *Router) CurrentScope() Scope

CurrentScope returns the scope of the current view.

func (*Router) CurrentTarget

func (r *Router) CurrentTarget() ViewTarget

CurrentTarget returns the ViewTarget of the current view, or 0 if empty.

func (*Router) Depth

func (r *Router) Depth() int

Depth returns the current stack depth.

func (*Router) Pop

func (r *Router) Pop() View

Pop removes and returns the top view from the stack. Returns nil if the stack has one or fewer entries (never pops the root).

func (*Router) PopToDepth

func (r *Router) PopToDepth(depth int) View

PopToDepth pops entries until the stack is at the given depth. Returns the view at the target depth, or nil if invalid.

func (*Router) Push

func (r *Router) Push(view View, scope Scope, target ViewTarget)

Push adds a view to the navigation stack.

func (*Router) Reset

func (r *Router) Reset()

Reset clears the entire navigation stack.

type ScheduleEntryInfo

type ScheduleEntryInfo = data.ScheduleEntryInfo

ScheduleEntryInfo is a type alias for data.ScheduleEntryInfo.

type Scope

type Scope struct {
	AccountID     string
	AccountName   string
	ProjectID     int64
	ProjectName   string
	ToolType      string // "todoset", "chat", "card_table", "message_board", etc.
	ToolID        int64
	RecordingID   int64
	RecordingType string

	// Ephemeral origin context — meaningful only for the target view, not session state.
	// Stripped from session scope during navigate() and restored in the view scope.
	OriginView string // source view name ("Activity", "Hey!", "Pulse")
	OriginHint string // context ("completed Todo", "needs your attention")
}

Scope represents the current position in the Basecamp hierarchy.

type ScopeRequirement

type ScopeRequirement int

ScopeRequirement defines what scope context an action needs.

const (
	// ScopeAny means the action works anywhere.
	ScopeAny ScopeRequirement = iota
	// ScopeAccount means the action needs an account selected.
	ScopeAccount
	// ScopeProject means the action needs a project selected.
	ScopeProject
)

type SearchResultInfo

type SearchResultInfo = data.SearchResultInfo

SearchResultInfo is a type alias for data.SearchResultInfo.

type SearchResultsMsg

type SearchResultsMsg struct {
	Results    []SearchResultInfo
	Query      string
	Err        error
	PartialErr error // non-nil when some accounts failed but results exist
}

SearchResultsMsg is sent when search results arrive.

type Session

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

Session holds the active workspace state: auth, SDK access, scope, and styles.

func NewSession

func NewSession(app *appctx.App) *Session

NewSession creates a session from the fully-initialized App.

func NewTestSession

func NewTestSession() *Session

NewTestSession returns a minimal Session for use in external package tests. It provides styles and an empty MultiStore (no accounts discovered), but no app, hub, or recents.

func NewTestSessionWithHub

func NewTestSessionWithHub() *Session

NewTestSessionWithHub returns a test Session that includes a Hub. The Hub's MultiStore has nil SDK, so ClientFor returns nil and Hub mutation methods return an error — but the Hub itself is non-nil, which is enough for key handler tests that exercise the state machine.

func NewTestSessionWithRecents

func NewTestSessionWithRecents(r *recents.Store) *Session

NewTestSessionWithRecents is like NewTestSession but includes a recents store.

func NewTestSessionWithScope

func NewTestSessionWithScope(scope Scope) *Session

NewTestSessionWithScope returns a test Session with a Hub and a pre-set scope.

func (*Session) AccountClient

func (s *Session) AccountClient() *basecamp.AccountClient

AccountClient returns the SDK client for the current account. Panics if AccountID is not set — call RequireAccount first. Thread-safe: reads scope under lock.

func (*Session) App

func (s *Session) App() *appctx.App

App returns the underlying appctx.App.

func (*Session) ConsumeInitialView

func (s *Session) ConsumeInitialView() (ViewTarget, Scope, bool)

ConsumeInitialView returns and clears the deep-link target, if any. Returns (target, scope, true) when a deep-link was set, or (0, {}, false) otherwise.

func (*Session) Context

func (s *Session) Context() context.Context

Context returns the session's cancellable context for SDK operations. Canceled on account switch or shutdown, aborting in-flight requests. Thread-safe: may be called from Cmd goroutines concurrently with ResetContext.

func (*Session) Epoch

func (s *Session) Epoch() uint64

Epoch returns the session's monotonic epoch counter. Incremented on every account switch; used by the workspace to discard stale async results that were initiated under a previous account. Thread-safe: may be called from Cmd goroutines.

func (*Session) HasAccount

func (s *Session) HasAccount() bool

HasAccount returns true if an account is selected. Thread-safe: may be called from Cmd goroutines.

func (*Session) Hub

func (s *Session) Hub() *data.Hub

Hub returns the central data coordinator for typed, realm-scoped pool access.

func (*Session) MultiStore

func (s *Session) MultiStore() *data.MultiStore

MultiStore returns the cross-account data layer.

func (*Session) Recents

func (s *Session) Recents() *recents.Store

Recents returns the recents store (may be nil if no cache dir).

func (*Session) ReloadTheme added in v0.2.0

func (s *Session) ReloadTheme()

ReloadTheme re-reads the theme from disk and updates the shared Styles in place. All components holding *Styles see new colors on the next render.

func (*Session) ResetContext

func (s *Session) ResetContext()

ResetContext cancels the current context (aborting in-flight operations), creates a fresh one, and advances the epoch counter. Called on account switch.

func (*Session) Scope

func (s *Session) Scope() Scope

Scope returns the current scope. Thread-safe: may be called from Cmd goroutines.

func (*Session) SetInitialView

func (s *Session) SetInitialView(target ViewTarget, scope Scope)

SetInitialView configures a deep-link target to navigate to on startup instead of Home. Called from the tui command when a URL argument is provided.

func (*Session) SetScope

func (s *Session) SetScope(scope Scope)

SetScope updates the current scope.

func (*Session) Shutdown

func (s *Session) Shutdown()

Shutdown cancels the session context and tears down all Hub realms. Called on program exit.

func (*Session) Styles

func (s *Session) Styles() *tui.Styles

Styles returns the current TUI styles.

type SplitPaneFocuser

type SplitPaneFocuser interface {
	HasSplitPane() bool
}

SplitPaneFocuser is an optional interface for views that use a split-pane layout with internal tab-cycling. When the sidebar is open, the workspace routes tab to the view instead of consuming it for sidebar focus switching.

type StatusClearMsg

type StatusClearMsg struct {
	Gen uint64
}

StatusClearMsg clears an expired status message.

type StatusMsg

type StatusMsg struct {
	Text    string
	IsError bool
}

StatusMsg sets a temporary status message.

type ThemeChangedMsg added in v0.2.0

type ThemeChangedMsg struct{}

ThemeChangedMsg signals that the theme file changed on disk.

type TimelineEventInfo

type TimelineEventInfo = data.TimelineEventInfo

TimelineEventInfo is a type alias for data.TimelineEventInfo.

type TodoCreatedMsg

type TodoCreatedMsg struct {
	TodolistID int64
	Content    string
	Err        error
}

TodoCreatedMsg is sent after a todo is created.

type TodoInfo

type TodoInfo = data.TodoInfo

TodoInfo is a type alias for data.TodoInfo.

type TodolistInfo

type TodolistInfo = data.TodolistInfo

TodolistInfo is a type alias for data.TodolistInfo.

type ToggleHelpMsg

type ToggleHelpMsg struct{}

ToggleHelpMsg toggles the help overlay.

type TogglePaletteMsg

type TogglePaletteMsg struct{}

TogglePaletteMsg toggles the command palette.

type View

type View interface {
	tea.Model

	// Title returns the breadcrumb segment for this view.
	Title() string

	// ShortHelp returns key bindings shown in the status bar.
	ShortHelp() []key.Binding

	// FullHelp returns all key bindings for the help overlay.
	FullHelp() [][]key.Binding

	// SetSize updates the view's available dimensions.
	SetSize(width, height int)
}

View is the interface that all workspace views must implement.

type ViewFactory

type ViewFactory func(target ViewTarget, session *Session, scope Scope) View

ViewFactory creates views for navigation targets.

type ViewTarget

type ViewTarget int

ViewTarget identifies which view to navigate to.

const (
	ViewProjects ViewTarget = iota
	ViewDock
	ViewTodos
	ViewCampfire
	ViewHey
	ViewCards
	ViewMessages
	ViewSearch
	ViewMyStuff
	ViewPeople
	ViewDetail
	ViewSchedule
	ViewDocsFiles
	ViewCheckins
	ViewForwards
	ViewPulse
	ViewAssignments
	ViewPings
	ViewCompose
	ViewHome
	ViewActivity
	ViewTimeline // project-scoped timeline
)

func (ViewTarget) IsGlobal

func (t ViewTarget) IsGlobal() bool

IsGlobal returns true for view targets that aggregate across all accounts.

type Workspace

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

Workspace is the root tea.Model for the persistent TUI application.

func New

func New(session *Session, factory ViewFactory) *Workspace

New creates a new Workspace model.

func (*Workspace) CloseWatcher added in v0.2.0

func (w *Workspace) CloseWatcher()

CloseWatcher shuts down the theme file watcher, if running. Safe to call multiple times.

func (*Workspace) Init

func (w *Workspace) Init() tea.Cmd

Init implements tea.Model.

func (*Workspace) Update

func (w *Workspace) Update(msg tea.Msg) (tea.Model, tea.Cmd)

Update implements tea.Model.

func (*Workspace) View

func (w *Workspace) View() string

View implements tea.Model.

Directories

Path Synopsis
Package chrome provides always-visible shell components for the workspace.
Package chrome provides always-visible shell components for the workspace.
Package views provides the individual screens for the workspace TUI.
Package views provides the individual screens for the workspace TUI.
Package widget provides reusable TUI components.
Package widget provides reusable TUI components.

Jump to

Keyboard shortcuts

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