diff --git a/webui/README.md b/webui/README.md index d32587c2..0074b850 100644 --- a/webui/README.md +++ b/webui/README.md @@ -79,6 +79,12 @@ webui/src/ test/ Shared test utilities and setup helpers ``` +Migration planning docs live under `webui/docs/migration/`. + +- keep the high-level route backlog there +- add one route-specific sketch per migration task +- keep migration notes close to the WebUI code rather than the repo root + ### Route Slices - Keep route-specific code inside `webui/src/routes//`. diff --git a/webui/docs/migration/README.md b/webui/docs/migration/README.md new file mode 100644 index 00000000..a7a9d5e3 --- /dev/null +++ b/webui/docs/migration/README.md @@ -0,0 +1,46 @@ +# WebUI Migration Docs + +This folder is the home for React migration planning work inside `webui`. + +## Purpose + +- Keep migration planning close to the code it describes. +- Separate WebUI migration docs from repo-level product or backend docs. +- Give each route migration a predictable place to live. + +## Current Docs + +- [page-migration-overview.md](./page-migration-overview.md) + - high-level route inventory + - migration waves + - cross-route risk assessment +- [stats-migration-plan.md](./stats-migration-plan.md) + - route-specific migration plan for `stats` + +## Naming Guidance + +- Keep one high-level backlog / sequencing doc: + - `page-migration-overview.md` +- Use one route-specific plan per migration task: + - `-migration-plan.md` + +Examples: + +- `search-migration-plan.md` +- `watchlist-migration-plan.md` +- `library-migration-plan.md` + +## Scope + +Use this folder for: + +- migration sequencing +- route-specific implementation sketches +- React ownership cutover notes +- shell handoff notes tied to WebUI page migrations + +Do not use this folder for: + +- generic product docs +- backend architecture notes unrelated to WebUI migration +- permanent user-facing documentation diff --git a/docs/webui-page-migration-analysis.md b/webui/docs/migration/page-migration-overview.md similarity index 100% rename from docs/webui-page-migration-analysis.md rename to webui/docs/migration/page-migration-overview.md diff --git a/webui/docs/migration/stats-migration-plan.md b/webui/docs/migration/stats-migration-plan.md new file mode 100644 index 00000000..d74215cc --- /dev/null +++ b/webui/docs/migration/stats-migration-plan.md @@ -0,0 +1,387 @@ +# WebUI Stats React Migration Sketch + +Snapshot date: 2026-05-14 + +## Goal + +- Migrate `stats` from the legacy shell to the React route host. +- Replace the global `Chart.js` CDN script with route-local React chart components. +- Use the `issues` route slice as the structural reference, but add a few stronger conventions for data-heavy read-only pages. + +## Why `stats` Is The Right Next Route + +- The route is shell-local today. +- The activation path is narrow. +- The page has real async data loading and interaction. +- The page is complex enough to validate query conventions, search-param state, and route-local chart components. +- The page does not currently drive broad shell-global workflows. + +## Current Legacy Shape + +Page surface in `webui/index.html`: + +- Header + - time range buttons + - last synced label + - manual sync action +- Overview cards +- Left column + - listening activity chart + - genre breakdown chart + - recently played list +- Right column + - top artists + - top albums + - top tracks +- Full-width sections + - library health + - library disk usage + - database storage +- Empty state + +Legacy JS responsibilities in `webui/static/stats-automations.js`: + +- page initialization +- range switch handling +- data fetch orchestration +- formatting helpers +- chart instantiation and teardown +- ranked list rendering +- cross-page deep links into library / artist detail +- playback handoff for recent and top tracks + +Backend endpoints already split cleanly: + +- `GET /api/stats/cached` +- `GET /api/stats/db-storage` +- `GET /api/stats/library-disk-usage` +- `POST /api/listening-stats/sync` +- `GET /api/listening-stats/status` + +There are also narrower stats endpoints in the backend, but the current page already gets most of its main payload from the cached route. + +## Library Choice + +Recommended charting library: + +- `recharts` + +Reasoning: + +- React-native component model +- good fit for bar + doughnut-style dashboards +- easy to split into small route-local components +- easier to theme from CSS variables than raw imperative chart setup +- easier to test than a canvas-first imperative path + +Not recommended for this migration: + +- `react-chartjs-2` + - better for parity-only migration + - still keeps the mental model close to Chart.js +- `visx` + - stronger for bespoke visualization systems + - more work than this page needs + +## Proposed Route Slice + +```text +webui/src/routes/stats/ + route.tsx + -stats.types.ts + -stats.api.ts + -stats.helpers.ts + -stats.api.test.ts + -stats.helpers.test.ts + -ui/ + stats-page.tsx + stats-page.module.css + stats-header.tsx + stats-overview-cards.tsx + stats-empty-state.tsx + stats-ranked-list.tsx + stats-recent-plays.tsx + stats-library-health.tsx + stats-disk-usage.tsx + stats-activity-chart.tsx + stats-genre-chart.tsx + stats-db-storage-chart.tsx +``` + +## Proposed Route Responsibilities + +`route.tsx` + +- declare `/stats` +- validate search params +- gate route through `bridge.isPageAllowed('stats')` +- preload the shell context +- load the main cached stats payload plus listening-status payload +- optionally preload disk usage and db storage if we want zero-layout-shift first render + +`-stats.types.ts` + +- search param schema +- response payload types +- normalized display shapes +- chart row types + +`-stats.api.ts` + +- query keys +- fetchers for: + - cached stats + - listening status + - db storage + - library disk usage +- mutation helper for manual sync +- invalidation helpers + +`-stats.helpers.ts` + +- range labels +- numeric and duration formatters +- disk size formatters +- chart data shaping +- legend shaping +- safe fallbacks for empty server responses + +`-ui/stats-page.tsx` + +- page composition +- search-param driven range selection +- section layout +- empty-state branching + +## Search Params + +Use search params for state that should survive reloads and linking: + +- `range` + +Recommended values: + +- `7d` +- `30d` +- `12m` +- `all` + +This is the one clear page-state value worth encoding in the URL. Everything else can remain derived from server data. + +## Query Model + +Recommended split: + +- primary query: + - `statsCachedQueryOptions(profileId, range)` +- secondary queries: + - `statsListeningStatusQueryOptions(profileId)` + - `statsDbStorageQueryOptions(profileId)` + - `statsLibraryDiskUsageQueryOptions(profileId)` + +Why this split: + +- `cached` is the real page backbone +- `db-storage` and `library-disk-usage` are already separate in the backend +- they can render as progressively enhanced cards without blocking the whole route +- `listening-stats/status` updates the sync label and complements the sync mutation + +Recommended route-loader behavior: + +- always ensure: + - cached stats + - listening status +- optional: + - db storage + - disk usage + +If we want a snappier first migration, we should keep the last two as client-side `useQuery` calls rather than route-loader requirements. + +## Component Sketch + +`StatsPage` + +- calls `useReactPageShell('stats')` +- reads `range` from route search +- renders: + - `StatsHeader` + - `StatsOverviewCards` + - `StatsEmptyState` or main sections + +`StatsHeader` + +- range segmented control +- last synced text +- sync button mutation + +`StatsOverviewCards` + +- five summary cards + +`StatsActivityChart` + +- Recharts `BarChart` +- responsive container +- route-local tooltip +- accepts already-shaped rows + +`StatsGenreChart` + +- Recharts `PieChart` +- legend rendered in React markup beside the chart +- top-10 clipping stays in helpers + +`StatsDbStorageChart` + +- Recharts `PieChart` +- custom center label rendered in React +- legend list rendered beside chart + +`StatsRankedList` + +- shared component for artists / albums / tracks +- variant props for: + - artwork + - subtitle/meta + - count label + - optional play action + - optional artist-detail deep link + +`StatsRecentPlays` + +- simple list component +- play action + +`StatsLibraryHealth` + +- overview metrics +- format breakdown bar +- enrichment coverage rows + +`StatsDiskUsage` + +- total bytes row +- pending/deep-scan message +- per-format horizontal bars + +## Recharts Mapping + +Legacy Chart.js to React mapping: + +- listening activity + - from imperative `new Chart(... type: 'bar')` + - to `ResponsiveContainer` + `BarChart` + `Bar` + `XAxis` + `YAxis` + `Tooltip` +- genre breakdown + - from doughnut chart + - to `PieChart` + `Pie` + custom legend +- database storage + - from doughnut chart with center total overlay + - to `PieChart` + `Pie` + React-rendered center label + +Suggested chart convention: + +- keep all chart data shaping outside the chart components +- chart components should receive already-normalized rows and colors +- never read directly from raw server payloads inside Recharts markup + +## CSS Strategy + +Recommended first pass: + +- create `stats-page.module.css` +- port stats-specific selectors from `webui/static/style.css` +- keep class names semantically similar to reduce migration risk + +Suggested approach: + +- move only the selectors needed by the React route +- leave legacy stats selectors in place until the route flip is complete +- after the React route owns `stats`, remove unused legacy selectors in a cleanup pass + +Do not try to redesign the page during the migration. + +## Shell And Routing Changes + +When the route is ready: + +1. Add `webui/src/routes/stats/route.tsx` +2. Regenerate the TanStack route tree if needed +3. Change `stats` from `legacy` to `react` in `webui/src/platform/shell/route-manifest.ts` +4. Keep the legacy `stats-page` DOM in `webui/index.html` during the initial cutover if that reduces risk +5. Remove legacy activation from `webui/static/init.js` once React ownership is confirmed +6. Remove the global Chart.js script from `webui/index.html` + +## Incremental Migration Order + +Recommended order: + +1. Add types, API layer, and helpers +2. Build the React route with plain markup and no charts yet +3. Port overview, ranked lists, recent plays, and empty state +4. Port library health and disk usage +5. Port Recharts activity, genre, and db storage charts +6. Flip route ownership from legacy to React +7. Remove global Chart.js import +8. Delete or shrink legacy `stats` logic from `stats-automations.js` + +This order gives us a working React page before charting becomes the critical path. + +## Testing Sketch + +Unit tests: + +- `-stats.helpers.test.ts` + - range formatting + - duration formatting + - db storage grouping into `Other` + - genre top-10 shaping + - disk usage empty-state shaping + +API tests: + +- `-stats.api.test.ts` + - cached stats success / error + - listening status success / error + - db storage success / error + - disk usage success / error + - sync mutation success / error + +Route / component tests: + +- initial render for default `range=7d` +- changing range updates the URL and query key +- empty state renders when `overview.total_plays === 0` +- ranked artist click deep-links to library / artist detail +- track play action triggers the expected handoff +- sync action shows pending state and invalidates relevant queries + +Playwright is optional for the first pass. + +## Decisions To Keep Simple + +- Keep the existing page structure. +- Keep the current backend endpoint split. +- Keep the current time-range set. +- Reuse the existing shell deep-link behavior for library and playback. +- Use Recharts only inside `stats` first. + +## Follow-Up Opportunities + +- Extract shared chart colors into route-local constants or a small shared viz helper. +- Consider a tiny `components/charts/` layer only after a second React page needs charts. +- Revisit whether `stats/cached` should remain the primary page payload or whether the route should fan out to narrower endpoints later. + +## Recommendation + +The first implementation should optimize for: + +- parity +- clear route-local boundaries +- removal of global `Chart.js` +- reusable data/query conventions + +It should not optimize for: + +- visual redesign +- a cross-app chart abstraction +- backend reshaping