Files
iptvnator/docs/architecture
7550a22a41 test(workspace): cover the atomic route swap and document it (#1922)
* fix(workspace): swap routed pages in one task when returning to the dashboard

Leaving an Xtream portal section for the dashboard inserted the dashboard
below the still-present portal page: the @angular/animations renderer
queues every DOM removal until the end of the next change detection, while
the router inserts the new page at once. With event coalescing that tick
runs in the next requestAnimationFrame, so Chromium laid the document out
with both pages (the dashboard at y=720 in a 720 px window) and scored the
swap as one layout shift: 0.337 at 1280 px, 0.394 at 900 px, 0.723 at
420 px, sources lib-workspace-dashboard-rails.

Nothing uses @angular/animations triggers (Material animates with CSS), so
the app no longer provides provideAnimations(). The old page now leaves in
the same microtask, before the new one is inserted, and the swap records no
shift. The router itself was already atomic: NavigationEnd and the shell's
currentUrl follow activation synchronously.

app.config.spec.ts keeps the animations renderer out;
workspace-route-swap.e2e.ts asserts that no mutation checkpoint holds both
pages and that neither the dashboard nor the content pane shifts. Both fail
with provideAnimations() restored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(workspace): read scroll and focus state before a view's DOM detaches

Without the @angular/animations renderer a destroyed view's DOM leaves
before its destroy hooks run, which the deferred removal used to hide. Two
hooks read state from the detached DOM:

- CategoryContentView.ngOnDestroy saved the grid's scrollTop, which reads 0
  once detached, over the offset saved when a card was clicked: back from a
  VOD detail landed at the top of the list (catalog-sorting E2E on every
  OS). The view now keeps the offset of the grid that scrolled last; a grid
  rendered since (a new category's) saves the top.
- Chromium blurs a focused element synchronously while removing it, so
  removing the focused player settings panel fired a focusout without a
  relatedTarget before the panel's destroy hook, which cleared focusInside:
  Escape left focus on the page instead of the tune button
  (player-controls-keyboard web E2E). The panel now judges a focusout a
  microtask later: a removed panel is detached then, while a pointer
  release blurs a panel that stays attached and still drops focus.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: 4gray <fourgray@proton.me>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:41:10 +02:00
..