Rework per hermes-sweeper review (keep_open, salvageability=medium):
- Capture moves out of the render body into the session-switch layout-effect
cleanup, so only committed switches persist state (no uncommitted render
can write localStorage).
- Live state is fed by both scroll events and a ResizeObserver on the
content element, so distance-from-bottom stays fresh under async
relayout that changes height without a scroll event (the staleness gap
#70478's own review threads flagged).
- State is distance-from-bottom (or sticky-bottom), not raw scrollTop:
the render-budget backfill prepends older turns and main anchors by
distance-from-bottom, so an absolute offset no longer identifies the
same reading location after the height change.
- Restore integrates with main's hasGroups/settle/anchor lifecycle: the
settle loop re-applies the remembered target, defers on a clamped offset
(content still arriving), hands back locked only for sticky-bottom, and
leaves mid-read sessions escaped at their offset.
- anchorBeforePrepend no longer records 0 mid-load, which would clobber a
restored offset once the backfill lands; the settle loop owns the target
every frame until settled.
- Storage is scoped per profile with the session.ts .profile.<encoded>
key pattern (no cross-profile bleed, #67709 pattern) and LRU-capped at
120 sessions per profile.
- Regression tests: state classification, target math, profile isolation,
LRU eviction, corrupt/invalid payload handling.
Related to #45562 (partial; no automatic closure)
(cherry picked from commit 1d79bef93f63d7b190b471bf652b5109b4288a8e)