Skip to content

refactor(webapp): split the three stylesheets into ordered parts - #361

Merged
InstaZDLL merged 2 commits into
mainfrom
refactor/webapp-split-styles
Oct 26, 2026
Merged

InstaZDLL merged 2 commits into
mainfrom
refactor/webapp-split-styles

Conversation

@InstaZDLL

@InstaZDLL InstaZDLL commented Oct 26, 2026 •

Copy link
Copy Markdown
Owner

Phase A of the webapp restructuring, stylesheets: styles.css (4,582 lines), refresh.css (1,229) and fluid.css (1,788) split into parts along their real sections. Moves only — no rule changed, added, removed or reordered.

main.tsx is untouched: the three entry files keep their names and their order there (styles.css, refresh.css, the fonts, fluid.css). Each entry is now an ordered list of @imports, which Vite inlines at build.

Old → new

Each part is one contiguous range of the old sheet, in the old order. Line ranges are in the old file.

styles.css → styles/ (the entry: a one-line header and 14 @imports, 16 lines)

Part Old lines Lines Holds
base.css 1–307 308 tokens, reset, form controls, type, skip link
shell.css 308–829 523 shell, sidebar, navigation, pickers, account chip; the not-found, share and sign-in pages
catalog.css 830–1338 510 page headers and controls, the cover grid, detail and artist heads, lists, the song table, ratings
panels.css 1339–1548 211 forms, collections and admin panels, resource lists, empty and error states, skeletons
player.css 1549–1739 192 the player bar
phone.css 1740–1996 258 the 820 px and 680 px layouts, reduced motion
playing.css 1997–2407 412 genres, lyrics, the playing screen's columns, view switcher, section menu, lyrics view, immersive view
admin.css 2408–2879 473 scans, tokens, members, the tag editor, uploads, playlist import, canvas, scrobbling
menus.css 2880–3144 266 the more sheet, the track menu, track properties
folders.css 3145–3495 352 the folders page and picker, "Needs attention"
artist-hero.css 3496–3714 220 the artist header's backdrop, image controls, the Deezer picker
maintenance.css 3715–3938 225 the quality page, missing files, a library's danger zone
mixes-and-stats.css 3939–4268 331 toggle rows, smart playlists, podcast episodes, the mixes shelf, statistics
workspace.css 4269–4582 316 the workspace pages: radio, podcasts, folders, the Maintenance hub

refresh.css → refresh/ (entry: 8 lines)

Part Old lines Lines Holds
base.css 1–139 140 the quieter frame's tokens, type and controls
shell.css 140–301 163 shell, sidebar, page header
surfaces.css 302–564 264 tiles, panels, the admin and settings pages, ReplayGain, list rows
queue.css 565–766 203 saved playback positions, queue rows
album.css 767–1088 323 the album page
login-player-responsive.css 1089–1229 143 sign-in, the player bar, the narrow layouts, reduced motion

fluid.css → fluid/ (entry: the sheet's own 30-line banner, kept where it was, and 7 @imports — 38 lines)

Part Old lines Lines Holds
tokens.css 32–222 192 tokens: shared, the dark material, the light one
base.css 223–470 249 ground and atmosphere, type, controls
rail.css 471–704 235 the rail
page.css 705–1107 404 the page, tiles, tables, surfaces, album and artist heads
player-bar.css 1108–1365 259 the player bar
menus-sign-in-motion.css 1366–1567 203 menus and sheets, sign-in, phones, motion, the track menu
theme-picker.css 1568–1788 223 the theme picker

Each part's count is its old range plus its one-line header and the blank line after it, minus the blank line that separated it from the next part. Largest file: 523 lines (cap 800).

Tests. ?raw of an entry is now a list of paths and not one rule, so styles.test.ts and design-tokens/themes-contrast.test.ts read the sheets through a new src/styles/sheets.ts, which inlines each entry's parts in its @import order (via import.meta.glob, so the order is read from the entry, not written a second time). It throws at import if a part is imported by no entry or an entry names a part that does not exist. Each test's change is its import line only; every assertion is as it was. themes-contrast.test.ts sits under design-tokens/ and is not moved.

Proofs

CSS, source. A script strips each part's one-line header (with the blank line after it) and the entries' @import lines, joins the parts in @import order with the blank line that separated them, and compares to the old file byte for byte:

styles.css: 14 parts, 4582 lines, rebuilt 98098 chars vs 98098 -> IDENTICAL
refresh.css: 6 parts, 1229 lines, rebuilt 23802 chars vs 23802 -> IDENTICAL
fluid.css: 7 parts, 1788 lines, rebuilt 47825 chars vs 47825 -> IDENTICAL

The check fails as it should: swapping catalog.css and panels.css in styles.css gives styles.css … -> DIFFERENT, exit 1.

The check script
import { readFileSync } from "node:fs";
import { dirname, join } from "node:path";

const [origDir, stylesDir] = process.argv.slice(2);
const HEADER = /^\/\* [^\n]* \*\/\n\n/;
const IMPORT = /^@import "(\.\/[^"]+)";$/;
let failed = false;

