From 341bc42778fdd84761a8e735ad1f3b503cc440ee Mon Sep 17 00:00:00 2001 From: aaverbitskiy Date: Fri, 14 Aug 2026 11:29:51 +0000 Subject: [PATCH] auth ui: prevent flash of manager-only controls on load Anonymous visitors briefly saw the manager-only filters (Brand/Manager/Status), checkboxes and appearance menu on refresh, because applyRoleUi() hides them only after the async Keycloak check-sso resolves. Fix: mark those elements .manager-only and hide them pre-paint via a .role-pending class set on by the inline head script, gated by a render-blocking CSS rule (html.role-pending .manager-only { display:none }). applyRoleUi() drops .role-pending once the role is known and then applies the final per-element state. Default is now fail-closed (hidden until confirmed manager), so anonymous never flashes them; managers see the controls appear a moment after load. --- frontend/index.html | 14 +++++++++----- frontend/src/main.ts | 3 +++ frontend/src/styles.css | 5 +++++ 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/frontend/index.html b/frontend/index.html index fb8b0f3..6c87cd8 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -9,6 +9,10 @@ // hidden — force light regardless of stored/OS preference. To re-enable, // restore the localStorage/prefers-color-scheme logic and unhide the toggle. document.documentElement.setAttribute('data-theme', 'light'); + // Role is unknown until Keycloak check-sso resolves (async). Start in the + // "pending" state that hides manager-only controls, so anonymous visitors + // never see a flash of them before applyRoleUi() runs. + document.documentElement.classList.add('role-pending'); @@ -47,7 +51,7 @@ -
+
-
+
-
+
-
+
@@ -128,7 +132,7 @@
-
+
diff --git a/frontend/src/main.ts b/frontend/src/main.ts index 6a49ba8..bb66ed2 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -59,6 +59,9 @@ const el = { // sense for the full-data (manager) tier so the anonymous view stays clean. function applyRoleUi(): void { const manager = isManager(); + // Role resolved — leave the pre-paint "pending" state; from here explicit + // per-element display below is the source of truth. + document.documentElement.classList.remove('role-pending'); const hideField = (node: Element | null): void => { const f = node?.closest('.field') as HTMLElement | null; if (f) f.style.display = manager ? '' : 'none'; diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 8a0295e..d802aa1 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -313,6 +313,11 @@ input::placeholder { color: var(--text-muted); } } .theme-toggle:hover { border-color: var(--border-strong); background: var(--surface); } +/* Anti-flash: until the role is resolved (Keycloak check-sso), hide every + manager-only control so anonymous visitors never see them flash on load. + applyRoleUi() drops .role-pending and then sets each element's final state. */ +html.role-pending .manager-only { display: none !important; } + /* Login / logout button — pinned to the header's top-right corner */ .auth-wrap { position: absolute; right: 18px; top: 12px; z-index: 3; }