Repository navigation
v0.3.0
·
184 commits
to develop
since this release
v0.3.0 — Architecture overhaul, scrolling lists, and visualizer polish
This is a large refactor release. The UI shell is split into screens + capability interfaces, the bootstrap path is fully context-aware, and lists now scroll with the mouse. There's a new visualizer, smarter device handling, and a long list of correctness fixes.
New features
- Scrolling lists + mouse clicks — scroll the wheel anywhere in a list, click to select.
- Marquee track titles — when the track + artist don't fit the now-playing bar, they scroll horizontally.
- Loading spinners while data is in flight.
- Spectrogram visualizer — new entry in the visualizer cycle (visualizer module also refactored).
- Volume-aware visualizer gain — visualizers compensate when you change device volume so they don't go silent.
- Device transfer preserves progress — transferring playback no longer resets the timeline.
Bug fixes
- Search → select bug — fixed selecting albums or artists after a search.
- Search/select track bug fixed.
- Shuffle deselected when manually picking a track — fixed.
- lazylist View panic — fixed the bubbles/list paginator panic when scrolling deep then filtering to a small result set (with regression tests).
- pipe_reader deadlock — Start/Stop no longer hold the mutex while waiting on a hung read loop.
- Auth goroutine leak — NewSavingClient now returns a cleanup func that cancels the proactive-refresh goroutine; bootstrap.Run defers it.
- generateRandomBase64 returns an error instead of swallowing it; both call sites wrap with %w.
Architecture / refactors
This release reshapes the internals around a few invariants that are now codified in CLAUDE.md.
- app → bootstrap — the startup package was renamed and its responsibilities tightened.
- A root context.Context is threaded from bootstrap.Run through every Spotify call, sub-model, and view constructor. Shutdown now cancels in-flight requests instead of letting them
run to their own timeouts. Every context.Background() in production UI code is gone. - Explicit construction path — ctx is the only way to build a Model, nil panics, sub-models propagate deterministically. No hidden defaults in the UI bootstrap path.
- app.go split into smaller modules.
- Screens send intents rather than depending on Model — communication is one-way; the shell interprets the intent.
- Capability interfaces — new screens implement interfaces (listProvider, scrollable, clickable, enterable, searchAware, …) instead of editing centralized switches.
- nowplaying.go, search.go, visualizers, client.go all refactored along the same lines.
- *APIError everywhere — doWithRetry returns a structured error (Status, Body, URL) on non-2xx; bodies truncated to 500 bytes; GetPlayerState keeps its 204→nil shortcut.
- doc.go for every internal package — packages now declare their intent and invariants in code.
CI / tooling
- New CI lint job: gofmt -l, go vet, and golangci-lint with a conservative .golangci.yml. Every real issue surfaced was fixed (ineffectual assignment, deprecated oto.Close, three unchecked errors).
- More linters added on top of the baseline, all findings resolved.
- golangci-lint pinned to v1.64.8, Go targeted at 1.24.
Other
- Device list styling fixes and improvements.
- Improved logging across the board.
- Added more tests (auth, audio pipe, lazylist, etc.).
Full Changelog: v0.2.0...v0.3.0