docs(webui): group migration planning docs
Keep WebUI migration plans next to the frontend code so route work has one predictable home. Standardize the set around one page migration overview plus per-route migration plan docs for future tasks.
This commit is contained in:
parent
fa3b7fbef3
commit
f6c6fc8579
4 changed files with 439 additions and 0 deletions
|
|
@ -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/<route>/`.
|
||||
|
|
|
|||
46
webui/docs/migration/README.md
Normal file
46
webui/docs/migration/README.md
Normal file
|
|
@ -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:
|
||||
- `<route>-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
|
||||
387
webui/docs/migration/stats-migration-plan.md
Normal file
387
webui/docs/migration/stats-migration-plan.md
Normal file
|
|
@ -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
|
||||
Loading…
Reference in a new issue