Skip to content

refactor(webapp): split main.tsx into app/ and player.tsx along the provider's seams - #362

Merged
InstaZDLL merged 3 commits into
mainfrom
refactor/webapp-split-shell-player
Oct 26, 2026
Merged

InstaZDLL merged 3 commits into
mainfrom
refactor/webapp-split-shell-player

Conversation

@InstaZDLL

@InstaZDLL InstaZDLL commented Oct 26, 2026 •

Copy link
Copy Markdown
Owner

Phase A of the webapp restructuring, for the two root-level giants: main.tsx (1,267 lines) and player/player.tsx (1,759 lines). Moves only; no import specifier changes — "./main" is the entry and "../player/player" still answers every name it exported.

Old → new

src/main.tsx (1,267) → bootstrap + src/app/

New file Lines What moved there
main.tsx 74 the bootstrap only: the CSS and font imports (unchanged, same order), createRouter, the declare module … Register / StaticDataRouteOption block, the query client, the service worker and the render
app/navigation-entries.ts 155 NAVIGATION_GROUPS, GROUP_LABELS, navigation, NavigationTarget
app/navigation.tsx 329 TAB_WAIT_MS, prepareTab, Brand, permitted, Navigation
app/more-sheet.tsx 100 MoreSheet
app/shell.tsx 118 SplashGate, ScopedOutlet, Shell, RootFrame
app/routes/root.tsx 75 rootRoute, loginRoute, shareRoute, offlineRoute, authedRoute, authorizeRoute
app/routes/library.tsx 137 albums, album, songs, artists, artist, genres, genre, history, stats, mix, search, favourites, playlists, folders, track editor
app/routes/listen.tsx 48 random, playing, queue, radio, podcasts
app/routes/settings.tsx 111 the settings hub and its six sections (admin with its guard), the three legacy redirects
app/routes/maintenance.tsx 95 the maintenance hub, its index and three sections, the two search validators, the three legacy redirects
app/route-tree.ts 104 routeTree, with the children in exactly the old order

Route paths, ids, beforeLoad guards, validateSearch, staticData and the order in the tree are untouched; the only edit to a route definition is the export in front of it. Type registration still works: a scratch <Link to="/no-such-route" /> fails tsc with the full list of registered paths.

src/player/player.tsx (1,759) → player.tsx as provider and face + siblings

New file Lines What moved there
player.tsx 535 PlayerProvider, and explicit re-exports of every name the file used to export
volume.ts 23 VOLUME_KEY (now exported for the provider), readStoredVolume
order.ts 145 RepeatMode, shuffledOrder, advance, continuationOf, followQueue, orderForQueue, retreat
context.ts 93 PlayerState, PlayerProgress, the three contexts, usePlayer, usePlayerProgress, useAsideProgress
replay-gain-graph.ts 156 useReplayGainGraph: the provider's lines from useReplayGainPreference() to prepareGraph (graph refs, the unmount teardown, wakeGraph, prepareGraph)
aside-playback.ts 349 useAsidePlayback: the whole "aside" section (station / episode element, bookmarks, playLive, playEpisode, toggleAside, seekAside, stopAside)
queue-element.ts 279 useQueueElement: the queue element's event effect, the ticketed source effect, the preload and the half-way scrobble
system-controls.ts 240 the two Media Session helpers, and useSystemControls: the Media Session handlers and metadata effects and the keyboard effect
bar.tsx 283 Scrubber, QueueScrubber, AsideScrubber, LevelControls, AsideBar, PlayerBar

Each extracted hook is a contiguous run of the provider's body, called from the provider exactly where that run used to be, so React sees the same hooks in the same order. Tests (order.test.ts, player.test.ts, volume.test.ts) are unchanged and still import from ./player.

CLAUDE.md's layout sentence now describes main.tsx as the bootstrap and names app/.

