diff --git a/cortex/auth_middleware.py b/cortex/auth_middleware.py index 2f3caf0..ec813a4 100644 --- a/cortex/auth_middleware.py +++ b/cortex/auth_middleware.py @@ -17,7 +17,7 @@ from starlette.responses import RedirectResponse, JSONResponse from auth_utils import COOKIE_NAME, decode_token # Paths that don't require a session cookie -_PUBLIC = {"/login", "/logout", "/health"} +_PUBLIC = {"/login", "/logout", "/health", "/manifest.json", "/sw.js", "/favicon.ico"} # Path prefixes that are always public (setup flow + webhooks + Google OAuth) _PUBLIC_PREFIXES = ("/setup/", "/channels/", "/webhook/", "/auth/google") diff --git a/cortex/routers/ui.py b/cortex/routers/ui.py index 614b9a2..3db0663 100644 --- a/cortex/routers/ui.py +++ b/cortex/routers/ui.py @@ -90,6 +90,18 @@ async def favicon(): return Response(content=_FAVICON_SVG, media_type="image/svg+xml") +@router.get("/sw.js", include_in_schema=False) +async def service_worker(): + from fastapi.responses import FileResponse + return FileResponse(str(_STATIC / "sw.js"), media_type="application/javascript") + + +@router.get("/manifest.json", include_in_schema=False) +async def web_manifest(): + from fastapi.responses import FileResponse + return FileResponse(str(_STATIC / "manifest.json"), media_type="application/manifest+json") + + # --------------------------------------------------------------------------- # Root redirect # --------------------------------------------------------------------------- diff --git a/cortex/static/app.js b/cortex/static/app.js index a74ca2a..5fedb18 100644 --- a/cortex/static/app.js +++ b/cortex/static/app.js @@ -1549,10 +1549,14 @@ // ── Theme toggle ────────────────────────────────────────────── const themeBtn = document.getElementById('theme-btn'); + const _metaThemeColor = document.getElementById('meta-theme-color'); + const _themeColors = { dark: '#1a1228', light: '#f2eef9' }; + function applyTheme(theme) { document.documentElement.setAttribute('data-theme', theme); themeBtn.textContent = theme === 'dark' ? '☀' : '☾'; themeBtn.title = theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'; + if (_metaThemeColor) _metaThemeColor.content = _themeColors[theme] || _themeColors.dark; } { @@ -1729,3 +1733,8 @@ const stored = get_stored_session(); if (stored) resumeSession(stored, true).catch(clear_stored_session); } + + // ── Service worker registration ─────────────────────────────── + if ('serviceWorker' in navigator) { + navigator.serviceWorker.register('/sw.js').catch(() => {}); + } diff --git a/cortex/static/icon-192.png b/cortex/static/icon-192.png new file mode 100644 index 0000000..19fc079 Binary files /dev/null and b/cortex/static/icon-192.png differ diff --git a/cortex/static/icon-512.png b/cortex/static/icon-512.png new file mode 100644 index 0000000..b65642d Binary files /dev/null and b/cortex/static/icon-512.png differ diff --git a/cortex/static/icon.svg b/cortex/static/icon.svg new file mode 100644 index 0000000..2b8af3c --- /dev/null +++ b/cortex/static/icon.svg @@ -0,0 +1,4 @@ + diff --git a/cortex/static/index.html b/cortex/static/index.html index cbd55c3..7ea35a9 100644 --- a/cortex/static/index.html +++ b/cortex/static/index.html @@ -5,6 +5,13 @@