10 Commits
Author SHA1 Message Date
peter 3fe0897fd3 build(frontend): enable the zero-fallout compiler flags (S3a)
Three strictness flags that the codebase already satisfies (0 errors each):
- noFallthroughCasesInSwitch on src + e2e — a missing `break` is now a type error.
- noUnusedLocals + noUnusedParameters on the e2e project, so it matches src and node
  and dead spec scaffolding can't accumulate (it was the one project without them).
- allowJs + checkJs on the node project, pulling tailwind.config.js and
  postcss.config.js under the type checker (the last hole in C-4.5) — a real type
  error in either is now caught (verified: `content: 123` fails; Config stays
  permissive on plugin keys by design).

noUncheckedIndexedAccess is NOT here — it has 98 call-site fixes and lands as its own
reviewable sub-sprint (S3b).
2026-07-21 03:47:55 +02:00
peter 3c119b66c7 test(e2e): click a visible channel card in the scroll-restore test
The "Back restores the feed scroll position" spec failed against the production
build while the app was in fact correct. Root cause, found by instrumenting
PageScroller and comparing the baked :8080 build to live :5173:

`getByTestId("video-card-channel").first()` grabs the first card in the DOM, which
after scrolling 3000px is in the virtualizer's overscan ABOVE the viewport.
Playwright scrolls it into view to click it, moving the feed from 2879 to ~1017–1253
BEFORE navigating; PageScroller's save-on-scroll records that moved position, so Back
faithfully restores the moved position — under the test's own tolerance. A real user
clicks a card they can see, which does not move the feed.

New helper clickVisibleChannelLink() clicks a card whose bounding box is inside the
viewport. Restore now lands at ~2804 (was ~1017). 17/17 pass; stable over 3 reruns.
No app change — the scroll-restore logic was already correct.
2026-07-21 03:10:19 +02:00
peter e257e2aca0 style(frontend): Prettier config + one-time repo-wide format pass
Prettier 3 (pinned), printWidth 100, double quotes — chosen to match the existing
hand-formatting and minimise reflow. This commit is ONLY the mechanical format pass
(`prettier --write`) plus the config/ignore/scripts, isolated so it never pollutes a
feature diff. Verified behaviour-neutral: typecheck, eslint (0 errors), and vitest
all green before and after. See .git-blame-ignore-revs — `git blame` skips this sha.
2026-07-21 02:26:40 +02:00
peter 7d7e2c6146 test(e2e): lock the channel content-filter inherit + no-leak (bug 2)
Add feed-content-<key> testids to the toolbar's content pills and two channel
specs: the channel view inherits the feed's Live/Upcoming both ways, and a
content toggle made in the channel view does not leak back into the feed.
2026-07-18 18:27:17 +02:00
peter ec807c8b31 feat(shell): channel chrome is fully fixed, only the grid scrolls
Reworks the channel detail per UAT: instead of the banner scrolling away
and a sticky sub-header (which felt odd to scroll and needed an opaque bar
that clashed with the design even at rest), the ENTIRE chrome — banner,
identity, tabs, toolbar — is fixed and only the video grid scrolls.

Mechanism: ChannelPage wraps its whole chrome in <PageToolbar> so it
portals into the shell's fixed band (like the feed toolbar); the
channelScoped Feed portals its toolbar into a slot at the bottom of that
chrome. No sticky, no opaque bar — the chrome is transparent over the page
bg because nothing scrolls under it (the band sits above the scroller).

E2E updated: the channel chrome (title + toolbar) stays at a fixed
viewport position while the grid scrolls. Suite 15/15, tsc clean.
2026-07-18 04:53:49 +02:00
peter 0e03e561eb feat(shell): sticky tabs+toolbar on the channel page
Generalise the fixed-chrome pattern to the channel detail page: the
banner + identity now scroll away, while the tab row and the channel
feed's toolbar stick to the top as a sticky sub-header — only the video
grid scrolls.

Mechanism: PageToolbarSlot (the toolbar portal target) is now an exported,
overridable context. ChannelPage provides its own sticky sub-header node
as the target, so the channelScoped Feed portals its toolbar up next to
the tabs instead of into the shell's top band. Feed always portals its
toolbar now (the slot decides where it lands).

Also: the page scroller now fades only its BOTTOM edge — the top is
bounded by the fixed/sticky chrome, so a top fade only bit into it.

E2E: adds a lock that the channel tabs/toolbar stay pinned while the
banner scrolls away; suite 15/15 on :5173, tsc + knip clean.
2026-07-18 04:45:11 +02:00
peter 10f4baab3b fix(shell): scroll restore on Back + reset on new filter (bugs 1, 3)
The shell owns the scroller, so it owns the scroll policy. PageScroller
now takes a scrollKey identifying the logical view and keeps a per-view
offset in a module-level map, recorded live on scroll:
- same key returning (feed -> channel -> Back, filters unchanged) restores
  where you were (bug 1) -- the feed's scroller unmounts, but the saved
  offset survives and is re-applied, retried over a few frames so the
  virtualized list has time to reach its full height;
- a new key (feed filters changed) starts at the top of the fresh result
  set (bug 3). q is normalised out of the feed key so typing doesn't reset.

Recording on scroll rather than at unmount matters: by cleanup time the
scroller's scrollTop is already zeroed, so a cleanup-read saved 0.

Also fixes a Maximum-update-depth loop this introduced: the combined
element/fade ref was an inline callback, so React re-attached it every
commit and re-ran useScrollFade's state setter. Bound it with useCallback.

Adds E2E locks for bugs 1, 3 and 4 (restore on Back, reset on filter,
toolbar stays pinned). Full suite 14/14 green on :5173; tsc + knip clean.
2026-07-18 03:51:40 +02:00
peter 833aee07d4 test(e2e): make the seeded account admin + cover admin nav pages
The E2E account is now admin (matching the real primary user), so the nav spec
also drives scheduler/config/users/audit — exercising the render ternary's admin
branches, which the upcoming PageShell refactor will restructure.
2026-07-18 02:52:54 +02:00
peter c855924fdc feat(settings): auto-save preferences instead of draft + Save
Every Settings change now persists on its own (debounced ~500ms so a slider drag
is one PUT), with a small transient indicator; the draft/dirty-save UI and the
navigation leave-guard are gone. Also unblocks the navigation extraction — there
is no longer a cross-page "unsaved settings" guard to thread through routing.
2026-07-18 01:44:25 +02:00
peter e7091158fd test(e2e): golden-master Playwright net for the Platform refactor
Locks the five flows the refactor must not break (feed, channel, player,
settings, nav) so later sprints can prove they changed nothing. Adds a thin
data-testid layer and a dedicated seeded non-demo account; the suite runs on
the baked :8080 stack (authoritative) or :5173. No product-behaviour change.
2026-07-18 00:35:57 +02:00