Proofs

  • vitest: 40 files, 323 tests before and after.
  • Playwright (bunx playwright test --list, listing only): 508 tests in 5 files before and after.
  • Hook order (a throwaway script that lists every use…( call in the old PlayerProvider, and in the new one with each extracted hook expanded in place): 88 calls before, 88 after, identical order.
  • Lines (a throwaway script: every non-blank, non-import line of the old file, as a multiset, against the new files, export prefixes ignored):
    • main.tsx: 1,089 old lines, 0 missing. The only new lines are the one-line headers.
    • player.tsx: 1,642 old lines; the 10 missing lines are all dependency arrays, listed below. Everything else in the new files is the wrappers (hook signatures, parameter types, the destructured returns and call sites) and the headers.

The one edit inside moved code: dependency arrays

Biome's useExhaustiveDependencies treats a useRef result or a useState setter as stable only when it is declared in the same function. Once a run of the provider becomes a hook that receives audio, positionRef, setPlaying… as arguments, the rule can no longer see that they are stable, and it raised 50 new warnings. The fix applied: the ref objects and setters were added to the dependency arrays that use them — never x.current. Each one is a useRef or a useState setter in the provider (or side, a useRef inside useAsidePlayback). Their identity never changes, so no effect or callback runs more often than before. Affected arrays (11):

  • replay-gain-graph.ts: the unmount effect [] → [audio, preloader]; prepareGraph + audio
  • aside-playback.ts: pauseQueue [] → [audio]; the element effect + audio
  • queue-element.ts: the event effect, the source effect, the preload (+ preloader) and the scrobble (+ submitted)
  • system-controls.ts: the handlers effect + audio, side; the keyboard effect + asideRef, asidePositionRef, positionRef
  • player.tsx: the volume effect + side

The other way out was a biome-ignore per hook. It was not chosen because it would turn the rule off for those hooks from now on, where the extra entries keep the rule checking them.

Checks run (in webapp/)

  • bunx biome check .: the same 10 warnings main has, all CSS noDescendingSpecificity; none new.
  • bun run typecheck: clean.
  • bun run test: 323 passed.
  • bun run build: passes.
  • Playwright is left to CI: only --list was run locally.

Noticed, not changed

  • PlayerProvider keeps a queueLength ref that is written on every render (queueLength.current = queue.length) and never read. Its comment says the ended handler reads it, but onEnd now goes through orderRef/indexRef.
  • The comment above the reading-order effect says the queue "is compared by identity and by prefix". continuationOf's own doc says it compares by entry identity and that the prefix test was dropped.
  • onEnd's "stop on the last track rather than stepping past it" explanation appears twice: once above the handler and again inside it.

Summary by CodeRabbit

  • Nouvelles fonctionnalités
    • Ajout d’une navigation adaptée aux écrans mobiles, avec un menu « Plus », des liens regroupés et un indicateur de chargement lors des changements de section.
    • Ajout de la lecture des stations et des podcasts, avec reprise des épisodes, mémorisation de la progression et commandes de lecture.
    • Amélioration de la lecture en file d’attente : lecture aléatoire, répétition, préchargement du morceau suivant et contrôle depuis le clavier et les commandes système.
    • Regroupement des paramètres et des outils de maintenance dans des sections dédiées. Les anciennes adresses redirigent vers les nouvelles.
  • Documentation
    • Précisions sur l’organisation de l’application.

….tsx into app/

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
…g it as the player's face

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
Signed-off-by: InstaZDLL <github.105mh@8shield.net>
@github-actions github-actions Bot added type: refactor Code refactoring scope: web Embedded web player (React) scope: routes Web routes scope: docs Docs, README, assets size: xl > 500 lines and removed type: refactor Code refactoring labels Oct 26, 2026
@coderabbitai

coderabbitai Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between f4b3952 and 9d7f736.

Files selected for processing: 21
  • CLAUDE.md
  • webapp/src/app/more-sheet.tsx
  • webapp/src/app/navigation-entries.ts
  • webapp/src/app/navigation.tsx
  • webapp/src/app/route-tree.ts
  • webapp/src/app/routes/library.tsx
  • webapp/src/app/routes/listen.tsx
  • webapp/src/app/routes/maintenance.tsx
  • webapp/src/app/routes/root.tsx
  • webapp/src/app/routes/settings.tsx
  • webapp/src/app/shell.tsx
  • webapp/src/main.tsx
  • webapp/src/player/aside-playback.ts
  • webapp/src/player/bar.tsx
  • webapp/src/player/context.ts
  • webapp/src/player/order.ts
  • webapp/src/player/player.tsx
  • webapp/src/player/queue-element.ts
  • webapp/src/player/replay-gain-graph.ts
  • webapp/src/player/system-controls.ts
  • webapp/src/player/volume.ts

Limit details: You’ve used the included review currently available. Your 84 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Walkthrough

Le changement extrait le routage, la navigation et la coque de l’application dans des modules dédiés. Il répartit aussi la gestion du lecteur entre des hooks, des contextes, des commandes système et des composants d’affichage.

Changes: Routage et navigation de l’application

Layer / File(s) Summary
Routes racine et coque authentifiée
webapp/src/app/routes/root.tsx, webapp/src/app/shell.tsx
Les routes racine distinguent les accès publics et authentifiés. La coque compose les fournisseurs et les éléments d’interface, puis affiche un état d’attente lorsque le périmètre de bibliothèque n’est pas prêt.
Routes par domaine et assemblage
webapp/src/app/routes/library.tsx, webapp/src/app/routes/listen.tsx, webapp/src/app/routes/maintenance.tsx, webapp/src/app/routes/settings.tsx, webapp/src/app/route-tree.ts, webapp/src/main.tsx, CLAUDE.md
Les routes sont réparties par domaine, avec validation de paramètres et redirections d’anciennes adresses. route-tree.ts les assemble; main.tsx importe cet arbre.
Navigation responsive et préchargement
webapp/src/app/navigation-entries.ts, webapp/src/app/navigation.tsx, webapp/src/app/more-sheet.tsx
Les entrées sont groupées et filtrées selon les autorisations. Les changements d’onglet peuvent attendre le préchargement des données, jusqu’à 1 500 ms. Sur mobile, les entrées secondaires apparaissent dans la feuille « More ».

Lecteur audio

Layer / File(s) Summary
État du lecteur et ordre de lecture
webapp/src/player/context.ts, webapp/src/player/order.ts, webapp/src/player/volume.ts, webapp/src/player/player.tsx
Les contextes exposent l’état et la progression. Les fonctions d’ordre gèrent le mélange, la répétition et le suivi des entrées de la file. La lecture du volume enregistré accepte les valeurs finies comprises entre 0 et 1.
Gestion des éléments audio
webapp/src/player/aside-playback.ts, webapp/src/player/queue-element.ts, webapp/src/player/replay-gain-graph.ts, webapp/src/player/player.tsx
Des hooks distincts gèrent la file, la lecture radio et podcast, les signets d’épisodes et ReplayGain. player.tsx appelle ces hooks à la place des implémentations locales précédentes.
Commandes système et barres de lecture
webapp/src/player/system-controls.ts, webapp/src/player/bar.tsx, webapp/src/player/player.tsx
Les commandes Media Session et les raccourcis clavier ciblent la source affichée. La barre présente les commandes de la file ou de l’aside, selon l’état du lecteur.

Priority: Low

Estimated code review effort: 4 (Complex) | ~60 minutes

Change: Refactor

Sequence Diagram(s)

sequenceDiagram
  actor Utilisateur
  participant Navigation
  participant prepareTab
  participant Routeur
  Utilisateur->>Navigation: clic sur un lien
  Navigation->>prepareTab: préparer les données de destination
  prepareTab-->>Navigation: résultat ou échec du préchargement
  Navigation->>Routeur: naviguer après préparation ou délai maximal
Loading
sequenceDiagram
  actor Utilisateur
  participant useAsidePlayback
  participant AudioAside
  participant AudioFile
  participant PlayerBar
  Utilisateur->>useAsidePlayback: demander la lecture d’un épisode
  useAsidePlayback->>AudioAside: charger le flux et la position de reprise
  AudioAside-->>useAsidePlayback: événement de lecture
  useAsidePlayback->>AudioFile: mettre en pause la file
  useAsidePlayback->>PlayerBar: fournir l’état de l’aside
Loading

Suggested labels: scope: web, size: xl

Merge Risk: Minimal · up to 9d7f7

This is a structural refactor of the web client's routing and player code that is meant to leave behavior unchanged. I found no concrete defect introduced by the change. Merge once the Playwright suite passes in CI.

Security architecture risk: Minimal · up to 9d7f7

The split preserves existing route protections, public access, playback ownership and cleanup behavior. No material security risk introduced or worsened by this PR was identified.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The reviewed extraction preserves the existing browser-session and playback authority scope. High-fanout player consumers continue through the same provider and compatible exports, and protected listen destinations retain their session-gated parentage.

Trust Boundaries and Controls

  • observed — Login, token-based sharing and offline access remain direct root-route children outside the authenticated group, as at the merge base. These public exceptions are not newly introduced exposure.

Resilience and Maintainability Implications

  • observed — Existing attempt-token and cancellation checks continue to prevent superseded stream requests from changing active playback. Bookmark writes remain serialized, with completion deletion and interruption persistence preserved. These controls retain failure containment across replacement, stopping and unmount.
Pre-merge checks: Passed 5
Check name Status Explanation
Title check Passed Le titre décrit bien le changement principal : découpage de main.tsx en app/ et de player.tsx en modules frères, un refactoring sans changement de comportement. Il est précis et correspond au résumé d…
Description check Passed La description suit le modèle : résumé, tableaux de changements, plan de test (commandes listées avec résultats locaux, Playwright laissé à la CI) et notes. Les cases de la section Test plan ne sont p…
Docstring Coverage Passed Docstring coverage is 81.82% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 33 functions across 20 files. (1 skipped: 1…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

Le titre décrit bien le changement principal : découpage de main.tsx en app/ et de player.tsx en modules frères, un refactoring sans changement de comportement. Il est précis et correspond au résumé des fichiers modifiés.

Full details: Description check

Explanation

La description suit le modèle : résumé, tableaux de changements, plan de test (commandes listées avec résultats locaux, Playwright laissé à la CI) et notes. Les cases de la section Test plan ne sont pas cochées, et la ligne de confirmation DCO/AGPL du modèle est absente. Le contenu est substantiel et couvre les points essentiels.

Full details: Docstring Coverage

Explanation

Docstring coverage is 81.82% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 33 functions across 20 files. (1 skipped: 1 unsupported.)

Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Warning

Some tools did not complete. Review the errors below.

🔧 Biome (2.5.15)
webapp/src/app/more-sheet.tsx

Biome could not lint this file: nested root configuration. Check the repository's Biome configuration and plugins.

webapp/src/app/navigation-entries.ts

Biome could not lint this file: nested root configuration. Check the repository's Biome configuration and plugins.

webapp/src/app/navigation.tsx

Biome could not lint this file: nested root configuration. Check the repository's Biome configuration and plugins.

  • 17 others

Usage-based review receipt

Note

This review was completed with usage-based billing: files reviewed beyond your plan's included limits are billed at $0.25/file. View usage-based billing.

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the type: refactor Code refactoring label Oct 26, 2026
@InstaZDLL InstaZDLL self-assigned this Oct 26, 2026
@InstaZDLL
InstaZDLL merged commit 8eec190 into main Oct 26, 2026
16 checks passed
@InstaZDLL
InstaZDLL deleted the refactor/webapp-split-shell-player branch October 26, 2026 23:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: docs Docs, README, assets scope: routes Web routes scope: web Embedded web player (React) size: xl > 500 lines type: refactor Code refactoring

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant