ui

package
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Aug 11, 2026 License: MIT Imports: 27 Imported by: 0

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

func NewModel(ctx context.Context, client *spotify.Client, opts ...ModelOption) Model

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.

func (Model) Init

func (m Model) Init() tea.Cmd

func (Model) Update

func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd)

func (Model) View

func (m Model) View() string

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

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.

Jump to

Keyboard shortcuts

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