How xndr2/listening-stats works
1. What this repo is
- listening-stats is a Spicetify custom app: a TypeScript/React app that runs inside the Spotify desktop client.
- It has two runtime halves: the dashboard UI (src/app) and the background extension/tracker (src/extension) that records listening activity and maintains local state.
2. Top-level architecture
- The code is organized around a shared domain layer in src/shared plus two thin entrypoints:
- src/app/index.tsx boots the dashboard UI.
- src/extension/index.ts boots the extension/tracker logic.
- This split is important: the extension is responsible for capturing and storing data, while the app is responsible for reading, aggregating, and visualizing it.
3. Data collection boundary
- src/extension/tracker contains the listening-event pipeline: finite-state tracking (fsm.ts), health checks, watchdog logic, and settings integration.
- The tracker feeds play/activity events into shared storage utilities, so the UI can later query historical data without needing Spotify playback internals directly.
- This is the main boundary between “live capture” and “analytics presentation.”
4. Local persistence and storage layer
- src/shared/storage is the persistence subsystem: database access, backups, import/export, integrity checks, migrations, URI resolution, and play-event storage.
- This layer gives the repo a durable local source of truth for listening history, independent of external providers.
- Synthetic URIs and URI resolution suggest the app normalizes Spotify content identifiers before they are used by stats and UI features.
5. Stats provider abstraction
- src/shared/stats defines a provider model and the aggregation logic used to produce dashboards.
- It includes a local provider plus provider-specific code for stats.fm and supports Last.fm through shared API/client/cache modules.
- The provider abstraction is a core architectural decision: the rest of the UI consumes standardized stats, not provider-specific payloads.
6. External service integrations
- src/shared/api contains client, cache, and resilience code for remote calls, including Last.fm, stats.fm, and generic async/circuit-breaker helpers.
- src/shared/update and src/shared/announcements integrate with GitHub-hosted release/update and remote announcement content.
- src/app/world-charts-service.ts and src/shared/types/world-charts.ts indicate a separate remote data source for global chart data shown in the app.
7. Dashboard composition
- src/app/components is a feature-oriented UI layer: overview cards, activity sections, top lists, listening patterns, recently played, heatmaps, share modal, setup wizard, update modal, and guided tour.
- src/app/App.tsx likely orchestrates the main dashboard layout and section visibility, while reusable controls like tabs, pills, and segmented controls support filtering and period selection.
- The UI is modular enough that the dashboard can be rearranged, hidden, or customized without changing the underlying data model.
8. Settings and onboarding
- src/app/components/settings contains the settings modal and tabs for providers, tracking, data, display, and about.
- src/app/preferences.ts, src/app/capabilities.ts, and src/app/tour.ts suggest the app adapts to platform/support constraints and first-run state.
- SetupWizard, GuidedTour, and TourPopover form the onboarding path, while settings persist configuration that affects both tracker behavior and which providers are active.
9. Shared domain types and utilities
- src/shared/types centralizes the application contracts for play events, stats, stats.fm, Spotify-specific typings, and world charts.
- src/shared/constants and src/shared/errors.ts provide cross-cutting primitives used by both runtime halves.
- This shared layer reduces drift between the collector, storage, provider, and UI code paths.
10. Export, sharing, and presentation features
- The app supports share cards and exports via ShareModal, plus data presentation widgets like ActivityChart, CalendarHeatmap, TopGenres, and TopLists.
- src/shared/stats/progressive.ts and related modules imply incremental loading/aggregation so the dashboard can render before all stats are fully computed.
- The architecture is optimized for browsing large listening histories with progressive hydration rather than waiting for one monolithic fetch.
11. Build, test, and release pipeline
- The repo is TypeScript-based, with React UI code, a custom build script (build.mjs), and install scripts for macOS/Linux and Windows.
- vitest.config.ts plus the large src/test suite show architecture is heavily unit/integration tested across storage, provider logic, UI components, and tracker behavior.
- GitHub Actions in .github/workflows handle CI, build, and release packaging, which aligns with shipping a distributable Spicetify custom app zip.
12. Where to draw the architecture diagram
- A good diagram would show:
- Spotify/Spicetify runtime containing both the extension/tracker and the React dashboard
- Local storage as the central data hub
- Provider integrations for stats.fm and Last.fm feeding/enriching stats
- Update/announcement/GitHub release services as ancillary external dependencies
- Shared domain modules bridging all layers
- The key architectural theme is offline-first local analytics with optional cloud provider enrichment.
Main entry points: provider.ts Stats core · App.tsx Dashboard · fsm.ts Event tracker · OverviewSection.tsx UI modules
Components 19
Runtime
Spotify/Spicetify entrypoints and in-client execution
- Spotify app · Spicetify host environment where the app and extension run
Dashboard
React UI for viewing listening statistics
- UI boot · Bootstraps the dashboard app
src/app/index.tsx - Dashboard · Top-level dashboard composition and state wiring
src/app/App.tsx - UI modules · Charts, lists, cards, modals, and settings views
src/app/components/OverviewSection.tsx - Settings flow · Setup wizard, guided tour, and settings tabs
src/app/components/settings/SettingsModal.tsx - World charts · Dashboard page for global chart data
src/app/components/WorldChartsPage.tsx
Tracker
Background extension that captures listening activity
- Tracker boot · Bootstraps the background extension
src/extension/index.ts - Event tracker · Listening-state machine plus health and watchdog logic
src/extension/tracker/fsm.ts - Tracker config · Extension settings integration and tracking controls
src/extension/tracker/settings.ts
Shared Core
Common domain, storage, provider, and service code
- Storage · Local database, backups, import/export, migrations, and play-event storage
src/shared/storage/db.ts - Stats core · Provider model, local stats, progressive loading, and enrichment
src/shared/stats/provider.ts - stats.fm · stats.fm-specific provider implementation
src/shared/stats/statsfm-provider.ts - API clients · Last.fm, stats.fm, cache, and resilience helpers
src/shared/api/lastfm-client.ts - Updates · Changelog, release info, update checks, and announcements
src/shared/update/update-check.ts - Domain types · Shared play-event, stats, Spotify, and world-chart contracts
src/shared/types/stats.ts - Constants · Cross-cutting constants and shared error definitions
src/shared/constants/events.ts
External Services
Remote systems the app talks to
- Last.fm · Scrobble/statistics provider used through shared clients
- GitHub · Release and announcement source
- World charts API · Remote source for global chart data
Connections 22
- Spotify app → UI boot: loads · Spotify/Spicetify launches the dashboard entrypoint
- Spotify app → Tracker boot: loads · Spotify/Spicetify launches the tracker extension
- UI boot → Dashboard: boots · UI entrypoint renders the main dashboard shell
- Dashboard → UI modules: composes · Dashboard is assembled from reusable feature components
- Dashboard → Settings flow: controls · Shell coordinates onboarding and settings surfaces
- Dashboard → World charts: navigates · Dashboard includes a dedicated world charts view
- Tracker boot → Event tracker: starts · Extension entrypoint starts the listening tracker pipeline
- Tracker boot → Tracker config: configures · Extension wires tracking behavior from settings
- Event tracker → Storage: writes · Captured play and activity events are persisted locally
- Event tracker → Domain types: emits · Tracker produces shared play-event contracts
- Event tracker → Constants: uses · Tracker relies on shared event and error primitives
- Storage → Stats core: feeds · Persisted history becomes the source of stats aggregation
- Stats core → stats.fm: adapts · Stats core plugs in provider-specific stats.fm logic
- Stats core → API clients: queries · Aggregation can consult remote API/client layers
- Stats core → Domain types: models · Stats logic is defined by shared contracts
- API clients → Last.fm: calls · Last.fm client code talks to the external service
- Updates → GitHub: fetches · Update and announcement logic reads GitHub-hosted content
- World charts → World charts API: fetches · World charts view reads remote chart data
- UI modules → Storage: reads · Dashboard components render from locally stored history
- UI modules → Stats core: reads · UI consumes standardized aggregated stats
- Settings flow → Tracker config: updates · Settings UI changes tracker-related configuration
- Settings flow → API clients: configures · Provider settings affect external integrations