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
|
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
|
### Route Slices
|
||||||
|
|
||||||
- Keep route-specific code inside `webui/src/routes/<route>/`.
|
- 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