Documentation
¶
Index ¶
- func RealmPool[P Pooler](r *Realm, key string, create func() P) P
- type AccountData
- type AccountInfo
- type AccountResult
- type ActivityEntryInfo
- type AssignmentInfo
- type BoostEmbed
- type BoostInfo
- type BoostPreview
- type BoostSummary
- type CampfireLineInfo
- type CampfireLinesResult
- type CardColumnInfo
- type CardCreateMutation
- type CardInfo
- type CardMoveMutation
- type CheckinAnswerInfo
- type CheckinQuestionInfo
- type DockToolInfo
- type DocsFilesItemInfo
- type FetchFunc
- type ForwardInfo
- type HeyEntryInfo
- type Hub
- func (h *Hub) Account() *Realm
- func (h *Hub) AccountContext() context.Context
- func (h *Hub) Assignments() *Pool[[]AssignmentInfo]
- func (h *Hub) Boosts(projectID, recordingID int64) *Pool[BoostSummary]
- func (h *Hub) CampfireLines(projectID, campfireID int64) *Pool[CampfireLinesResult]
- func (h *Hub) Cards(projectID, tableID int64) *MutatingPool[[]CardColumnInfo]
- func (h *Hub) CheckinAnswers(projectID, questionID int64) *Pool[[]CheckinAnswerInfo]
- func (h *Hub) Checkins(projectID, questionnaireID int64) *Pool[[]CheckinQuestionInfo]
- func (h *Hub) ClearCardAssignees(ctx context.Context, accountID string, projectID, cardID int64) error
- func (h *Hub) ClearCardDueOn(ctx context.Context, accountID string, projectID, cardID int64) error
- func (h *Hub) ClearTodoAssignees(ctx context.Context, accountID string, projectID, todoID int64) error
- func (h *Hub) ClearTodoDueOn(ctx context.Context, accountID string, projectID, todoID int64) error
- func (h *Hub) CompleteTodo(ctx context.Context, accountID string, projectID, todoID int64) error
- func (h *Hub) CompletedTodos(projectID, todolistID int64) *Pool[[]TodoInfo]
- func (h *Hub) CreateBoost(ctx context.Context, accountID string, projectID, recordingID int64, ...) (BoostInfo, error)
- func (h *Hub) CreateCheckinAnswer(ctx context.Context, accountID string, projectID, questionID int64, ...) error
- func (h *Hub) CreateDocument(ctx context.Context, accountID string, projectID, vaultID int64, title string) error
- func (h *Hub) CreateScheduleEntry(ctx context.Context, accountID string, projectID, scheduleID int64, ...) error
- func (h *Hub) CreateTodolist(ctx context.Context, accountID string, projectID, todosetID int64, name string) error
- func (h *Hub) CreateVault(ctx context.Context, accountID string, projectID, parentVaultID int64, ...) error
- func (h *Hub) DeleteBoost(ctx context.Context, projectID, boostID int64) error
- func (h *Hub) DocsFiles(projectID, vaultID int64) *Pool[[]DocsFilesItemInfo]
- func (h *Hub) EnsureAccount(accountID string) *Realm
- func (h *Hub) EnsureProject(projectID int64) *Realm
- func (h *Hub) Forwards(projectID, inboxID int64) *Pool[[]ForwardInfo]
- func (h *Hub) Global() *Realm
- func (h *Hub) HeyActivity() *Pool[[]ActivityEntryInfo]
- func (h *Hub) LeaveProject()
- func (h *Hub) Messages(projectID, boardID int64) *Pool[[]MessageInfo]
- func (h *Hub) Metrics() *PoolMetrics
- func (h *Hub) MultiStore() *MultiStore
- func (h *Hub) People() *Pool[[]PersonInfo]
- func (h *Hub) PinMessage(ctx context.Context, accountID string, projectID, messageID int64) error
- func (h *Hub) PingRooms() *Pool[[]PingRoomInfo]
- func (h *Hub) Poller() *Poller
- func (h *Hub) Project() *Realm
- func (h *Hub) ProjectContext() context.Context
- func (h *Hub) ProjectTimeline(projectID int64) *Pool[[]TimelineEventInfo]
- func (h *Hub) Projects() *Pool[[]ProjectInfo]
- func (h *Hub) Pulse() *Pool[[]ActivityEntryInfo]
- func (h *Hub) ScheduleEntries(projectID, scheduleID int64) *Pool[[]ScheduleEntryInfo]
- func (h *Hub) Shutdown()
- func (h *Hub) Subscribe(ctx context.Context, accountID string, projectID, recordingID int64) error
- func (h *Hub) SwitchAccount(accountID string)
- func (h *Hub) Timeline() *Pool[[]TimelineEventInfo]
- func (h *Hub) Todolists(projectID, todosetID int64) *Pool[[]TodolistInfo]
- func (h *Hub) Todos(projectID, todolistID int64) *MutatingPool[[]TodoInfo]
- func (h *Hub) TrashComment(ctx context.Context, accountID string, projectID, commentID int64) error
- func (h *Hub) TrashRecording(ctx context.Context, accountID string, projectID, recordingID int64) error
- func (h *Hub) TrashTodolist(ctx context.Context, accountID string, projectID, todolistID int64) error
- func (h *Hub) UncompleteTodo(ctx context.Context, accountID string, projectID, todoID int64) error
- func (h *Hub) UnpinMessage(ctx context.Context, accountID string, projectID, messageID int64) error
- func (h *Hub) Unsubscribe(ctx context.Context, accountID string, projectID, recordingID int64) error
- func (h *Hub) UpdateCard(ctx context.Context, accountID string, projectID, cardID int64, ...) error
- func (h *Hub) UpdateComment(ctx context.Context, accountID string, projectID, commentID int64, ...) error
- func (h *Hub) UpdateMessage(ctx context.Context, accountID string, projectID, messageID int64, ...) error
- func (h *Hub) UpdateTodo(ctx context.Context, accountID string, projectID, todoID int64, ...) error
- func (h *Hub) UpdateTodolist(ctx context.Context, accountID string, projectID, todolistID int64, ...) error
- type KeyedPool
- type MessageInfo
- type MetricsSummary
- type MultiStore
- func (ms *MultiStore) Accounts() []AccountInfo
- func (ms *MultiStore) ClientFor(accountID string) *basecamp.AccountClient
- func (ms *MultiStore) DiscoverAccounts(ctx context.Context) ([]AccountInfo, error)
- func (ms *MultiStore) FanOut(ctx context.Context, ...) []AccountResult
- func (ms *MultiStore) FanOutSingle(ctx context.Context, accountID string, ...) (any, error)
- func (ms *MultiStore) Identity() *basecamp.Identity
- func (ms *MultiStore) SetAccountsForTest(accounts []AccountInfo)
- type MutatingPool
- type Mutation
- type MutationErrorMsg
- type NavigationEvent
- type PersonInfo
- type PingRoomInfo
- type PollConfig
- type PollMsg
- type Poller
- type Pool
- func (p *Pool[T]) Clear()
- func (p *Pool[T]) Fetch(ctx context.Context) tea.Cmd
- func (p *Pool[T]) FetchIfStale(ctx context.Context) tea.Cmd
- func (p *Pool[T]) Get() Snapshot[T]
- func (p *Pool[T]) Invalidate()
- func (p *Pool[T]) Key() string
- func (p *Pool[T]) PollInterval() time.Duration
- func (p *Pool[T]) RecordHit()
- func (p *Pool[T]) RecordMiss()
- func (p *Pool[T]) Set(data T)
- func (p *Pool[T]) SetFocused(focused bool)
- func (p *Pool[T]) SetMetrics(m *PoolMetrics)
- func (p *Pool[T]) SetPushMode(enabled bool)
- func (p *Pool[T]) Status() PoolStatus
- func (p *Pool[T]) Version() uint64
- type PoolConfig
- type PoolEvent
- type PoolEventType
- type PoolMetrics
- func (m *PoolMetrics) Apdex() float64
- func (m *PoolMetrics) PoolStatsList() []PoolStatus
- func (m *PoolMetrics) Record(e PoolEvent)
- func (m *PoolMetrics) RecordNavigation(e NavigationEvent)
- func (m *PoolMetrics) RegisterPool(key string, reporter func() PoolStatus)
- func (m *PoolMetrics) Summary() MetricsSummary
- func (m *PoolMetrics) UnregisterPool(key string)
- type PoolStats
- type PoolStatus
- type PoolUpdatedMsg
- type Pooler
- type ProjectInfo
- type Realm
- type RecordingWithBoosts
- type ScheduleEntryInfo
- type SearchResultInfo
- type Snapshot
- type SnapshotState
- type TimelineEventInfo
- type TodoCompleteMutation
- type TodoCreateMutation
- type TodoInfo
- type TodolistInfo
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type AccountData ¶
type AccountData[T any] struct { Account AccountInfo Data T Err error }
AccountData holds the typed outcome of a fan-out query against one account. Replaces the type-erased AccountResult for new pool-based code.
func FanOut ¶
func FanOut[T any](ctx context.Context, ms *MultiStore, fn func(acct AccountInfo, client *basecamp.AccountClient) (T, error), ) []AccountData[T]
FanOut runs fn against all discovered accounts concurrently, limiting parallelism to maxConcurrent. Results are returned in account order. Unlike MultiStore.FanOut, the result is fully typed — no type assertions.
type AccountInfo ¶
AccountInfo represents a discovered Basecamp account.
type AccountResult ¶
type AccountResult struct {
Account AccountInfo
Data any
Err error
}
AccountResult holds the outcome of a single fan-out query against one account.
type ActivityEntryInfo ¶
type ActivityEntryInfo struct {
ID int64
Title string
Type string // "Todo", "Message", "Document", etc.
Creator string
Account string
AccountID string
Project string
ProjectID int64
UpdatedAt string // formatted time
UpdatedAtTS int64 // unix timestamp for sorting
}
ActivityEntryInfo represents a recording from any account for the activity feed.
type AssignmentInfo ¶
type AssignmentInfo struct {
ID int64
Content string
DueOn string
Completed bool
Account string
AccountID string
Project string
ProjectID int64
Todolist string
Overdue bool
}
AssignmentInfo represents a todo assigned to the current user.
type BoostEmbed ¶
type BoostEmbed struct {
BoostsSummary BoostSummary
}
BoostEmbed can be embedded in recording info types to add boost support.
func (BoostEmbed) GetBoosts ¶
func (be BoostEmbed) GetBoosts() BoostSummary
GetBoosts returns the embedded boost summary.
func (*BoostEmbed) SetBoosts ¶
func (be *BoostEmbed) SetBoosts(summary BoostSummary)
SetBoosts sets the boost summary on the embedded field.
type BoostInfo ¶
type BoostInfo struct {
ID int64
Content string // emoji or short text
Booster string // name of person who boosted
BoosterID int64
CreatedAt string // formatted time
}
BoostInfo is a lightweight representation of a boost (emoji reaction).
type BoostPreview ¶
type BoostPreview struct {
Content string // the boost content (emoji or text)
BoosterID int64 // for avatar-based display
}
BoostPreview is a single boost preview for list display. Shows either emoji content or avatar indicator depending on context.
type BoostSummary ¶
type BoostSummary struct {
Count int // total boost count
Preview []BoostPreview // compact preview for list display
}
BoostSummary holds boost metadata for display on list items. Includes both a count and a preview of who's boosting.
func (BoostSummary) GetBoosts ¶
func (b BoostSummary) GetBoosts() BoostSummary
GetBoosts returns an empty boost summary - recording types should embed BoostEmbed and override this if they have boosts.
type CampfireLineInfo ¶
type CampfireLineInfo struct {
ID int64
Body string // HTML content
Creator string
CreatedAt string // formatted time
CreatedAtTS time.Time // raw timestamp for grouping
BoostEmbed
}
CampfireLineInfo is a lightweight representation of a campfire line.
type CampfireLinesResult ¶
type CampfireLinesResult struct {
Lines []CampfireLineInfo
TotalCount int
}
CampfireLinesResult holds the lines plus pagination metadata from a campfire fetch. This compound type is the Pool's data value so that views can access TotalCount for pagination without a side-channel.
type CardColumnInfo ¶
type CardColumnInfo struct {
ID int64
Title string
Color string
Type string // "Kanban::Triage", "Kanban::Column", "Kanban::DoneColumn", "Kanban::NotNowColumn"
CardsCount int // from column metadata (available without fetching cards)
Deferred bool // true when cards were not fetched (Done/NotNow columns)
Cards []CardInfo
}
CardColumnInfo represents a kanban column with its cards.
type CardCreateMutation ¶
type CardCreateMutation struct {
Title string
ColumnID int64 // identity-based column target (not index)
ProjectID int64
Client *basecamp.AccountClient
// contains filtered or unexported fields
}
CardCreateMutation optimistically prepends a new card to a kanban column. Implements Mutation[[]CardColumnInfo] for use with MutatingPool.
The createdID field uses atomic.Int64 because ApplyRemotely runs in the mutation's tea.Cmd goroutine while a concurrent background fetch can trigger reconcile → IsReflectedIn under the pool lock in a different goroutine. Pointer receiver required so the atomic field is shared.
func (*CardCreateMutation) ApplyLocally ¶
func (m *CardCreateMutation) ApplyLocally(columns []CardColumnInfo) []CardColumnInfo
ApplyLocally prepends a placeholder card to the target column (by ID). If the column is not found (e.g. reorder during flight), no-op.
func (*CardCreateMutation) ApplyRemotely ¶
func (m *CardCreateMutation) ApplyRemotely(ctx context.Context) error
ApplyRemotely calls the SDK to create the card.
func (*CardCreateMutation) IsReflectedIn ¶
func (m *CardCreateMutation) IsReflectedIn(columns []CardColumnInfo) bool
IsReflectedIn returns true when the created card appears in the target column. Returns false if ApplyRemotely hasn't completed yet (createdID == 0).
type CardInfo ¶
type CardInfo struct {
ID int64
Title string
Assignees []string
DueOn string
Position int
Completed bool
StepsTotal int
StepsDone int
CommentsCount int
BoostEmbed // embedded boost support
}
CardInfo represents a single card.
type CardMoveMutation ¶
type CardMoveMutation struct {
CardID int64
SourceColIdx int // index of the source column
TargetColIdx int // index of the target column
TargetColumnID int64 // API ID of target column
Client *basecamp.AccountClient
ProjectID int64
}
CardMoveMutation moves a card from one column to another. Implements Mutation[[]CardColumnInfo] for use with MutatingPool.
func (CardMoveMutation) ApplyLocally ¶
func (m CardMoveMutation) ApplyLocally(columns []CardColumnInfo) []CardColumnInfo
ApplyLocally moves the card between columns in the local data.
func (CardMoveMutation) ApplyRemotely ¶
func (m CardMoveMutation) ApplyRemotely(ctx context.Context) error
ApplyRemotely calls the SDK to move the card.
func (CardMoveMutation) IsReflectedIn ¶
func (m CardMoveMutation) IsReflectedIn(columns []CardColumnInfo) bool
IsReflectedIn returns true when the card appears in the target column in the remote data. For deferred target columns (Done/NotNow) whose Cards slice is always empty, we check that the card is absent from the source column instead.
type CheckinAnswerInfo ¶
type CheckinAnswerInfo struct {
ID int64
Creator string
CreatedAt time.Time
Content string // HTML
GroupOn string // YYYY-MM-DD or empty
CommentsCount int
}
CheckinAnswerInfo is a lightweight representation of a check-in answer.
type CheckinQuestionInfo ¶
type CheckinQuestionInfo struct {
ID int64
Title string
Paused bool
AnswersCount int
Frequency string
}
CheckinQuestionInfo is a lightweight representation of a check-in question.
type DockToolInfo ¶
type DockToolInfo struct {
ID int64
Name string // "todoset", "chat", "message_board", etc.
Title string
Enabled bool
}
DockToolInfo represents an enabled tool on a project's dock.
type DocsFilesItemInfo ¶
type DocsFilesItemInfo struct {
ID int64
Title string
Type string // "Folder", "Document", "Upload"
CreatedAt string
Creator string
VaultsCount int // sub-folders (Type=="Folder" only)
DocsCount int // documents (Type=="Folder" only)
UploadsCount int // uploads (Type=="Folder" only)
}
DocsFilesItemInfo is a lightweight representation of a vault item.
type ForwardInfo ¶
ForwardInfo is a lightweight representation of an email forward.
type HeyEntryInfo ¶
type HeyEntryInfo struct {
ID int64
Title string
Excerpt string
Creator string
Project string
CreatedAt string
IsRead bool
}
HeyEntryInfo is a lightweight representation of an inbox entry.
type Hub ¶
type Hub struct {
// contains filtered or unexported fields
}
Hub is the central data coordinator providing typed, realm-scoped pool access.
Hub manages three realm tiers:
- Global: app lifetime (identity, account list)
- Account: active account session (projects, people)
- Project: active project context (schedule, campfire, messages, etc.)
Typed pool accessors return realm-scoped pools whose lifecycle is automatic: project pools are torn down on LeaveProject/EnsureProject(different), account pools on SwitchAccount, global pools on Shutdown.
func NewHub ¶
func NewHub(multi *MultiStore, poller *Poller) *Hub
NewHub creates a Hub with a global realm and the given dependencies.
func (*Hub) AccountContext ¶
AccountContext returns the account realm's context, or the global context as fallback. Views should pass this to pool Fetch calls for account-scoped data so that SwitchAccount cancels in-flight fetches.
func (*Hub) Assignments ¶
func (h *Hub) Assignments() *Pool[[]AssignmentInfo]
Assignments returns a global-scope pool of cross-account todo assignments.
func (*Hub) Boosts ¶
func (h *Hub) Boosts(projectID, recordingID int64) *Pool[BoostSummary]
Boosts returns a project-scoped pool of boosts for a recording. The pool stores BoostSummary (count + preview) for list item display.
func (*Hub) CampfireLines ¶
func (h *Hub) CampfireLines(projectID, campfireID int64) *Pool[CampfireLinesResult]
CampfireLines returns a project-scoped pool of campfire lines with polling config. The pool stores CampfireLinesResult (lines + TotalCount) for pagination support. Pagination (fetchOlderLines) and writes (sendLine) remain view-owned.
func (*Hub) Cards ¶
func (h *Hub) Cards(projectID, tableID int64) *MutatingPool[[]CardColumnInfo]
Cards returns a project-scoped MutatingPool of card columns with their cards. The MutatingPool supports optimistic card moves via CardMoveMutation. Done and Not Now columns are deferred: metadata only, no card fetching.
func (*Hub) CheckinAnswers ¶
func (h *Hub) CheckinAnswers(projectID, questionID int64) *Pool[[]CheckinAnswerInfo]
CheckinAnswers returns a project-scoped pool of answers for a specific check-in question.
func (*Hub) Checkins ¶
func (h *Hub) Checkins(projectID, questionnaireID int64) *Pool[[]CheckinQuestionInfo]
Checkins returns a project-scoped pool of check-in questions.
func (*Hub) ClearCardAssignees ¶
func (h *Hub) ClearCardAssignees(ctx context.Context, accountID string, projectID, cardID int64) error
ClearCardAssignees clears all assignees on a card. Uses a raw Put to bypass the SDK's omitempty on AssigneeIDs which prevents sending empty slices.
func (*Hub) ClearCardDueOn ¶
ClearCardDueOn clears the due date on a card. Uses a raw Put to bypass the SDK's omitempty on DueOn which prevents sending empty strings.
func (*Hub) ClearTodoAssignees ¶
func (h *Hub) ClearTodoAssignees(ctx context.Context, accountID string, projectID, todoID int64) error
ClearTodoAssignees clears all assignees on a todo. Uses a raw Put to bypass the SDK's omitempty on AssigneeIDs which prevents sending empty slices.
func (*Hub) ClearTodoDueOn ¶
ClearTodoDueOn clears the due date on a todo. Uses a raw Put to bypass the SDK's omitempty on DueOn which prevents sending empty strings.
func (*Hub) CompleteTodo ¶
CompleteTodo marks a todo as completed. Uses explicit accountID for cross-account mutations from aggregate views (Assignments, Hey).
func (*Hub) CompletedTodos ¶
CompletedTodos returns a project-scoped Pool of completed todos for a specific todolist. Unlike Todos(), this is a plain Pool (not MutatingPool) since un-completing uses invalidate+refetch rather than optimistic mutation.
func (*Hub) CreateBoost ¶
func (h *Hub) CreateBoost(ctx context.Context, accountID string, projectID, recordingID int64, content string) (BoostInfo, error)
CreateBoost creates a new boost on a recording. accountID selects which account's client to use; empty means the Hub's current account. Returns the created BoostInfo or an error.
func (*Hub) CreateCheckinAnswer ¶
func (h *Hub) CreateCheckinAnswer(ctx context.Context, accountID string, projectID, questionID int64, content string) error
CreateCheckinAnswer posts a new answer to a check-in question.
func (*Hub) CreateDocument ¶
func (h *Hub) CreateDocument(ctx context.Context, accountID string, projectID, vaultID int64, title string) error
CreateDocument creates a new document in a vault.
func (*Hub) CreateScheduleEntry ¶
func (h *Hub) CreateScheduleEntry(ctx context.Context, accountID string, projectID, scheduleID int64, req *basecamp.CreateScheduleEntryRequest) error
CreateScheduleEntry creates a new schedule entry.
func (*Hub) CreateTodolist ¶
func (h *Hub) CreateTodolist(ctx context.Context, accountID string, projectID, todosetID int64, name string) error
CreateTodolist creates a new todolist in a todoset.
func (*Hub) CreateVault ¶
func (h *Hub) CreateVault(ctx context.Context, accountID string, projectID, parentVaultID int64, title string) error
CreateVault creates a new sub-folder in a vault.
func (*Hub) DeleteBoost ¶
DeleteBoost deletes a boost by ID.
func (*Hub) DocsFiles ¶
func (h *Hub) DocsFiles(projectID, vaultID int64) *Pool[[]DocsFilesItemInfo]
DocsFiles returns a project-scoped pool of vault items (folders, documents, uploads).
func (*Hub) EnsureAccount ¶
EnsureAccount returns the account realm, creating one if needed. If called with a different accountID than the current realm, the old realm is torn down (along with any project realm) and a fresh one created.
func (*Hub) EnsureProject ¶
EnsureProject returns the project realm, creating one if needed. If called with a different projectID than the current realm, the old realm is torn down and a fresh one created.
func (*Hub) Forwards ¶
func (h *Hub) Forwards(projectID, inboxID int64) *Pool[[]ForwardInfo]
Forwards returns a project-scoped pool of email forwards.
func (*Hub) HeyActivity ¶
func (h *Hub) HeyActivity() *Pool[[]ActivityEntryInfo]
HeyActivity returns a global-scope pool of cross-account activity entries. The pool fans out Recordings.List across all accounts, caches for 30s (fresh) / 5m (stale), and polls at 30s/2m intervals.
func (*Hub) Messages ¶
func (h *Hub) Messages(projectID, boardID int64) *Pool[[]MessageInfo]
Messages returns a project-scoped pool of message board posts.
func (*Hub) Metrics ¶
func (h *Hub) Metrics() *PoolMetrics
Metrics returns the pool metrics collector.
func (*Hub) MultiStore ¶
func (h *Hub) MultiStore() *MultiStore
MultiStore returns the cross-account SDK access layer.
func (*Hub) People ¶
func (h *Hub) People() *Pool[[]PersonInfo]
People returns an account-scoped pool of people in the current account. Panics if no account realm is active — callers must EnsureAccount first.
func (*Hub) PinMessage ¶
PinMessage pins a message to the top of its board.
func (*Hub) PingRooms ¶
func (h *Hub) PingRooms() *Pool[[]PingRoomInfo]
PingRooms returns a global-scope pool of 1:1 campfire threads.
func (*Hub) ProjectContext ¶
ProjectContext returns the project realm's context, or the account/global context as fallback. Views should pass this to pool Fetch calls for project-scoped data so that LeaveProject cancels in-flight fetches.
func (*Hub) ProjectTimeline ¶
func (h *Hub) ProjectTimeline(projectID int64) *Pool[[]TimelineEventInfo]
ProjectTimeline returns a project-scoped pool of timeline events.
func (*Hub) Projects ¶
func (h *Hub) Projects() *Pool[[]ProjectInfo]
Projects returns a global-scope pool of all projects across accounts. Each project carries account attribution for cross-account navigation. Used by Home (bookmarks), Projects view, and Dock.
func (*Hub) Pulse ¶
func (h *Hub) Pulse() *Pool[[]ActivityEntryInfo]
Pulse returns a global-scope pool of cross-account recent activity. Like HeyActivity but includes more recording types and groups by account.
func (*Hub) ScheduleEntries ¶
func (h *Hub) ScheduleEntries(projectID, scheduleID int64) *Pool[[]ScheduleEntryInfo]
ScheduleEntries returns a project-scoped pool of schedule entries.
func (*Hub) Shutdown ¶
func (h *Hub) Shutdown()
Shutdown tears down all realms. Call on program exit.
func (*Hub) SwitchAccount ¶
SwitchAccount tears down the project and account realms, then creates a fresh account realm. Replaces the store.Clear() + router.Reset() sledgehammer with targeted realm teardown.
func (*Hub) Timeline ¶
func (h *Hub) Timeline() *Pool[[]TimelineEventInfo]
Timeline returns a global-scope pool of cross-account timeline events. Uses the richer Timeline.Progress() API instead of Recordings.List.
func (*Hub) Todolists ¶
func (h *Hub) Todolists(projectID, todosetID int64) *Pool[[]TodolistInfo]
Todolists returns a project-scoped pool of todolists.
func (*Hub) Todos ¶
func (h *Hub) Todos(projectID, todolistID int64) *MutatingPool[[]TodoInfo]
Todos returns a project-scoped MutatingPool of todos for a specific todolist. The MutatingPool supports optimistic todo completion via TodoCompleteMutation.
func (*Hub) TrashComment ¶
TrashComment moves a comment to the trash.
func (*Hub) TrashRecording ¶
func (h *Hub) TrashRecording(ctx context.Context, accountID string, projectID, recordingID int64) error
TrashRecording moves a recording to the trash.
func (*Hub) TrashTodolist ¶
func (h *Hub) TrashTodolist(ctx context.Context, accountID string, projectID, todolistID int64) error
TrashTodolist moves a todolist to the trash.
func (*Hub) UncompleteTodo ¶
UncompleteTodo reopens a completed todo.
func (*Hub) UnpinMessage ¶
UnpinMessage unpins a message from the board.
func (*Hub) Unsubscribe ¶
func (h *Hub) Unsubscribe(ctx context.Context, accountID string, projectID, recordingID int64) error
Unsubscribe unsubscribes the current user from a recording.
func (*Hub) UpdateCard ¶
func (h *Hub) UpdateCard(ctx context.Context, accountID string, projectID, cardID int64, req *basecamp.UpdateCardRequest) error
UpdateCard updates a card's fields.
func (*Hub) UpdateComment ¶
func (h *Hub) UpdateComment(ctx context.Context, accountID string, projectID, commentID int64, content string) error
UpdateComment updates a comment's content.
func (*Hub) UpdateMessage ¶
func (h *Hub) UpdateMessage(ctx context.Context, accountID string, projectID, messageID int64, req *basecamp.UpdateMessageRequest) error
UpdateMessage updates a message's fields.
func (*Hub) UpdateTodo ¶
func (h *Hub) UpdateTodo(ctx context.Context, accountID string, projectID, todoID int64, req *basecamp.UpdateTodoRequest) error
UpdateTodo updates a todo's fields.
type KeyedPool ¶
type KeyedPool[K comparable, T any] struct { // contains filtered or unexported fields }
KeyedPool manages sub-collection pools keyed by a parent ID. For data keyed by a parent: todos by todolist, campfire lines by campfire, comments by recording.
func NewKeyedPool ¶
func NewKeyedPool[K comparable, T any](factory func(key K) *Pool[T]) *KeyedPool[K, T]
NewKeyedPool creates a KeyedPool with the given factory for creating new pools on demand. All pools share the factory's config but have independent fetch state.
func (*KeyedPool[K, T]) Clear ¶
func (kp *KeyedPool[K, T]) Clear()
Clear removes all sub-pools and their data.
func (*KeyedPool[K, T]) Get ¶
Get returns the Pool for the given key, creating one if it doesn't exist.
func (*KeyedPool[K, T]) Invalidate ¶
func (kp *KeyedPool[K, T]) Invalidate()
Invalidate marks all sub-pools as stale.
type MessageInfo ¶
type MessageInfo struct {
ID int64
Subject string
Creator string
CreatedAt string
Category string
Pinned bool
BoostEmbed // embedded boost support
}
MessageInfo represents a message board post.
type MetricsSummary ¶
type MetricsSummary struct {
ActivePools int
P50Latency time.Duration
ErrorRate float64
Apdex float64
}
MetricsSummary provides a point-in-time snapshot of pool health.
type MultiStore ¶
type MultiStore struct {
// contains filtered or unexported fields
}
MultiStore manages cross-account data access. It discovers all accessible accounts, caches per-account SDK clients, and provides FanOut for running the same query against all accounts concurrently with semaphore limiting.
func NewMultiStore ¶
func NewMultiStore(sdk *basecamp.Client) *MultiStore
NewMultiStore creates a MultiStore backed by the given SDK client.
func (*MultiStore) Accounts ¶
func (ms *MultiStore) Accounts() []AccountInfo
Accounts returns the discovered account list. Returns nil before DiscoverAccounts.
func (*MultiStore) ClientFor ¶
func (ms *MultiStore) ClientFor(accountID string) *basecamp.AccountClient
ClientFor returns an AccountClient for the given account ID. Clients are cached and reused across calls. Returns nil if the SDK is not initialized.
func (*MultiStore) DiscoverAccounts ¶
func (ms *MultiStore) DiscoverAccounts(ctx context.Context) ([]AccountInfo, error)
DiscoverAccounts fetches all accessible accounts and initializes the store. Safe to call multiple times; subsequent calls refresh the account list.
func (*MultiStore) FanOut ¶
func (ms *MultiStore) FanOut(ctx context.Context, fn func(acct AccountInfo, client *basecamp.AccountClient) (any, error)) []AccountResult
FanOut runs fn against all discovered accounts concurrently, limiting parallelism to maxConcurrent. Results are returned in account order. Individual account failures are captured in AccountResult.Err — the overall operation only fails if no accounts are discovered yet.
func (*MultiStore) FanOutSingle ¶
func (ms *MultiStore) FanOutSingle(ctx context.Context, accountID string, fn func(ctx context.Context, client *basecamp.AccountClient) (any, error)) (any, error)
FanOutSingle runs fn against a single account by ID. Returns ctx.Err() immediately if the context is already canceled.
func (*MultiStore) Identity ¶
func (ms *MultiStore) Identity() *basecamp.Identity
Identity returns the logged-in user's identity, or nil before discovery.
func (*MultiStore) SetAccountsForTest ¶
func (ms *MultiStore) SetAccountsForTest(accounts []AccountInfo)
SetAccountsForTest sets the account list directly, bypassing DiscoverAccounts. Only for use in tests.
type MutatingPool ¶
MutatingPool extends Pool with optimistic mutation support. Go equivalent of iOS BaseConcurrentDataStore / Android BaseConcurrentRepository.
func NewMutatingPool ¶
func NewMutatingPool[T any](key string, config PoolConfig, fetchFn FetchFunc[T]) *MutatingPool[T]
NewMutatingPool creates a MutatingPool with the given key, config, and fetch function.
func (*MutatingPool[T]) Apply ¶
Apply executes an optimistic mutation:
- Applies locally to the snapshot (immediate, synchronous)
- Returns a Cmd that applies remotely, then re-fetches and reconciles
The caller should read pool.Get() after calling Apply to get the optimistic data for immediate rendering.
func (*MutatingPool[T]) Clear ¶
func (mp *MutatingPool[T]) Clear()
Clear overrides Pool.Clear to also reset mutation state.
func (*MutatingPool[T]) Fetch ¶
func (mp *MutatingPool[T]) Fetch(ctx context.Context) tea.Cmd
Fetch overrides Pool.Fetch to reconcile pending mutations after a successful fetch rather than overwriting them.
func (*MutatingPool[T]) FetchIfStale ¶
func (mp *MutatingPool[T]) FetchIfStale(ctx context.Context) tea.Cmd
FetchIfStale overrides Pool.FetchIfStale to route through MutatingPool.Fetch.
type Mutation ¶
type Mutation[T any] interface { // ApplyLocally modifies the current state optimistically. ApplyLocally(current T) T // ApplyRemotely performs the server-side operation. ApplyRemotely(ctx context.Context) error // IsReflectedIn returns true when the remote data already contains // this mutation's effect (for pending-mutation pruning on re-fetch). IsReflectedIn(remote T) bool }
Mutation describes an optimistic mutation lifecycle. Go equivalent of iOS's Mutation protocol / Android's Mutation<T, A>.
type MutationErrorMsg ¶
MutationErrorMsg is sent when a mutation's remote apply fails.
type NavigationEvent ¶
type NavigationEvent struct {
}
NavigationEvent records a view navigation with data quality.
type PersonInfo ¶
type PersonInfo struct {
ID int64
Name string
Email string
Title string
Admin bool
Owner bool
Client bool
PersonType string // "User", "Client", etc.
Company string
}
PersonInfo is a lightweight representation of a person for the view.
type PingRoomInfo ¶
type PingRoomInfo struct {
CampfireID int64
ProjectID int64
PersonName string
Account string
AccountID string
LastMessage string
LastAt string
LastAtTS int64 // unix timestamp for sorting
}
PingRoomInfo represents a 1:1 campfire thread.
type PollConfig ¶
type PollConfig struct {
Tag string
Base time.Duration // interval when focused and active
Background time.Duration // interval when blurred
Max time.Duration // maximum backoff
// contains filtered or unexported fields
}
PollConfig configures a single polling channel.
type PollMsg ¶
type PollMsg struct {
Tag string
}
PollMsg is sent when a poll interval fires. Tag identifies which poller triggered.
type Poller ¶
type Poller struct {
// contains filtered or unexported fields
}
Poller manages adaptive polling for multiple data channels.
func (*Poller) RecordMiss ¶
RecordMiss increases the interval (no new data).
func (*Poller) SetFocused ¶
SetFocused adjusts base interval for focus/blur.
type Pool ¶
type Pool[T any] struct { // contains filtered or unexported fields }
Pool is a typed, self-refreshing data source. Go equivalent of iOS RemoteReadService / Android BaseApiRepository. One Pool per logical data set (projects, hey-activity, campfire-lines).
The Pool does not subscribe or push — it's a typed cache with fetch capabilities. TEA's polling mechanism (PollMsg -> view calls FetchIfStale) drives the refresh cycle.
func NewPool ¶
func NewPool[T any](key string, config PoolConfig, fetchFn FetchFunc[T]) *Pool[T]
NewPool creates a Pool with the given key, config, and fetch function.
func (*Pool[T]) Fetch ¶
Fetch returns a Cmd that fetches fresh data and emits PoolUpdatedMsg. Concurrent fetches are deduped — returns nil if a fetch is in progress.
func (*Pool[T]) FetchIfStale ¶
FetchIfStale returns a Fetch Cmd if data is stale or empty, nil if fresh.
func (*Pool[T]) Get ¶
Get returns the current snapshot. Never blocks. Recalculates state based on TTL — a snapshot stored as Fresh may be returned as Stale if FreshTTL has elapsed, or expired (HasData=false) if StaleTTL has also elapsed.
func (*Pool[T]) Invalidate ¶
func (p *Pool[T]) Invalidate()
Invalidate marks current data as stale. Next FetchIfStale will re-fetch.
func (*Pool[T]) PollInterval ¶
PollInterval returns the current recommended polling interval, accounting for focus state, push mode, and miss backoff.
func (*Pool[T]) RecordHit ¶
func (p *Pool[T]) RecordHit()
RecordHit resets the miss counter (new data arrived).
func (*Pool[T]) RecordMiss ¶
func (p *Pool[T]) RecordMiss()
RecordMiss increments the miss counter for adaptive backoff.
func (*Pool[T]) Set ¶
func (p *Pool[T]) Set(data T)
Set writes data directly into the pool (for prefetch / dual-write patterns).
func (*Pool[T]) SetFocused ¶
SetFocused marks whether the view consuming this pool has focus.
func (*Pool[T]) SetMetrics ¶
func (p *Pool[T]) SetMetrics(m *PoolMetrics)
SetMetrics sets the metrics collector for this pool.
func (*Pool[T]) SetPushMode ¶
SetPushMode enables/disables push mode (SSE connected). In push mode, poll intervals are extended significantly.
func (*Pool[T]) Status ¶
func (p *Pool[T]) Status() PoolStatus
Status returns a live status snapshot for the metrics panel.
type PoolConfig ¶
type PoolConfig struct {
FreshTTL time.Duration // how long data is "fresh" (0 = no expiry)
StaleTTL time.Duration // how long stale data is served during revalidation
PollBase time.Duration // base polling interval when focused (0 = no auto-poll)
PollBg time.Duration // background polling interval when blurred
PollMax time.Duration // max interval after consecutive misses
}
PoolConfig configures a Pool's timing behavior.
type PoolEvent ¶
type PoolEvent struct {
Timestamp time.Time
PoolKey string
EventType PoolEventType
Duration time.Duration
DataSize int
}
PoolEvent records a single pool fetch event.
type PoolEventType ¶
type PoolEventType int
PoolEventType classifies pool fetch events.
const ( FetchStart PoolEventType = iota FetchComplete FetchError )
type PoolMetrics ¶
type PoolMetrics struct {
// contains filtered or unexported fields
}
PoolMetrics collects pool fetch telemetry for status bar display.
func NewPoolMetrics ¶
func NewPoolMetrics() *PoolMetrics
NewPoolMetrics creates an empty metrics collector.
func (*PoolMetrics) Apdex ¶
func (m *PoolMetrics) Apdex() float64
Apdex returns the navigation quality score (0.0-1.0). Fresh = satisfied (1.0), Stale = tolerating (0.5), Empty = frustrated (0.0).
func (*PoolMetrics) PoolStatsList ¶
func (m *PoolMetrics) PoolStatsList() []PoolStatus
PoolStatsList returns live status from all registered pools. Copies the reporter slice under lock, then invokes reporters without holding the metrics lock to avoid lock-order inversion with pool mutexes.
func (*PoolMetrics) Record ¶
func (m *PoolMetrics) Record(e PoolEvent)
Record adds a pool event to the ring buffer and updates stats.
func (*PoolMetrics) RecordNavigation ¶
func (m *PoolMetrics) RecordNavigation(e NavigationEvent)
RecordNavigation logs a view navigation with data quality.
func (*PoolMetrics) RegisterPool ¶
func (m *PoolMetrics) RegisterPool(key string, reporter func() PoolStatus)
RegisterPool adds a live status reporter for a pool.
func (*PoolMetrics) Summary ¶
func (m *PoolMetrics) Summary() MetricsSummary
Summary returns aggregate metrics for status bar display.
func (*PoolMetrics) UnregisterPool ¶
func (m *PoolMetrics) UnregisterPool(key string)
UnregisterPool removes a pool's status reporter.
type PoolStatus ¶
type PoolStatus struct {
Key string
State SnapshotState
FetchedAt time.Time
PollInterval time.Duration
HitCount int
MissCount int
FetchCount int
ErrorCount int
AvgLatency time.Duration
}
PoolStatus is a live status snapshot from a registered pool.
type PoolUpdatedMsg ¶
type PoolUpdatedMsg struct {
Key string
}
PoolUpdatedMsg is sent when a pool's snapshot changes. Views match on Key to identify which pool updated, then read typed data via the pool's Get() method.
type Pooler ¶
type Pooler interface {
Invalidate()
Clear()
}
Pooler is the non-generic interface for pool lifecycle management. Realm uses this to manage pools of different types uniformly.
type ProjectInfo ¶
type ProjectInfo struct {
ID int64
Name string
Description string
Purpose string
Bookmarked bool
AccountID string
AccountName string
Dock []DockToolInfo
}
ProjectInfo wraps a project with account attribution for multi-account pools. basecamp.Project doesn't carry which account it belongs to, so the Hub's Projects() FetchFunc annotates each project during fan-out.
type Realm ¶
type Realm struct {
// contains filtered or unexported fields
}
Realm manages a group of pools with a shared lifecycle. Pools belong to realms. Realm teardown cancels all owned pools' in-flight fetches and clears their data.
Three realms mirror the mobile architecture:
- Global: app lifetime (identity, account list)
- Account: active account session (projects, hey, assignments)
- Project: active project context (todos, campfire, messages)
func (*Realm) Context ¶
Context returns the realm's context. Canceled on teardown. Pass this to pool fetch functions so they abort when the realm dies.
func (*Realm) Invalidate ¶
func (r *Realm) Invalidate()
Invalidate marks all pools in this realm as stale.
type RecordingWithBoosts ¶
type RecordingWithBoosts interface {
GetBoosts() BoostSummary
}
RecordingWithBoosts extends recording info types to include boost metadata.
type ScheduleEntryInfo ¶
type ScheduleEntryInfo struct {
ID int64
Summary string
StartsAt string
EndsAt string
AllDay bool
Participants []string
}
ScheduleEntryInfo is a lightweight representation of a schedule entry.
type SearchResultInfo ¶
type SearchResultInfo struct {
ID int64
Title string
Excerpt string
Type string // "todo", "message", "document", etc.
Project string
ProjectID int64
Account string // account name (populated in multi-account mode)
AccountID string // account ID for navigation
CreatedAt string
CreatedAtTS int64 // unix timestamp for sorting
}
SearchResultInfo represents a single search result.
type Snapshot ¶
type Snapshot[T any] struct { Data T State SnapshotState Err error FetchedAt time.Time HasData bool // distinguishes zero-value T from "never fetched" }
Snapshot holds typed data along with its fetch state. Every pool exposes Snapshot[T] — consumers never see any.
type SnapshotState ¶
type SnapshotState int
SnapshotState represents the freshness state of a data snapshot. Go equivalent of iOS DataUpdate<T> / Android Data<T>.
const ( StateEmpty SnapshotState = iota // no data yet StateFresh // data within TTL StateStale // data past TTL, usable for SWR StateLoading // fetch in progress (may have stale data) StateError // fetch failed (may have stale data) )
func (SnapshotState) String ¶
func (s SnapshotState) String() string
type TimelineEventInfo ¶
type TimelineEventInfo struct {
ID int64
RecordingID int64 // parent recording ID for navigation/detail
CreatedAt string // formatted time
CreatedAtTS int64 // unix timestamp for sorting
Kind string // "todo_completed", "message_created", etc.
Action string // "completed", "created", "commented on"
Target string // "Todo", "Message", "Document"
Title string
SummaryExcerpt string // first ~100 chars of body
Creator string
Project string
ProjectID int64
Account string
AccountID string
}
TimelineEventInfo is a lightweight representation of a timeline event.
type TodoCompleteMutation ¶
type TodoCompleteMutation struct {
TodoID int64
Completed bool // target state (true = complete, false = uncomplete)
Client *basecamp.AccountClient
ProjectID int64
}
TodoCompleteMutation toggles a todo's completion state. Implements Mutation[[]TodoInfo] for use with MutatingPool.
func (TodoCompleteMutation) ApplyLocally ¶
func (m TodoCompleteMutation) ApplyLocally(todos []TodoInfo) []TodoInfo
ApplyLocally toggles the todo's Completed field in the local data.
func (TodoCompleteMutation) ApplyRemotely ¶
func (m TodoCompleteMutation) ApplyRemotely(ctx context.Context) error
ApplyRemotely calls the SDK to complete or uncomplete the todo.
func (TodoCompleteMutation) IsReflectedIn ¶
func (m TodoCompleteMutation) IsReflectedIn(todos []TodoInfo) bool
IsReflectedIn returns true when the remote data shows the todo in the target completion state.
type TodoCreateMutation ¶
type TodoCreateMutation struct {
Content string
TodolistID int64
ProjectID int64
Client *basecamp.AccountClient
// contains filtered or unexported fields
}
TodoCreateMutation optimistically prepends a new todo to a todolist. Implements Mutation[[]TodoInfo] for use with MutatingPool.
The createdID field uses atomic.Int64 because ApplyRemotely runs in the mutation's tea.Cmd goroutine while a concurrent background fetch can trigger reconcile → IsReflectedIn under the pool lock in a different goroutine. Pointer receiver required so the atomic field is shared.
func (*TodoCreateMutation) ApplyLocally ¶
func (m *TodoCreateMutation) ApplyLocally(todos []TodoInfo) []TodoInfo
ApplyLocally prepends a placeholder todo with a temporary negative ID.
func (*TodoCreateMutation) ApplyRemotely ¶
func (m *TodoCreateMutation) ApplyRemotely(ctx context.Context) error
ApplyRemotely calls the SDK to create the todo.
func (*TodoCreateMutation) IsReflectedIn ¶
func (m *TodoCreateMutation) IsReflectedIn(todos []TodoInfo) bool
IsReflectedIn returns true when the created todo appears in the remote data. Returns false if ApplyRemotely hasn't completed yet (createdID == 0).