diff --git a/resources/js/app.js b/resources/js/app.js
index 129251f..3b7ad81 100644
--- a/resources/js/app.js
+++ b/resources/js/app.js
@@ -4,25 +4,52 @@ import './webauthn'; // exposes window.clusevWebauthn (register/login ceremonies
window.Pusher = Pusher;
-// Reverb (Pusher protocol). The endpoint is injected at runtime by the layout
+// Reverb (Pusher protocol). The endpoint is injected at RUNTIME by the app layout
// (window.__clusev.reverb), derived from the effective panel domain — so changing the
-// domain from the dashboard needs no JS rebuild. Falls back to Vite env for HMR/dev.
-const rc = (typeof window !== 'undefined' && window.__clusev && window.__clusev.reverb) || {};
-const reverbScheme = rc.scheme ?? import.meta.env.VITE_REVERB_SCHEME ?? 'https';
-const reverbPort = Number(rc.port ?? import.meta.env.VITE_REVERB_PORT ?? (reverbScheme === 'https' ? 443 : 80));
-// Channels are PRIVATE: Echo POSTs to /broadcasting/auth (web group → PanelScheme + session)
-// before subscribing, so we must send the CSRF token the layout exposes as .
-const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content;
-window.Echo = new Echo({
- broadcaster: 'reverb',
- key: rc.key ?? import.meta.env.VITE_REVERB_APP_KEY,
- wsHost: rc.host ?? import.meta.env.VITE_REVERB_HOST,
- wsPort: reverbPort,
- wssPort: reverbPort,
- forceTLS: reverbScheme === 'https',
- enabledTransports: ['ws', 'wss'],
- auth: { headers: { 'X-CSRF-TOKEN': csrfToken } },
-});
+// domain from the dashboard needs no JS rebuild. The prod build deliberately bakes NO
+// VITE_REVERB_* env (it would freeze the build-time domain); those env reads are only a
+// dev/HMR convenience.
+//
+// CRITICAL: this MUST NOT throw at module top level. Auth pages (login / forced password
+// change) use a layout that injects no __clusev, and the prod bundle has no VITE_REVERB_*
+// fallback, so the config is legitimately absent there. A thrown `new Echo({key: undefined})`
+// would abort this whole module BEFORE the alpine:init block below registers cmdk /
+// modalTrigger / dualChart — leaving the command-palette's `x-data="cmdk(...)"` unresolved.
+// Alpine then strips x-cloak but can't evaluate `x-show="open"`, so the palette renders as a
+// full-screen, undismissable overlay. So: construct only when a real config is present, guard
+// it, and (re)try on navigation since the config first appears on app-layout pages.
+function ensureEcho() {
+ if (window.Echo) return; // singleton — construct once
+ const rc = (typeof window !== 'undefined' && window.__clusev && window.__clusev.reverb) || {};
+ const key = rc.key ?? import.meta.env.VITE_REVERB_APP_KEY;
+ const host = rc.host ?? import.meta.env.VITE_REVERB_HOST;
+ if (! key || ! host) return; // no realtime config (e.g. auth pages) — skip, never throw
+
+ const scheme = rc.scheme ?? import.meta.env.VITE_REVERB_SCHEME ?? 'https';
+ const port = Number(rc.port ?? import.meta.env.VITE_REVERB_PORT ?? (scheme === 'https' ? 443 : 80));
+ // Channels are PRIVATE: Echo POSTs to /broadcasting/auth (web group → PanelScheme + session)
+ // before subscribing, so we must send the CSRF token the layout exposes as .
+ const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content;
+ try {
+ window.Echo = new Echo({
+ broadcaster: 'reverb',
+ key,
+ wsHost: host,
+ wsPort: port,
+ wssPort: port,
+ forceTLS: scheme === 'https',
+ enabledTransports: ['ws', 'wss'],
+ auth: { headers: { 'X-CSRF-TOKEN': csrfToken } },
+ });
+ } catch (e) {
+ // A realtime/websocket misconfig must never take down the rest of the UI.
+ console.error('[clusev] realtime (Echo/Reverb) init failed — continuing without live updates', e);
+ }
+}
+ensureEcho();
+// The reverb config first appears when an app-layout page mounts; on a SPA navigation INTO it
+// (e.g. forced password change → dashboard) this module does not re-run, so try again then.
+document.addEventListener('livewire:navigated', ensureEcho);
// ── Modal-open detection ────────────────────────────────────────────────
// wire-elements/modal emits NO "opened" event; the only reliable, package-version-