fix(web,learner): navigation swaps at once; content-only entrance, loading line, focus (R19)

Drops document-level View Transitions and the lesson hold: they dropped clicks for 120-180 ms, froze the screen
while route code loaded, and let the dock act on a stale lesson. Navigation now swaps immediately; only the
content region enters (opacity and a 6 px rise, 160 ms); a 2 px loading line appears when code or data takes over
150 ms; the dock works from the outline and the URL, so a second Next advances again; focus moves to the new h1
and a polite live region announces it. Route chunks of a feature preload together so the sidebar never drops.
Scroll restore stops on user input; pdf.js never starts a load after cleanup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas Winther
2026-10-30 21:49:49 +01:00
co-authored by Claude Opus 5.5
parent 305c262ff1
commit b5e00af4f7
18 changed files with 231 additions and 275 deletions
@@ -215,3 +215,13 @@
- **`*` does not match `::details-content`.** The reduced-motion block has a separate `*::details-content` rule.
- **Chrome lists no `::details-content` animations in `getAnimations()` and sends no transition events for them.** The fold tests sample the computed height instead.
- **Pre-existing bug: pdf.js 6 documents have no `destroy()`.** Leaving a lesson whose deck had loaded crashed the app. Fixed by destroying the loading task instead.
## Fix round 1 (ruling R19)
- Document-level View Transitions are gone, along with the lesson hold. Navigation now swaps at once, and only the content region enters: `stc-page`, opacity plus a 6 px rise in 160 ms.
- When route code or data takes longer than 150 ms, a 2 px accent loading line (`LoadingLine`) shows at the top of the content region.
- The dock's previous/next come from the course outline and the URL, so the dock works while the lesson loads.
- After a navigation, focus moves to the new page's `h1`, and its text goes to a polite live region.
- Route components are preloadable: once a feature's route renders, its sibling routes load, so the sidebar is never dropped on a first visit.
- The scroll keeper loses defer/settle and gains `userInput()`.
- The e2e tests are deterministic: they use a fetch gate and condition waits, and finish animations through `getAnimations()`.