Content
View differences
Updated by Alexander Coles 14 days ago
## Context
On every Turbo navigation, four independent handlers react to the page changing \- Mission — bound to different Turbo events, in no guaranteed order, with no single module owning "what happens when we navigate": the Angular re-bootstrap (`turbo-angular-wrapper.ts`), per-navigation service re-registration (`app.module.ts` `initializeServices`), re-bootstrap-on-every-turbo:load (turbo-angular-wrapper.ts), crux of DREAM-514
\- Problem — the ui-router sync (`openproject.routes.ts`), and legacy DOM setup (`turbo-global-listeners.ts`). Turbo's `turbo:load`/`turbo:render` events are gappy and overlapping — neither fires in every navigation scenario (full load, first visit, subsequent visit, 422 form error) — so hidden interface: destroy all root components + runBootstrap on each handler independently guesses which events to bind and relies on idempotency to cover navigation, skip(1) guard, zero tests. Inlined candidate B's analysis since the gaps (see #65298). The "is this an Angular page?" decision is separately re-derived three times via `querySelector('openproject-base')`.
OP-19617 landed the jsdom test seam that makes this tractable; #OP-19618 (CSP nonce) and #OP-19666 (morph policy) split off the cross-cutting cleanups.
## Goal
Give the Angular↔Turbo navigation lifecycle one owner, so navigation behaviour is legible in one place, testable through a single interface, and cleanly editable as the ui-router is removed (#DREAM-45).
## Approach
Two scoped candidates first (A + B):
* A temp HTML report was purged (days elapsed)
\- Proposed deepening — named `navigation-lifecycle` bridge module that owns the Turbo (bootstrap/onTurboNavigate/teardown), inject event truth-table behind two semantic moments: `onBodyRendered` (fires on render or load, deduped once per navigation, all pages — legacy DOM setup source + service re-registration) and `onAngularVisit` (fires on a completed Drive visit, Angular-hosted pages only appRef for testability; deletion test
\- Integration map — root teardown + re-bootstrap + router sync). Existing handlers become injected collaborators; only the module binds Turbo events.
* A single `isAngularHostedPage()` predicate owning the `openproject-base` sentinel, replacing the three ad-hoc queries.
Behaviour is preserved except two guarded improvements: first-visit body-setup dedupes 2×→1×, 6-area coupling surface with file:line (re-bootstrap, plugin-context/useAngularServices seam, opce-\* custom elements, 3 morph guards, Rails op\_turbo, existing nav spec)
\- Build-on, don't redo — PR #23980 test seam; turbo-angular-wrapper.ts deliberately left out for exactly this work
\- Gotchas — jsdom vs browser-mode vitest, precompiled-asset rebuild, chruby 4.0.2, zoneless, RxJS-vs-signals
\- Scope — B in; D/E (OP-19618) and per-navigation service re-registration routes through the module. The router-sync step is isolated as a named seam so #DREAM-45 can delete it in one place.
## Notes
Fixing the double-selected-menu bug (#DREAM-406) and retiring per-navigation re-bootstrap entirely are sequenced as follow-ups on top of this module, the latter gated on #DREAM-45.
Supersedes the exploratory spike in https://github.com/opf/openproject/pull/20569. turbo#1300 out; DREAM-514 on-hold → confirm appetite, seam-first
On every Turbo navigation, four independent handlers react to the page changing
\- Problem —
OP-19617 landed the jsdom test seam that makes this tractable; #OP-19618 (CSP nonce) and #OP-19666 (morph policy) split off the cross-cutting cleanups.
## Goal
Give the Angular↔Turbo navigation lifecycle one owner, so navigation behaviour is legible in one place, testable through a single interface, and cleanly editable as the ui-router is removed (#DREAM-45).
## Approach
Two scoped candidates first (A + B):
* A
\- Proposed deepening —
\- Integration map
* A single `isAngularHostedPage()` predicate owning the `openproject-base` sentinel, replacing the three ad-hoc queries.
Behaviour is preserved except two guarded improvements: first-visit body-setup dedupes 2×→1×,
\- Build-on, don't redo — PR #23980 test seam; turbo-angular-wrapper.ts deliberately left out for exactly this work
\- Gotchas — jsdom vs browser-mode vitest, precompiled-asset rebuild, chruby 4.0.2, zoneless, RxJS-vs-signals
\- Scope — B in; D/E (OP-19618)
## Notes
Fixing the double-selected-menu bug (#DREAM-406) and retiring per-navigation re-bootstrap entirely are sequenced as follow-ups on top of this module, the latter gated on #DREAM-45.
Supersedes the exploratory spike in https://github.com/opf/openproject/pull/20569.