From 3f6858513f271a3472ba078ad354ee157eb94935 Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Sat, 4 Apr 2026 12:53:39 +0300 Subject: [PATCH] fix: frontend UX improvements (SSE status, responsive tables, dark mode, login toggle, theme) - Add SSE connection status banner showing when real-time updates are lost (UX-H8, UX-M1) - Add password visibility toggle on login page (UX-H10) - Add dark mode variants to stat card backgrounds (UX-M11) - Add overflow-x-auto to tables for mobile responsiveness (UX-H9) - Add flex-wrap to stage header for mobile overflow (UX-H11) - Fix theme store system preference listener reactivity (UX-M12) - Parallelize registry health checks (UX-L4) --- web/src/lib/stores/theme.ts | 24 +++++++++++++---- web/src/routes/+layout.svelte | 14 ++++++++++ web/src/routes/+page.svelte | 4 +-- web/src/routes/login/+page.svelte | 26 +++++++++++++------ web/src/routes/projects/+page.svelte | 2 +- web/src/routes/projects/[id]/+page.svelte | 2 +- web/src/routes/projects/[id]/env/+page.svelte | 4 +-- .../routes/projects/[id]/volumes/+page.svelte | 2 +- .../routes/settings/registries/+page.svelte | 5 ++-- 9 files changed, 61 insertions(+), 22 deletions(-) diff --git a/web/src/lib/stores/theme.ts b/web/src/lib/stores/theme.ts index 95dfe40..2d5e666 100644 --- a/web/src/lib/stores/theme.ts +++ b/web/src/lib/stores/theme.ts @@ -26,16 +26,30 @@ themeMode.subscribe((value) => { } }); +/** + * Tracks system color-scheme preference changes so that the derived store + * re-evaluates when the OS theme changes while mode is 'system'. + */ +const systemDark = writable( + typeof window !== 'undefined' + ? window.matchMedia('(prefers-color-scheme: dark)').matches + : false +); + +if (typeof window !== 'undefined') { + const mq = window.matchMedia('(prefers-color-scheme: dark)'); + mq.addEventListener('change', (e) => { + systemDark.set(e.matches); + }); +} + /** * Resolved theme based on mode and system preference. * Returns 'light' or 'dark'. */ -export const resolvedTheme = derived(themeMode, ($mode) => { +export const resolvedTheme = derived([themeMode, systemDark], ([$mode, $dark]) => { if ($mode === 'system') { - if (typeof window !== 'undefined') { - return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; - } - return 'light'; + return $dark ? 'dark' : 'light'; } return $mode; }); diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte index 73c6a4c..1ac8ee9 100644 --- a/web/src/routes/+layout.svelte +++ b/web/src/routes/+layout.svelte @@ -43,6 +43,7 @@ let healthChecked = $state(false); let healthInterval: ReturnType | null = null; let hintsExpanded = $state(false); + let sseConnected = $state(true); const dockerConnected = $derived(dockerHealth?.connected ?? false); @@ -90,6 +91,12 @@ }, onDeployStatus(payload) { instanceStatusStore.notifyDeploy(payload); + }, + onOpen() { + sseConnected = true; + }, + onError() { + sseConnected = false; } }); @@ -278,6 +285,13 @@ {$t('app.name')} + + {#if !sseConnected} +
+ Real-time connection lost. Reconnecting... +
+ {/if} +
diff --git a/web/src/routes/+page.svelte b/web/src/routes/+page.svelte index 5902ac2..61d9c45 100644 --- a/web/src/routes/+page.svelte +++ b/web/src/routes/+page.svelte @@ -97,7 +97,7 @@
-
+
@@ -106,7 +106,7 @@
-
+
diff --git a/web/src/routes/login/+page.svelte b/web/src/routes/login/+page.svelte index 816491c..c5d98a9 100644 --- a/web/src/routes/login/+page.svelte +++ b/web/src/routes/login/+page.svelte @@ -11,6 +11,7 @@ let password = $state(''); let error = $state(''); let loading = $state(false); + let showPassword = $state(false); // Apply theme on login page too. $effect(() => { @@ -106,14 +107,23 @@
- +
+ + +