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.
This commit is contained in:
2026-07-18 03:51:40 +02:00
parent a6cbc45334
commit 10f4baab3b
5 changed files with 158 additions and 9 deletions
+4 -1
View File
@@ -38,10 +38,13 @@ export default function PageShell({
header,
banners,
children,
scrollKey,
}: {
header?: ReactNode;
banners?: ReactNode;
children: ReactNode;
// Identifies the logical view for the scroll restore/reset policy — see PageScroller.
scrollKey?: string;
}) {
const [toolbarSlot, setToolbarSlot] = useState<HTMLDivElement | null>(null);
return (
@@ -50,7 +53,7 @@ export default function PageShell({
<div className={`flex-1 min-w-0 min-h-0 flex flex-col ${header ? "pt-[var(--hdr-h)]" : ""}`}>
{banners}
<div ref={setToolbarSlot} className="shrink-0" />
<PageScroller>{children}</PageScroller>
<PageScroller scrollKey={scrollKey}>{children}</PageScroller>
</div>
</ToolbarSlot.Provider>
);