for (const entry of ["styles.css", "refresh.css", "fluid.css"]) {
  const original = readFileSync(join(origDir, entry), "utf8");
  const lines = readFileSync(join(stylesDir, entry), "utf8").slice(0, -1).split("\n");
  const imports = lines.filter((l) => IMPORT.test(l));
  // Everything but the @import lines sits ahead of them.
  const rest = lines.slice(0, lines.length - imports.length);
  if (lines.slice(rest.length).some((l) => !IMPORT.test(l))) throw new Error(entry);

  // A one-line header plus its blank line is ours; a longer head is the sheet's
  // own and stays part of what is compared.
  const kept = `${rest.join("\n")}\n`;
  const head = /^\/\* [^\n]* \*\/\n\n$/.test(kept) ? "" : kept;
  const parts = imports.map((line) => {
    const path = join(dirname(join(stylesDir, entry)), line.match(IMPORT)[1]);
    const part = readFileSync(path, "utf8");
    if (!HEADER.test(part)) throw new Error(`no header: ${path}`);
    return part.replace(HEADER, "");
  });
  const rebuilt = head + parts.join("\n");

  const same = rebuilt === original;
  failed ||= !same;
  console.log(`${entry}: ${imports.length} parts, … -> ${same ? "IDENTICAL" : "DIFFERENT"}`);
}
process.exit(failed ? 1 : 0);

CSS, built. bun run build on main and on this branch produce the same single bundle, dist/assets/index-ac4mmiW1.css (111.50 kB), byte-identical: same content hash in the name, cmp clean, sha256 4de7650d…10b6 on both. No @import survives in it.

The tests still read every rule. A throwaway test (not committed) parsed sheets.ts's output and the old files with postcss: same counts, and the same text once comments and whitespace are removed. The raw entry alone parses to 0 rules — what the tests would have read without the helper.

styles.css  inlined {rules 624, decls 2140, atrules 22, top 562}  original identical
refresh.css inlined {rules 190, decls 556,  atrules 7,  top 164}  original identical
fluid.css   inlined {rules 232, decls 681,  atrules 15, top 191}  original identical
raw styles.css entry alone: {rules 0, decls 0, atrules 14}

Dropping one @import from styles.css makes styles.test.ts fail at import with no entry imports ./styles/menus.css (entry restored and re-checked afterwards).

vitest: 40 files, 323 tests before and after. Playwright: bunx playwright test --list — 508 tests in 5 files before and after.

Checks run (in webapp/)

  • bunx biome check . — no new warnings; see the note below on the count
  • bun run typecheck — clean
  • bun run test — 323 passed
  • bun run build — clean, CSS byte-identical to main

Playwright is left to CI: listed locally, not run.

Noticed, not changed

  • Biome's noDescendingSpecificity now sees less. main's 10 warnings are all this rule in the three sheets; after the split there are 2 (refresh/album.css 248 and 256, refresh.css 1012 and 1020 before). The rule compares selectors within one file, so the other 8 pairs now sit in different parts and are no longer reported — the CSS is unchanged, the lint is just blind across files. Nothing new is reported. Worth knowing if anyone relies on that rule.
  • The first commit is the git mv alone, as the restructuring asks, so history follows each sheet into its first part; on its own it does not build (the entries main.tsx imports come back in the second commit).
  • CLAUDE.md's webapp layout paragraph describes styles/ as "the sheet and the design tokens"; it predates this and is not changed here.
  • The sections in styles.css are partly historical, so some parts carry a neighbour that landed between them (the file-quality meter and the settings grid inside shell.css, the genre list and the hub section menu at the head of playing.css, the toggle row and podcast episodes in mixes-and-stats.css). Moving them would reorder the cascade, which this PR does not do.

