Commit Graph
11 Commits
Author SHA1 Message Date
peter 4aaaaa7860 chore(comments): drop epic, sprint and review-round ids from the code
The code cited its plan instead of saying why: `E28 review J4`, `(R6 S3b)`, `S1 justified…`,
`deep-review round 5`. That plan lives in the private ops repo, so for every reader of this one the
citation was noise, and a sentence whose subject was the plan step ("round 2 showed…") hid its
lesson behind an id. Each one is gone and the "why" kept: a bare citation is deleted, a sentence
carrying a lesson is reworded around it ("a later attempt showed…").

Measured (`scripts/audit_metrics.py`, product code): 652 lines in 270 files carried an epic code by
the definition the next commit holds (507 / 241 by the old, two-digit-only one) -> 0. Across all
code, tests, scripts and configs: ~1100 epic-code lines plus ~320 sprint/round ids.

Behaviour unchanged, checked mechanically: every changed .py file has the same AST once docstrings
are blanked; css/sh/toml/yml/json files are identical once comments are stripped; the only
non-comment changes in TS/JS are test titles, one description string in a contract table, and five
ESLint messages, whose text is all the gates snapshot diff holds. Not touched: Markdown (docs and
the changelog keep their history), Alembic migrations, TV episode notation (`S2 · E5`), DESIGN.md
ids (`C1`, `B4`, `S3h`), tracker ids (`SEC-16`, `DX-17`).

One stale claim fixed on the way: test_hls_reaper's docstring said the player checkpoints "every
10 s whether or not it is playing", which plex/stream.py calls wrong; it now says what the code does.
2026-10-05 00:19:39 +02:00
peter dfd43ae89c docs: the folding channel header in its e2e, and three comments brought up to date
The e2e asserted the channel chrome never moves; it folds once now. The
ui catalog lists scrollFold and progressiveRows, Tabs names the
per-tab memory its callers use, and the welcome note says a removed
language orphans its stored copies too.
2026-10-02 23:42:32 +02:00
peter 45d8b47366 feat(toolbar): fixed-width ItemCount pill, lead the toolbar with it, align chrome to the cards
- New ItemCount: a generic, FIXED-WIDTH count pill (glass, non-interactive) — "192,050 items"
  (neutral noun, dynamic plural), min-w-[9rem] so a changing number never shifts the controls
  beside it; a million or more collapses to "1M+" with the exact figure in the title tooltip.
- Feed toolbar: ItemCount is the fixed leading element now; dropped the mx-1 separators so every
  gap is a uniform 1.5 (Content-types↔Source used to be wider than Watch-state↔Content-types).
- Chrome-to-card alignment (app-wide, via the shared Header + PageShell): the floating header and
  the toolbar band now span exactly the card grid's width — left-4, and a right edge of
  1rem + the scrollbar gutter (measured once into --sbw), so the fixed chrome lines up with the
  scrolled cards instead of running ~15px past them. Wraps cleanly to multiple rows on mobile.

Renames the count testid feed-result-count → item-count (E2Es updated).
2026-08-21 20:33:55 +02:00
peter 5851ddd329 feat(feed): compress the toolbar chip groups into labeled FilterSelect dropdowns
Adds a reusable FilterSelect: a "Label: value ▾" pill that opens a listbox (a checkmark
row for single-select, a checkbox row for multi), on the AnchoredPopover platform. It
replaces the two feed-toolbar chip walls:
  - Watch state (single-select, default All) — picking a row replaces + closes.
  - Content types (multi-select, default Normal; requireOne keeps at least one on) — rows
    toggle and the panel stays open.
The trigger summarises multi picks as "First +N". The component is source-agnostic, meant
for reuse anywhere a small known option set currently renders as a chip group.

FilterSelect takes a `testId` → the trigger gets it, each option gets `${testId}-${value}`,
so the feed/channel E2Es keep stable selectors. They now open the dropdown and assert the
option's aria-selected instead of the old inline aria-pressed chips (new expectContentType/
toggleContentType helpers).
2026-08-21 17:28:57 +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 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