+ )
+}
diff --git a/apps/pwa/src/components/CalendarShell.tsx b/apps/pwa/src/components/CalendarShell.tsx
index 542d417..fea479b 100644
--- a/apps/pwa/src/components/CalendarShell.tsx
+++ b/apps/pwa/src/components/CalendarShell.tsx
@@ -46,6 +46,7 @@ import { hydrateEvents } from '../lib/hydrateEvents.js'
import { maybeRedirectToLogin, clearLoginRedirect } from '../lib/loginRedirect.js'
import { buildCalendarConfig, SX_FIRST_DAY_OF_WEEK } from '../lib/calendarConfig.js'
import { useCalendarStore } from '../store/calendarStore.js'
+import { AuthSplash } from './AuthSplash.js'
import { EventDetailPopover } from './EventDetailPopover.js'
import { EventForm } from './EventForm.js'
import { DeleteConfirmationDialog } from './DeleteConfirmationDialog.js'
@@ -209,29 +210,32 @@ export function CalendarShell({ onOpenSettings }: { onOpenSettings?: () => void
// ── Render helpers ────────────────────────────────────────────────────────
- // Determine which content to show in the calendar area
- const isInitialLoading = meQuery.isLoading || (eventsQuery.isLoading && !eventsQuery.data)
+ // Determine which content to show in the calendar area.
+ // Note: meQuery.isLoading is handled above by an early AuthSplash return — it
+ // will always be false when we reach this line (meQuery.isSuccess is guaranteed).
+ const isInitialLoading = eventsQuery.isLoading && !eventsQuery.data
const isEventsError = eventsQuery.isError
const phone = isPhone()
- // ── Sign-in required ───────────────────────────────────────────────────────
+ // ── Auth splash (D-10) ────────────────────────────────────────────────────
+ // Gate the calendar render on auth state so no calendar shell, skeleton, or
+ // "Sign-in required" alert paints before Authelia (D-10 success criterion 5).
+ //
+ // loading: meQuery is still pending — show the neutral "Signing you in" splash.
+ // No skeleton, no CalendarContent — nothing app-specific until authed.
+ // isError: meQuery failed — the maybeRedirectToLogin() useEffect fires immediately
+ // after this render. Show the "redirecting" splash while the browser navigates.
+ // If the one-shot guard is exhausted (flag already set), maybeRedirectToLogin
+ // returns false and this splash stays — the dead-end "Tap to try again" is
+ // rendered by AuthSplash's dead-end state (wired in Task 3 via sessionExpired).
+ if (meQuery.isLoading) {
+ return
+ }
if (meQuery.isError) {
- return (
-
- Sign-in required
-
- )
+ // useEffect at line 196 calls maybeRedirectToLogin() — splash shows while redirect fires
+ return
}
// ── Calendar content ───────────────────────────────────────────────────────