Summary by CodeRabbit

  • Améliorations visuelles
    • Refonte des styles de l’application, avec des mises en page et composants adaptés aux pages de catalogue, aux albums, aux artistes, aux playlists, aux statistiques et aux espaces d’administration.
    • Harmonisation de l’apparence des thèmes clair et sombre, des formulaires, des menus et des commandes du lecteur.
  • Responsive et accessibilité
    • Adaptation de l’interface aux écrans étroits, notamment la navigation et le lecteur.
    • Prise en compte de la préférence de réduction des animations et amélioration des repères de focus clavier.

…ories

The move alone, so that history follows each sheet into its first part. The next commit splits them and puts the three entry files back as ordered lists of @import.

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
styles.css, refresh.css and fluid.css keep their names and their place in main.tsx, and each is now an ordered list of @import over the parts under styles/, refresh/ and fluid/. Concatenated in that order, the parts are the old sheets byte for byte apart from the @import lines and each part's one-line header, and the built CSS bundle is byte-identical.

The style tests read the sheets through sheets.ts, which inlines each entry's parts in order and throws if a part is imported by no entry or an entry names a part that does not exist: read raw, an entry is a list of paths and not one rule.

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
@github-actions github-actions Bot added scope: web Embedded web player (React) scope: design-tokens Design tokens / theming 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 be45b99.

Files selected for processing: 33
  • webapp/src/styles/design-tokens/themes-contrast.test.ts
  • webapp/src/styles/fluid.css
  • webapp/src/styles/fluid/base.css
  • webapp/src/styles/fluid/menus-sign-in-motion.css
  • webapp/src/styles/fluid/page.css
  • webapp/src/styles/fluid/player-bar.css
  • webapp/src/styles/fluid/rail.css
  • webapp/src/styles/fluid/theme-picker.css
  • webapp/src/styles/fluid/tokens.css
  • webapp/src/styles/refresh.css
  • webapp/src/styles/refresh/album.css
  • webapp/src/styles/refresh/base.css
  • webapp/src/styles/refresh/login-player-responsive.css
  • webapp/src/styles/refresh/queue.css
  • webapp/src/styles/refresh/shell.css
  • webapp/src/styles/refresh/surfaces.css
  • webapp/src/styles/sheets.ts
  • webapp/src/styles/styles.css
  • webapp/src/styles/styles.test.ts
  • webapp/src/styles/styles/admin.css
  • webapp/src/styles/styles/artist-hero.css
  • webapp/src/styles/styles/base.css
  • webapp/src/styles/styles/catalog.css
  • webapp/src/styles/styles/folders.css
  • webapp/src/styles/styles/maintenance.css
  • webapp/src/styles/styles/menus.css
  • webapp/src/styles/styles/mixes-and-stats.css
  • webapp/src/styles/styles/panels.css
  • webapp/src/styles/styles/phone.css
  • webapp/src/styles/styles/player.css
  • webapp/src/styles/styles/playing.css
  • webapp/src/styles/styles/shell.css
  • webapp/src/styles/styles/workspace.css

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

Les feuilles CSS fluid, refresh et styles sont réparties en fichiers thématiques. Un module TypeScript assemble les feuilles à partir de leurs imports et vérifie la présence de chaque partie référencée.

Changes: Feuilles de style de l’application

