Documentation
¶
Overview ¶
Package ui is the Bubble Tea-based terminal UI for tuify.
Architecture ¶
The package is a shell + screens + submodels composition:
- The shell (app.go, app_update.go, app_keys.go, app_view.go, app_handlers.go, app_commands.go) owns the Model, the view stack, the event loop, and every Spotify/clipboard/device side effect.
- Screens are individual views living on the view stack — homeView, playlistView, trackView, podcastView, episodeView, searchView. Each owns its local state (cursor, fetched items, filter query) and renders itself. Screens never touch Model directly.
- Submodels are long-lived state owned by Model that transcend the view stack: nowPlayingModel (playback + marquee scroll), visualizerModel (viz pane + async image/lyrics loaders), deviceSelectorModel.
View → shell communication ¶
Views emit intent messages (see app_intents.go) rather than mutating Model directly. The shell's Update switch interprets each intent by constructing the target view or dispatching the corresponding command, so the view → shell dependency is strictly one-way. Concretely:
- User presses Enter on a playlist → playlistView.OnEnter emits openTracksIntent{id, name} → shell creates trackView and pushes.
- User selects a track in trackView → OnEnter emits playItemIntent → shell dispatches withDevice-wrapped Spotify Play call.
Capability interfaces ¶
The shell dispatches work via small capability interfaces rather than type-asserting against concrete view types (see common.go):
- view (Init/Update/View/SetSize/Breadcrumb) — every screen
- listProvider, searchableListProvider — for shared key handling
- syncableView — for "sync selection to playing track"
- enterable — for Enter-key activation
- scrollable, clickable — for mouse wheel / click dispatch
- backable — for views that consume "go back" internally
- searchAware — for views hosting a search-input mode
Adding a new screen means implementing the capabilities it cares about; no edits to handleMouse/handleBack/handleKeyMsg are needed.
Rendering ¶
Every rendered frame is wrapped in bubblezone.Scan so mouse clicks can be resolved back to zone-marked items (lists mark each row by Spotify URI; the home view marks each menu tab by name). The list delegate (zoneListDelegate in styles.go) does the per-row marking transparently.
Lifetime ¶
NewModel takes a root context from bootstrap.Run that cancels on app exit. That context is propagated to nowPlayingModel, visualizerModel, and every view constructor so long-running operations (polls, HTTP fetches, image/lyrics downloads) cancel cleanly at shutdown rather than running to their per-op timeout.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func RebuildStyles ¶ added in v0.4.0
func RebuildStyles()
RebuildStyles (re)constructs every package-level style from the current theme palette. Call once at startup after theme.Apply, before any UI rendering begins. Safe to call again if the theme is ever changed at runtime.
Types ¶
type AudioSource ¶ added in v0.2.0
type AudioSource interface {
Latest() *audio.FrequencyData
}
AudioSource provides real-time FFT data for the visualizer. Implemented by audio.PipeReader.
type LibrespotInactiveMsg ¶ added in v0.2.0
type LibrespotInactiveMsg struct{}
LibrespotInactiveMsg is sent (via p.Send) when librespot reports that the device became inactive, indicating playback moved to another device.
type Model ¶
type Model struct {
// contains filtered or unexported fields
}
func NewModel ¶
NewModel constructs the root UI model. ctx is the app-level lifetime plumbed from bootstrap.Run; every Spotify API call spawned by the UI wraps it with a per-operation timeout so shutdown cancellation cascades instead of leaking pending requests past tea.Program exit. Panics on nil ctx — forgetting to pass one would silently downgrade shutdown semantics.
type ModelOption ¶
type ModelOption func(*Model)
ModelOption configures optional Model features.
func WithAudioSource ¶ added in v0.2.0
func WithAudioSource(src AudioSource) ModelOption
WithAudioSource sets the audio source for real-time visualizer data and enables the audio-reactive visualizers.
func WithLibrespotInactive ¶ added in v0.2.0
func WithLibrespotInactive(ch <-chan struct{}) ModelOption
WithLibrespotInactive provides a channel that signals when librespot reports its device became inactive (playback moved to another device).
func WithTokenRevoked ¶ added in v0.4.0
func WithTokenRevoked(ch <-chan struct{}) ModelOption
WithTokenRevoked provides a channel that fires once if Spotify rejects the refresh token as permanently invalid. A receive triggers a clean TUI shutdown (bootstrap.Run then prints a re-login message on stderr).
func WithTokenSaveErrors ¶ added in v0.3.0
func WithTokenSaveErrors(ch <-chan error) ModelOption
WithTokenSaveErrors provides a channel that emits OAuth token persistence failures. Each value is rendered as a visible warning so the user can tell why they're getting logged out between sessions.
func WithVimMode ¶
func WithVimMode() ModelOption
WithVimMode enables vim-style keybindings (h/l for back/select, ctrl+d/u half-page, etc.).
type TokenRevokedMsg ¶ added in v0.4.0
type TokenRevokedMsg struct{}
TokenRevokedMsg is delivered when Spotify rejects the refresh token as permanently invalid (user revoked app access, expiry from inactivity, etc.). Every API call will fail from this point on, so the UI shuts down cleanly — bootstrap.Run() then prints a re-login message to stderr and exits.
type TokenSaveErrMsg ¶ added in v0.3.0
type TokenSaveErrMsg struct{ Err error }
TokenSaveErrMsg is delivered when the auth layer fails to persist a refreshed OAuth token. The UI surfaces this as a visible warning because the in-memory token still works for the session — but the user will be forced to log in again on next restart, and without a signal they have no way to connect that to a fixable cause (permissions, disk full, etc.).
Source Files
¶
- app.go
- app_commands.go
- app_handlers.go
- app_intents.go
- app_keys.go
- app_messages.go
- app_options.go
- app_update.go
- app_view.go
- common.go
- device.go
- doc.go
- episode.go
- home.go
- lazylist.go
- nowplaying.go
- nowplaying_poll.go
- nowplaying_render.go
- playlist.go
- podcast.go
- progressbar.go
- search.go
- search_fetch.go
- search_items.go
- search_nav.go
- search_parse.go
- styles.go
- track.go
- visualizer.go
- visualizer_cache.go
- visualizer_image.go
- visualizer_lyrics.go
Directories
¶
| Path | Synopsis |
|---|---|
|
Package visualizers holds the pluggable Visualizer implementations rendered on top of the now-playing view when the user toggles on the visualizer pane.
|
Package visualizers holds the pluggable Visualizer implementations rendered on top of the now-playing view when the user toggles on the visualizer pane. |