# xndr2/listening-stats architecture

> Architecture diagram of the GitHub repository xndr2/listening-stats, generated by GitDiagram from its file tree, README and a sample of source files (last updated 2026-05-10). It is an AI-made overview: check details against the source.

- Interactive diagram: https://gitdiagram.com/xndr2/listening-stats
- Repository: https://github.com/xndr2/listening-stats

## Overview

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: [`src/shared/stats/provider.ts`](https://github.com/xndr2/listening-stats/blob/main/src/shared/stats/provider.ts) (Stats core), [`src/app/App.tsx`](https://github.com/xndr2/listening-stats/blob/main/src/app/App.tsx) (Dashboard), [`src/extension/tracker/fsm.ts`](https://github.com/xndr2/listening-stats/blob/main/src/extension/tracker/fsm.ts) (Event tracker), [`src/app/components/OverviewSection.tsx`](https://github.com/xndr2/listening-stats/blob/main/src/app/components/OverviewSection.tsx) (UI modules)

## Diagram (Mermaid)

```mermaid
flowchart TD

subgraph group_runtime["Runtime"]
  node_spotify_runtime(("Spotify app<br/>host runtime"))
end

subgraph group_app["Dashboard"]
  node_app_entry["UI boot<br/>entrypoint<br/>[index.tsx]"]
  node_app_shell["Dashboard<br/>react shell<br/>[App.tsx]"]
  node_ui_components["UI modules<br/>feature UI"]
  node_onboarding_settings["Settings flow<br/>settings UI<br/>[SettingsModal.tsx]"]
  node_world_charts_page["World charts<br/>remote view"]
end

subgraph group_extension["Tracker"]
  node_extension_entry["Tracker boot<br/>entrypoint<br/>[index.ts]"]
  node_tracker_pipeline["Event tracker<br/>fsm tracker<br/>[fsm.ts]"]
  node_tracker_settings["Tracker config<br/>settings bridge<br/>[settings.ts]"]
end

subgraph group_shared["Shared Core"]
  node_storage_layer[("Storage<br/>persistence<br/>[db.ts]")]
  node_stats_layer["Stats core<br/>aggregation<br/>[provider.ts]"]
  node_statsfm_provider["stats.fm<br/>provider"]
  node_api_clients["API clients<br/>remote client layer<br/>[lastfm-client.ts]"]
  node_update_announcements["Updates<br/>release content<br/>[update-check.ts]"]
  node_domain_types["Domain types<br/>contracts<br/>[stats.ts]"]
  node_constants_errors["Constants<br/>shared primitives<br/>[events.ts]"]
end

subgraph group_external["External Services"]
  node_lastfm_service(("Last.fm<br/>external service"))
  node_github_service(("GitHub<br/>external service"))
  node_world_charts_service(("World charts API<br/>external service"))
end

node_spotify_runtime -->|"loads"| node_app_entry
node_spotify_runtime -->|"loads"| node_extension_entry
node_app_entry -->|"boots"| node_app_shell
node_app_shell -->|"composes"| node_ui_components
node_app_shell -->|"controls"| node_onboarding_settings
node_app_shell -->|"navigates"| node_world_charts_page
node_extension_entry -->|"starts"| node_tracker_pipeline
node_extension_entry -->|"configures"| node_tracker_settings
node_tracker_pipeline -->|"writes"| node_storage_layer
node_tracker_pipeline -->|"emits"| node_domain_types
node_tracker_pipeline -->|"uses"| node_constants_errors
node_storage_layer -->|"feeds"| node_stats_layer
node_stats_layer -->|"adapts"| node_statsfm_provider
node_stats_layer -->|"queries"| node_api_clients
node_stats_layer -->|"models"| node_domain_types
node_api_clients -->|"calls"| node_lastfm_service
node_update_announcements -->|"fetches"| node_github_service
node_world_charts_page -->|"fetches"| node_world_charts_service
node_ui_components -->|"reads"| node_storage_layer
node_ui_components -->|"reads"| node_stats_layer
node_onboarding_settings -->|"updates"| node_tracker_settings
node_onboarding_settings -->|"configures"| node_api_clients

click node_app_entry "https://github.com/xndr2/listening-stats/blob/main/src/app/index.tsx"
click node_app_shell "https://github.com/xndr2/listening-stats/blob/main/src/app/App.tsx"
click node_ui_components "https://github.com/xndr2/listening-stats/blob/main/src/app/components/OverviewSection.tsx"
click node_onboarding_settings "https://github.com/xndr2/listening-stats/blob/main/src/app/components/settings/SettingsModal.tsx"
click node_world_charts_page "https://github.com/xndr2/listening-stats/blob/main/src/app/components/WorldChartsPage.tsx"
click node_extension_entry "https://github.com/xndr2/listening-stats/blob/main/src/extension/index.ts"
click node_tracker_pipeline "https://github.com/xndr2/listening-stats/blob/main/src/extension/tracker/fsm.ts"
click node_tracker_settings "https://github.com/xndr2/listening-stats/blob/main/src/extension/tracker/settings.ts"
click node_storage_layer "https://github.com/xndr2/listening-stats/blob/main/src/shared/storage/db.ts"
click node_stats_layer "https://github.com/xndr2/listening-stats/blob/main/src/shared/stats/provider.ts"
click node_statsfm_provider "https://github.com/xndr2/listening-stats/blob/main/src/shared/stats/statsfm-provider.ts"
click node_api_clients "https://github.com/xndr2/listening-stats/blob/main/src/shared/api/lastfm-client.ts"
click node_update_announcements "https://github.com/xndr2/listening-stats/blob/main/src/shared/update/update-check.ts"
click node_domain_types "https://github.com/xndr2/listening-stats/blob/main/src/shared/types/stats.ts"
click node_constants_errors "https://github.com/xndr2/listening-stats/blob/main/src/shared/constants/events.ts"

classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_spotify_runtime toneBlue
class node_app_entry,node_app_shell,node_ui_components,node_onboarding_settings,node_world_charts_page toneAmber
class node_extension_entry,node_tracker_pipeline,node_tracker_settings toneMint
class node_storage_layer,node_stats_layer,node_statsfm_provider,node_api_clients,node_update_announcements,node_domain_types,node_constants_errors toneRose
class node_lastfm_service,node_github_service,node_world_charts_service toneIndigo
```

## Components

### Runtime

Spotify/Spicetify entrypoints and in-client execution

- **Spotify app**: host runtime · Spicetify host environment where the app and extension run

### Dashboard

React UI for viewing listening statistics

- [UI boot](https://github.com/xndr2/listening-stats/blob/main/src/app/index.tsx): entrypoint · Bootstraps the dashboard app · `src/app/index.tsx`
- [Dashboard](https://github.com/xndr2/listening-stats/blob/main/src/app/App.tsx): react shell · Top-level dashboard composition and state wiring · `src/app/App.tsx`
- [UI modules](https://github.com/xndr2/listening-stats/blob/main/src/app/components/OverviewSection.tsx): feature UI · Charts, lists, cards, modals, and settings views · `src/app/components/OverviewSection.tsx`
- [Settings flow](https://github.com/xndr2/listening-stats/blob/main/src/app/components/settings/SettingsModal.tsx): settings UI · Setup wizard, guided tour, and settings tabs · `src/app/components/settings/SettingsModal.tsx`
- [World charts](https://github.com/xndr2/listening-stats/blob/main/src/app/components/WorldChartsPage.tsx): remote view · Dashboard page for global chart data · `src/app/components/WorldChartsPage.tsx`

### Tracker

Background extension that captures listening activity

- [Tracker boot](https://github.com/xndr2/listening-stats/blob/main/src/extension/index.ts): entrypoint · Bootstraps the background extension · `src/extension/index.ts`
- [Event tracker](https://github.com/xndr2/listening-stats/blob/main/src/extension/tracker/fsm.ts): fsm tracker · Listening-state machine plus health and watchdog logic · `src/extension/tracker/fsm.ts`
- [Tracker config](https://github.com/xndr2/listening-stats/blob/main/src/extension/tracker/settings.ts): settings bridge · Extension settings integration and tracking controls · `src/extension/tracker/settings.ts`

### Shared Core

Common domain, storage, provider, and service code

- [Storage](https://github.com/xndr2/listening-stats/blob/main/src/shared/storage/db.ts): persistence · Local database, backups, import/export, migrations, and play-event storage · `src/shared/storage/db.ts`
- [Stats core](https://github.com/xndr2/listening-stats/blob/main/src/shared/stats/provider.ts): aggregation · Provider model, local stats, progressive loading, and enrichment · `src/shared/stats/provider.ts`
- [stats.fm](https://github.com/xndr2/listening-stats/blob/main/src/shared/stats/statsfm-provider.ts): provider · stats.fm-specific provider implementation · `src/shared/stats/statsfm-provider.ts`
- [API clients](https://github.com/xndr2/listening-stats/blob/main/src/shared/api/lastfm-client.ts): remote client layer · Last.fm, stats.fm, cache, and resilience helpers · `src/shared/api/lastfm-client.ts`
- [Updates](https://github.com/xndr2/listening-stats/blob/main/src/shared/update/update-check.ts): release content · Changelog, release info, update checks, and announcements · `src/shared/update/update-check.ts`
- [Domain types](https://github.com/xndr2/listening-stats/blob/main/src/shared/types/stats.ts): contracts · Shared play-event, stats, Spotify, and world-chart contracts · `src/shared/types/stats.ts`
- [Constants](https://github.com/xndr2/listening-stats/blob/main/src/shared/constants/events.ts): shared primitives · Cross-cutting constants and shared error definitions · `src/shared/constants/events.ts`

### External Services

Remote systems the app talks to

- **Last.fm**: external service · Scrobble/statistics provider used through shared clients
- **GitHub**: external service · Release and announcement source
- **World charts API**: external service · Remote source for global chart data

## Connections

- 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
