Review follow-up. The viewport-scaled lookahead was unbounded, so a tall window
prefetched several feed pages on every mount / filter change before any scroll —
wasted work on the app's heaviest query. Clamp it to MAX_LOOKAHEAD_ROWS, and move
the policy into a pure readAhead.ts (readAheadLookahead / shouldPrefetchNext) with
unit tests covering the floor, the scaling, the cap boundary, and the fetch trigger
— the core of the feed-scroll fix now has coverage instead of only manual E2E.
Keyset pagination is strictly serial (a page needs the previous page's cursor)
and the read-ahead only fired within a fixed 4 rows of the loaded end, pulling
one 60-item page per cycle. A fast free-spin flick (MX Master & co.) out-scrolls
that tiny lookahead, lands on an empty bottom, and waits out a chain of serial
round-trips — the multi-second "Load more" hang. Scale the lookahead to the
rendered row count (PREFETCH_VIEWPORTS screenfuls); since the effect re-runs when
a page settles it chains, keeping a couple of screens buffered ahead of the fold
so the content is usually already there. Bounded, and a no-op for finite lists.
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.
- The portalled tag picker now FOLLOWS its trigger on scroll (rAF-coalesced)
instead of closing. Closing on every wheel tick was a regression for the table,
where the menu used to travel with its row; it still closes once the trigger
leaves the viewport (nothing left to anchor to) or on resize.
- Place the menu by MEASUREMENT, not a px estimate: it opens below, then the
layout effect flips it above only if the real box overflows. The old
`rows * 30px` guess read short at font-scale 1.3 (the rows are rem), which
could leave a fixed menu hanging off the bottom with no way to reach it.
- `toggle` only opens once a position exists, so the `open && coords` gate can
never show a stale anchor.
- One owner for "is there anything to page": `hasPagerContent()` lives in Pager
and DataTable calls it, instead of both re-deriving the rule — that drift is
what left empty control bands behind in the first place.
- Reset the card page on the row COUNT too, not just the filter inputs, so an
unsubscribe or a shrinking refetch can't leave a stale page to spring back.
- `parseCardSize` clamps to the sizes the pager actually offers (like clampView /
clampStatus), so the select can't display one value while the grid pages by another.
- VirtualGrid's docblock now carries the stacking-context constraint: its rows are
transformed, so anything floating an item renders must portal out.
VirtualFeed becomes a thin wrapper that maps a FeedView to VirtualGrid's
layout spec and renders a VideoCard per item; all measure/chunk/place/
infinite-scroll mechanics move to VirtualGrid so the channel manager (E4 S3)
can reuse them. Behaviour-preserving: e2e 17/17, all render families smoked.