fix(ui): season list rows and both theme contexts follow the progress token

Review round 1:
- Codex: season list mode drew its episode bar with --mat-sys-primary;
  `.episode-list-item__progress-fill` now reads --app-progress-color, with
  a spec (the grid mode already goes through the capsule).
- Greptile: nothing pinned the token's `.dark-theme` re-declaration, so
  dropping it would have left every spec green while dark mode inherited
  the light colour. `m3-theme.spec.ts` (web) asserts the declaration in
  both theme contexts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 committed 2026-10-03 11:32:07 +02:00
1 parent f12dfabcce
commit ef40da23e4
4 files changed
+91 -7

No files matched your search

+53
View File
@@ -0,0 +1,53 @@
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
// A custom property that reads another one is resolved where it is declared,
// so a token derived from a per-theme colour has to be declared again inside
// `.dark-theme`; inherited from `html`, it would keep the light value there.
const THEME = readFileSync(
resolve(process.cwd(), 'apps/web/src/m3-theme.scss'),
'utf8'
)
.replace(/\/\*[\s\S]*?\*\//g, '')
.replace(/(^|\s)\/\/[^\n]*/g, '$1');
/** The body of the brace block that opens at `start` (an index of `{`). */
function block(source: string, start: number): string {
let depth = 0;
for (let index = start; index < source.length; index++) {
if (source[index] === '{') depth++;
if (source[index] === '}' && --depth === 0) {
return source.slice(start + 1, index);
}
}
throw new Error('Unbalanced braces in m3-theme.scss');
}
/** Light declarations (the `html` block minus `.dark-theme`) and dark ones. */
function themeContexts(): { light: string; dark: string } {
const darkStart = THEME.indexOf('.dark-theme {');
const htmlStart = THEME.lastIndexOf('html {', darkStart);
expect(darkStart).toBeGreaterThan(-1);
expect(htmlStart).toBeGreaterThan(-1);
const html = block(THEME, THEME.indexOf('{', htmlStart));
const dark = block(THEME, THEME.indexOf('{', darkStart));
return { light: html.replace(dark, ''), dark };
}
describe('m3-theme watch-progress token', () => {
it('declares --app-progress-color from the selection colour in both themes', () => {
const { light, dark } = themeContexts();
const progress =
/--app-progress-color:\s*var\(--app-selection-color\);/;
const selection = /--app-selection-color:\s*#[0-9a-f]{6};/i;
for (const context of [light, dark]) {
expect(context).toMatch(selection);
expect(context).toMatch(progress);
}
expect(light.match(selection)?.[0]).not.toBe(
dark.match(selection)?.[0]
);
});
});
+10 -6
View File
@@ -531,10 +531,12 @@ A title's watch progress (its resume share, `progressPercent`) has exactly one
colour per context, and never a literal of its own:
- **App chrome** — dashboard rail cards and the hero, catalog grids and season
episodes (`app-progress-capsule`): `--app-progress-color`, declared per theme
in `apps/web/src/m3-theme.scss` as that theme's `--app-selection-color`. The
capsule's green from 90 % marks a finished title; it is a status, not
progress.
episodes (`app-progress-capsule`, and the season list rows' own fill):
`--app-progress-color`, declared per theme in `apps/web/src/m3-theme.scss`
as that theme's `--app-selection-color`, and declared again inside
`.dark-theme` because a derived custom property resolves where it is
declared. The capsule's green from 90 % marks a finished title; it is a
status, not progress.
- **Over video** — the dock timeline, the Up next card, the Up Next rail and
the fullscreen episode panel: the player's fixed `--pc-progress` (accent
blue `#4f8eff`), never an app token, because the player palette is
@@ -550,8 +552,10 @@ colour per context, and never a literal of its own:
live hero slides) keeps `--app-live-color`; EPG programme progress keeps the
fill described below.
Specs hold the rule in each owning project: `player-progress.palette.spec.ts`
(ui-playback), `progress-capsule.component.spec.ts` (components) and
Specs hold the rule in each owning project: `m3-theme.spec.ts` (web: the token
in both theme contexts), `player-progress.palette.spec.ts` (ui-playback),
`progress-capsule.component.spec.ts` and
`season-container.progress-colour.spec.ts` (components) and
`dashboard-progress-colour.spec.ts` (workspace-dashboard-feature).
### Track
@@ -671,7 +671,7 @@ app-season-tabs {
&__progress-fill {
height: 100%;
background: var(--mat-sys-primary);
background: var(--app-progress-color);
border-radius: 2px;
transition: width 0.3s ease;
}
@@ -0,0 +1,27 @@
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
// Watch progress in app chrome reads one theme token in both season view
// modes; the list rows draw their own fill rather than the grid's capsule
// (see the UI guidelines' Progress Bars section).
const DIR = resolve(
process.cwd(),
'libs/ui/components/src/lib/season-container'
);
function read(file: string): string {
return readFileSync(resolve(DIR, file), 'utf8');
}
describe('season container watch-progress colour', () => {
it('fills list rows with the app watch-progress token', () => {
const styles = read('season-container.component.scss');
const listItem = styles.slice(styles.indexOf('.episode-list-item {'));
const fill = listItem.match(/&__progress-fill\s*\{([^}]*)\}/)?.[1];
expect(read('season-container.component.html')).toContain(
'class="episode-list-item__progress-fill"'
);
expect(fill).toMatch(/background:\s*var\(--app-progress-color\);/);
});
});