Couche / Fichier(s) Résumé
Assemblage et imports des feuilles
webapp/src/styles/sheets.ts, webapp/src/styles/fluid.css, webapp/src/styles/refresh.css, webapp/src/styles/design-tokens/themes-contrast.test.ts, webapp/src/styles/styles.test.ts
sheets.ts assemble les imports CSS bruts et exporte les feuilles styles, refresh et fluid. Les feuilles d’entrée et les tests utilisent ces exports.
Fondations et contrôles fluid
webapp/src/styles/fluid/tokens.css, webapp/src/styles/fluid/base.css
Les tokens définissent les palettes et valeurs partagées. La feuille de base définit le corps, la typographie, les boutons, les champs et les contrôles natifs.
Navigation et pages fluid
webapp/src/styles/fluid/rail.css, webapp/src/styles/fluid/page.css, webapp/src/styles/fluid/menus-sign-in-motion.css
Ces feuilles définissent la navigation, les pages, les panneaux et les menus fluid, avec des règles responsives et de réduction des animations.
Lecteur et sélecteur de thème fluid
webapp/src/styles/fluid/player-bar.css, webapp/src/styles/fluid/theme-picker.css
Les feuilles définissent la barre du lecteur, sa progression, la galerie de thèmes et les aperçus des cartes.
Base, coque et surfaces refresh
webapp/src/styles/refresh/base.css, webapp/src/styles/refresh/shell.css, webapp/src/styles/refresh/surfaces.css, webapp/src/styles/refresh/login-player-responsive.css
Ces feuilles définissent les thèmes, la coque, les surfaces et les adaptations responsives de connexion et du lecteur.
Albums et files refresh
webapp/src/styles/refresh/album.css, webapp/src/styles/refresh/queue.css
Ces feuilles définissent la présentation des albums, des morceaux, des albums associés et des files de lecture.
Fondations et catalogue styles
webapp/src/styles/styles/base.css, webapp/src/styles/styles/shell.css, webapp/src/styles/styles/panels.css, webapp/src/styles/styles/catalog.css, webapp/src/styles/styles/artist-hero.css
Ces feuilles définissent les thèmes, la coque, les panneaux, le catalogue et les en-têtes d’artiste.
Lecture et adaptation mobile styles
webapp/src/styles/styles/player.css, webapp/src/styles/styles/playing.css, webapp/src/styles/styles/phone.css
Ces feuilles définissent le lecteur, les vues de lecture et les adaptations mobiles, dont les règles de mouvement réduit.
Pages et outils styles
webapp/src/styles/styles/admin.css, webapp/src/styles/styles/folders.css, webapp/src/styles/styles/maintenance.css, webapp/src/styles/styles/menus.css, webapp/src/styles/styles/mixes-and-stats.css, webapp/src/styles/styles/workspace.css
Ces feuilles définissent les présentations de l’administration, des dossiers, de la maintenance, des menus, des mixes, des statistiques et des espaces Radio et Podcasts.

Priority: Low

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

Change: Refactor

Suggested labels: scope: web, scope: design-tokens, size: xl

Merge Risk: Minimal · up to be45b

The stylesheet split appears to preserve application styling and is mergeable after normal checks.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 3 files. (29 skipped: 2… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed Le titre décrit clairement le changement principal : la séparation des trois feuilles de style en parties ordonnées.
Description check Passed La description est complète et couvre le contexte, les changements, les tests exécutés, les limites de validation et les points d’attention. Elle n’utilise pas exactement les sections du modèle et ne …
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: Description check

Explanation

La description est complète et couvre le contexte, les changements, les tests exécutés, les limites de validation et les points d’attention. Elle n’utilise pas exactement les sections du modèle et ne reprend pas explicitement la déclaration finale de licence et de DCO, mais ces éléments restent non critiques au vu du niveau de détail fourni.

Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 3 files. (29 skipped: 29 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Finishing Touches: 💡 1
📝 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/styles/design-tokens/themes-contrast.test.ts

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

webapp/src/styles/fluid.css

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

webapp/src/styles/fluid/base.css

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

  • 29 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 size: xl > 500 lines type: refactor Code refactoring and removed type: refactor Code refactoring labels Oct 26, 2026
@InstaZDLL InstaZDLL self-assigned this Oct 26, 2026
@InstaZDLL
InstaZDLL merged commit 654659b into main Oct 26, 2026
16 checks passed
@InstaZDLL
InstaZDLL deleted the refactor/webapp-split-styles branch October 26, 2026 23:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: design-tokens Design tokens / theming 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