You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(ssr-overlay): drop anti-flicker overlay after content paints (not on isStable or bare auth gate)
The SSR anti-flicker overlay (PR #1288) is removed by AppComponent. Two prior
approaches each fixed one symptom and reintroduced the other:
- #1288 waited for ApplicationRef.isStable: no flicker, but admin sessions keep
the zone busy (authz/widgets, polling, AAI scripts) so isStable fires many
seconds late (or hits the 15s fallback), leaving the live, already-rendered page
masked & non-interactive (dspace-customers#725).
- #1317 switched to the loader-swap gate (!isAuthenticationBlocking &&
!isThemeLoading) + a single requestAnimationFrame: fast/interactive, but the
gate only un-hides <router-outlet> and one rAF runs before paint, so the
snapshot is dropped over an empty <ds-app> -> the flicker returned.
Neither signal means "the routed content is painted AND the app is interactive":
isStable over-waits (couples removal to unrelated background async); the auth/theme
gate under-waits (decoupled from actual content paint).
This keeps #1317's decoupling from isStable but, after the gate opens, waits across
animation frames until the real <ds-app> is actually laid out (height >= 200px AND
its #main-content host present) before removing the overlay, capped at ~3s
(MAX_FRAMES) so background async can never hold it open. The 15s fallback in
index.html stays as the catastrophic-error net.
Verified (DSpace 7.6.5 backend, CPU 4x, hard reload, admin session):
- #1288: TTI 15963ms (page masked ~13s) #1317: ds-app height 0 at removal (217ms flash)
- fix: TTI ~3.1-3.4s, ds-app height 5281px at removal, gap <= 0 (no flash), 3x deterministic;
anon reload also no-flicker; 0 CORS and 0 SSR/hydration/NG0 console errors.
See docs/flickering-fix/ for the full report, raw metrics and before/after videos.
Refs: dspace-customers#725, PR #1288, PR #1317
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Supersedes the tug-of-war between PR [#1288](https://github.com/dataquest-dev/dspace-angular/pull/1288) and PR [#1317](https://github.com/dataquest-dev/dspace-angular/pull/1317).
5
+
6
+
## TL;DR
7
+
8
+
Both prior PRs edited the **same decision** — *when* to remove the SSR anti-flicker overlay — and
9
+
each picked a signal at the opposite end of one tradeoff, so fixing one symptom reintroduced the
10
+
other. This change removes the overlay when the **routed content has actually painted**, decoupled
11
+
from `ApplicationRef.isStable`, with a frame-count safety cap. That satisfies both constraints at
12
+
once: no blank flash (content is on screen before the snapshot drops) and no lingering mask (a busy
13
+
zone can no longer hold the overlay open).
14
+
15
+
## Background: what the overlay is
16
+
17
+
This FE is Angular 15 + ngUniversal with **no hydration** (`provideClientHydration` is Angular 16+).
18
+
On every browser load Angular tears down the entire SSR DOM and re-renders from scratch (~0.6–1.5s).
19
+
PR #1288 masks that rebuild with a snapshot overlay (`#__dspace_ssr_overlay`, `pointer-events:none`)
20
+
while `<ds-app>` is hidden (`visibility:hidden`), and removes it via `window.__dspaceRemoveSsrOverlay()`.
21
+
The whole problem is *when* to call that removal:
22
+
23
+
-**Too early** → the half-built/empty `<ds-app>` shows → **flicker**.
24
+
-**Too late** → the inert snapshot masks the live page → **looks rendered but not clickable**.
25
+
26
+
## Root cause of the conflict
27
+
28
+
| PR | Removal trigger | Result |
29
+
|----|-----------------|--------|
30
+
|**#1288**|`ApplicationRef.isStable`| Content always painted ⇒ no flicker. But isStable is delayed by *any* ongoing zone async — after an **admin** login (authz/widgets, polling, AAI scripts) it fires many seconds late or hits the 15s fallback ⇒ **page masked & non-interactive (#725)**. |
31
+
|**#1317**|`!isAuthenticationBlocking && !isThemeLoading` + 1×`requestAnimationFrame`| Fires promptly ⇒ fixes #725. But that gate only un-hides `<router-outlet>`; Angular hasn't rendered the routed page yet and a single rAF runs *before* paint ⇒ snapshot dropped over an empty `<ds-app>` ⇒ **flicker returns**. |
32
+
33
+
Neither signal means *"the routed content for this page is committed to the DOM and painted, and the
34
+
app is interactive."*`isStable`**over-waits** (couples removal to unrelated background work);
35
+
the auth/theme gate **under-waits** (decouples removal from actual content paint).
36
+
37
+
## Measured reproduction (DSpace 7.6.5 backend, CPU throttled 4×, hard reload, admin session)
38
+
39
+
Measured with Playwright instrumentation on a single `performance.now()` timeline.
[2026-06-19T11:36:06.140Z] 200 GET http://127.0.0.1:4000/login
2
+
[2026-06-19T11:36:06.157Z] 200 GET http://127.0.0.1:4000/custom-theme.css
3
+
[2026-06-19T11:36:06.167Z] 200 GET http://127.0.0.1:4000/assets/images/vsb-en-logo-w.svg
4
+
[2026-06-19T11:36:06.168Z] 200 GET http://127.0.0.1:4000/assets/images/vsb-symbol-logo.svg
5
+
[2026-06-19T11:36:06.169Z] 200 GET http://127.0.0.1:4000/styles.0758bf16c5682bf4.css
6
+
[2026-06-19T11:36:06.169Z] 200 GET http://127.0.0.1:4000/runtime.e9d8b7f8ae1cbcfd.js
7
+
[2026-06-19T11:36:06.180Z] 200 GET http://127.0.0.1:4000/polyfills.5f189aa8f745c99e.js
8
+
[2026-06-19T11:36:06.180Z] 200 GET http://127.0.0.1:4000/scripts.3c162baabeb34571.js
9
+
[2026-06-19T11:36:06.275Z] 200 GET http://127.0.0.1:4000/main.f5182c0e526b9577.js
10
+
[2026-06-19T11:36:06.279Z] 200 GET https://code.jquery.com/jquery-2.1.4.min.js
11
+
[2026-06-19T11:36:07.099Z] 200 GET https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.min.js
12
+
[2026-06-19T11:36:07.580Z] 200 GET http://127.0.0.1:4000/assets/fonts/fa-solid-900.woff2
13
+
[2026-06-19T11:36:07.581Z] 200 GET http://127.0.0.1:4000/assets/fonts/Drive-Regular.woff2
14
+
[2026-06-19T11:36:07.582Z] 200 GET http://127.0.0.1:4000/assets/config.json
15
+
[2026-06-19T11:36:07.582Z] 200 GET http://127.0.0.1:4000/7974.877f026c8be0dd96.js
16
+
[2026-06-19T11:36:07.582Z] 200 GET http://127.0.0.1:4000/5034.624e21a6c30585d4.js
17
+
[2026-06-19T11:36:07.584Z] 204 GET http://127.0.0.1:8180/server/api/security/csrf
18
+
[2026-06-19T11:36:07.593Z] 200 GET http://127.0.0.1:8180/server/api/authn/status
19
+
[2026-06-19T11:36:07.595Z] 200 GET http://127.0.0.1:8180/server/api/config/properties/google.analytics.key
20
+
[2026-06-19T11:36:07.627Z] 200 GET http://127.0.0.1:8180/server/api
21
+
[2026-06-19T11:36:07.736Z] 200 GET http://127.0.0.1:8180/server/api/config/properties/registration.verification.enabled
22
+
[2026-06-19T11:36:07.736Z] 200 GET http://127.0.0.1:4000/89.c7c6337aca4f2d28.js
23
+
[2026-06-19T11:36:07.810Z] 200 GET http://127.0.0.1:4000/assets/fonts/Drive-Bold.woff2
24
+
[2026-06-19T11:36:07.812Z] 200 GET http://127.0.0.1:4000/8394.70edf5dd30530a0a.js
25
+
[2026-06-19T11:36:07.813Z] 200 GET http://127.0.0.1:4000/4754.627fba82092b8e7e.js
26
+
[2026-06-19T11:36:07.814Z] 200 GET http://127.0.0.1:4000/6199.a7e96c457fa0538e.js
27
+
[2026-06-19T11:36:07.814Z] 200 GET http://127.0.0.1:4000/common.871a11c60393d414.js
28
+
[2026-06-19T11:36:07.814Z] 200 GET http://127.0.0.1:4000/4234.b45310739d150de6.js
29
+
[2026-06-19T11:36:07.818Z] 200 GET http://127.0.0.1:8180/server/api/authz/authorizations/search/object?uri=http://127.0.0.1:8180/server/api/core/sites/6d65c6a2-3fe7-44dd-bacb-79271257c35d&feature=canSendFeedback&embed=feature
30
+
[2026-06-19T11:36:07.819Z] 200 GET http://127.0.0.1:4000/183.640131b33fe2d15e.js
31
+
[2026-06-19T11:36:07.874Z] 200 GET http://127.0.0.1:8180/server/api/authz/authorizations/search/object?uri=http://127.0.0.1:8180/server/api/core/sites/6d65c6a2-3fe7-44dd-bacb-79271257c35d&feature=epersonRegistration&embed=feature
32
+
[2026-06-19T11:36:08.147Z] 200 POST http://127.0.0.1:8180/server/api/authn/login
33
+
[2026-06-19T11:36:08.170Z] 200 GET http://127.0.0.1:8180/server/api/authn/status
34
+
[2026-06-19T11:36:08.772Z] 200 GET http://127.0.0.1:4000/reload/1781868968176?redirect=%2F
35
+
[2026-06-19T11:36:08.782Z] 200 GET http://127.0.0.1:4000/custom-theme.css
36
+
[2026-06-19T11:36:08.782Z] 200 GET https://code.jquery.com/jquery-2.1.4.min.js
37
+
[2026-06-19T11:36:08.782Z] 200 GET https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.min.js
38
+
[2026-06-19T11:36:08.785Z] 200 GET http://127.0.0.1:4000/assets/images/vsb-symbol-logo.svg
39
+
[2026-06-19T11:36:08.785Z] 200 GET http://127.0.0.1:4000/assets/images/vsb-en-logo-w.svg
40
+
[2026-06-19T11:36:08.785Z] 200 GET http://127.0.0.1:4000/runtime.e9d8b7f8ae1cbcfd.js
41
+
[2026-06-19T11:36:08.785Z] 200 GET http://127.0.0.1:4000/polyfills.5f189aa8f745c99e.js
42
+
[2026-06-19T11:36:08.785Z] 200 GET http://127.0.0.1:4000/scripts.3c162baabeb34571.js
43
+
[2026-06-19T11:36:08.785Z] 200 GET http://127.0.0.1:4000/main.f5182c0e526b9577.js
44
+
[2026-06-19T11:36:08.796Z] 200 GET http://127.0.0.1:4000/styles.0758bf16c5682bf4.css
45
+
[2026-06-19T11:36:08.836Z] 200 GET http://127.0.0.1:4000/assets/fonts/Drive-Regular.woff2
46
+
[2026-06-19T11:36:08.836Z] 200 GET http://127.0.0.1:4000/assets/fonts/Drive-Bold.woff2
47
+
[2026-06-19T11:36:08.836Z] 200 GET http://127.0.0.1:4000/assets/fonts/fa-solid-900.woff2
48
+
[2026-06-19T11:36:08.856Z] 200 GET http://127.0.0.1:4000/assets/fonts/Drive-Medium.woff2
49
+
[2026-06-19T11:36:08.856Z] 200 GET http://127.0.0.1:4000/assets/fonts/Drive-Italic.woff2
50
+
[2026-06-19T11:36:08.929Z] 200 GET http://127.0.0.1:4000/7974.877f026c8be0dd96.js
51
+
[2026-06-19T11:36:09.051Z] 200 GET http://127.0.0.1:4000/assets/config.json
52
+
[2026-06-19T11:36:09.051Z] 200 GET http://127.0.0.1:4000/292.7acac647ab59e0b2.js
53
+
[2026-06-19T11:36:09.053Z] 204 GET http://127.0.0.1:8180/server/api/security/csrf
54
+
[2026-06-19T11:36:09.066Z] 200 GET http://127.0.0.1:8180/server/api/authn/status
55
+
[2026-06-19T11:36:09.067Z] 200 GET http://127.0.0.1:8180/server/api/config/properties/google.analytics.key
56
+
[2026-06-19T11:36:09.095Z] 200 GET http://127.0.0.1:8180/server/api
57
+
[2026-06-19T11:36:09.228Z] 200 GET http://127.0.0.1:4000/89.c7c6337aca4f2d28.js
58
+
[2026-06-19T11:36:09.236Z] 200 GET http://127.0.0.1:8180/server/api/config/properties/registration.verification.enabled
59
+
[2026-06-19T11:36:09.240Z] 200 GET http://127.0.0.1:8180/server/api/authz/authorizations/search/object?uri=http://127.0.0.1:8180/server/api/core/sites/6d65c6a2-3fe7-44dd-bacb-79271257c35d&feature=isCollectionAdmin&embed=feature
60
+
[2026-06-19T11:36:09.244Z] 200 GET http://127.0.0.1:8180/server/api/authz/authorizations/search/object?uri=http://127.0.0.1:8180/server/api/core/sites/6d65c6a2-3fe7-44dd-bacb-79271257c35d&feature=isCommunityAdmin&embed=feature
61
+
[2026-06-19T11:36:09.252Z] 200 GET http://127.0.0.1:8180/server/api/authz/authorizations/search/object?uri=http://127.0.0.1:8180/server/api/core/sites/6d65c6a2-3fe7-44dd-bacb-79271257c35d&feature=administratorOf&embed=feature
62
+
[2026-06-19T11:36:09.254Z] 200 GET http://127.0.0.1:8180/server/api/authz/authorizations/search/object?uri=http://127.0.0.1:8180/server/api/core/sites/6d65c6a2-3fe7-44dd-bacb-79271257c35d&feature=canSubmit&embed=feature
63
+
[2026-06-19T11:36:09.257Z] 200 GET http://127.0.0.1:8180/server/api/authz/authorizations/search/object?uri=http://127.0.0.1:8180/server/api/core/sites/6d65c6a2-3fe7-44dd-bacb-79271257c35d&feature=canEditItem&embed=feature
64
+
[2026-06-19T11:36:09.260Z] 200 GET http://127.0.0.1:8180/server/api/authz/authorizations/search/object?uri=http://127.0.0.1:8180/server/api/core/sites/6d65c6a2-3fe7-44dd-bacb-79271257c35d&feature=canManageGroups&embed=feature
65
+
[2026-06-19T11:36:09.299Z] 200 GET http://127.0.0.1:4000/8394.70edf5dd30530a0a.js
66
+
[2026-06-19T11:36:09.299Z] 200 GET http://127.0.0.1:4000/6199.a7e96c457fa0538e.js
67
+
[2026-06-19T11:36:09.301Z] 200 GET http://127.0.0.1:4000/4234.b45310739d150de6.js
68
+
[2026-06-19T11:36:09.301Z] 200 GET http://127.0.0.1:4000/183.640131b33fe2d15e.js
69
+
[2026-06-19T11:36:09.306Z] 200 GET http://127.0.0.1:4000/1993.9691fab03e15f71f.js
70
+
[2026-06-19T11:36:09.329Z] 200 GET http://127.0.0.1:8180/server/api/authz/authorizations/search/object?uri=http://127.0.0.1:8180/server/api/core/sites/6d65c6a2-3fe7-44dd-bacb-79271257c35d&feature=canSendFeedback&embed=feature
71
+
[2026-06-19T11:36:09.350Z] 200 GET http://127.0.0.1:4000/1437.76be55e9f92b1b01.js
72
+
[2026-06-19T11:36:10.314Z] 200 GET http://127.0.0.1:4000/info/end-user-agreement?redirect=%252Fhome
73
+
[2026-06-19T11:36:10.360Z] 200 GET http://127.0.0.1:4000/custom-theme.css
74
+
[2026-06-19T11:36:10.366Z] 200 GET http://127.0.0.1:4000/assets/images/vsb-symbol-logo.svg
75
+
[2026-06-19T11:36:10.373Z] 200 GET http://127.0.0.1:4000/assets/images/vsb-en-logo-w.svg
76
+
[2026-06-19T11:36:10.454Z] 200 GET http://127.0.0.1:4000/styles.0758bf16c5682bf4.css
77
+
[2026-06-19T11:36:10.455Z] 200 GET https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.min.js
78
+
[2026-06-19T11:36:10.457Z] 200 GET https://code.jquery.com/jquery-2.1.4.min.js
79
+
[2026-06-19T11:36:10.457Z] 200 GET http://127.0.0.1:4000/runtime.e9d8b7f8ae1cbcfd.js
80
+
[2026-06-19T11:36:10.459Z] 200 GET http://127.0.0.1:4000/polyfills.5f189aa8f745c99e.js
81
+
[2026-06-19T11:36:10.490Z] 200 GET http://127.0.0.1:4000/scripts.3c162baabeb34571.js
82
+
[2026-06-19T11:36:10.501Z] 200 GET http://127.0.0.1:4000/main.f5182c0e526b9577.js
83
+
[2026-06-19T11:36:10.595Z] 200 GET http://127.0.0.1:4000/assets/fonts/Drive-Regular.woff2
84
+
[2026-06-19T11:36:10.597Z] 200 GET http://127.0.0.1:4000/assets/fonts/Drive-Medium.woff2
85
+
[2026-06-19T11:36:10.599Z] 200 GET http://127.0.0.1:4000/assets/fonts/fa-solid-900.woff2
86
+
[2026-06-19T11:36:10.737Z] 200 GET http://127.0.0.1:4000/assets/fonts/Drive-Italic.woff2
87
+
[2026-06-19T11:36:11.525Z] 200 GET http://127.0.0.1:4000/assets/config.json
88
+
[2026-06-19T11:36:11.527Z] 200 GET http://127.0.0.1:4000/7974.877f026c8be0dd96.js
89
+
[2026-06-19T11:36:11.528Z] 200 GET http://127.0.0.1:4000/292.7acac647ab59e0b2.js
90
+
[2026-06-19T11:36:11.533Z] 204 GET http://127.0.0.1:8180/server/api/security/csrf
91
+
[2026-06-19T11:36:11.590Z] 200 GET http://127.0.0.1:8180/server/api/authn/status
92
+
[2026-06-19T11:36:11.602Z] 200 GET http://127.0.0.1:8180/server/api/config/properties/google.analytics.key
93
+
[2026-06-19T11:36:11.742Z] 200 GET http://127.0.0.1:8180/server/api
94
+
[2026-06-19T11:36:12.369Z] 200 GET http://127.0.0.1:8180/server/api/config/properties/registration.verification.enabled
95
+
[2026-06-19T11:36:12.389Z] 200 GET http://127.0.0.1:8180/server/api/authz/authorizations/search/object?uri=http://127.0.0.1:8180/server/api/core/sites/6d65c6a2-3fe7-44dd-bacb-79271257c35d&feature=isCollectionAdmin&embed=feature
96
+
[2026-06-19T11:36:12.408Z] 200 GET http://127.0.0.1:8180/server/api/authz/authorizations/search/object?uri=http://127.0.0.1:8180/server/api/core/sites/6d65c6a2-3fe7-44dd-bacb-79271257c35d&feature=isCommunityAdmin&embed=feature
97
+
[2026-06-19T11:36:12.458Z] 200 GET http://127.0.0.1:8180/server/api/authz/authorizations/search/object?uri=http://127.0.0.1:8180/server/api/core/sites/6d65c6a2-3fe7-44dd-bacb-79271257c35d&feature=administratorOf&embed=feature
98
+
[2026-06-19T11:36:12.485Z] 200 GET http://127.0.0.1:4000/assets/fonts/Drive-Bold.woff2
99
+
[2026-06-19T11:36:12.507Z] 200 GET http://127.0.0.1:8180/server/api/authz/authorizations/search/object?uri=http://127.0.0.1:8180/server/api/core/sites/6d65c6a2-3fe7-44dd-bacb-79271257c35d&feature=canSubmit&embed=feature
100
+
[2026-06-19T11:36:12.528Z] 200 GET http://127.0.0.1:8180/server/api/authz/authorizations/search/object?uri=http://127.0.0.1:8180/server/api/core/sites/6d65c6a2-3fe7-44dd-bacb-79271257c35d&feature=canEditItem&embed=feature
101
+
[2026-06-19T11:36:12.547Z] 200 GET http://127.0.0.1:8180/server/api/authz/authorizations/search/object?uri=http://127.0.0.1:8180/server/api/core/sites/6d65c6a2-3fe7-44dd-bacb-79271257c35d&feature=canManageGroups&embed=feature
102
+
[2026-06-19T11:36:12.549Z] 200 GET http://127.0.0.1:4000/89.c7c6337aca4f2d28.js
103
+
[2026-06-19T11:36:12.819Z] 200 GET http://127.0.0.1:4000/8394.70edf5dd30530a0a.js
104
+
[2026-06-19T11:36:12.822Z] 200 GET http://127.0.0.1:4000/6199.a7e96c457fa0538e.js
105
+
[2026-06-19T11:36:12.823Z] 200 GET http://127.0.0.1:4000/1993.9691fab03e15f71f.js
106
+
[2026-06-19T11:36:12.838Z] 200 GET http://127.0.0.1:8180/server/api/authz/authorizations/search/object?uri=http://127.0.0.1:8180/server/api/core/sites/6d65c6a2-3fe7-44dd-bacb-79271257c35d&feature=canSendFeedback&embed=feature
107
+
[2026-06-19T11:36:12.838Z] 200 GET http://127.0.0.1:4000/4234.b45310739d150de6.js
108
+
[2026-06-19T11:36:12.839Z] 200 GET http://127.0.0.1:4000/183.640131b33fe2d15e.js
109
+
[2026-06-19T11:36:13.030Z] 200 GET http://127.0.0.1:4000/1437.76be55e9f92b1b01.js
0 commit comments