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 ¶
This section is empty.
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 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 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. |