From 9eb7de13d84d12c4954c58f48c7581b69d4d386a Mon Sep 17 00:00:00 2001 From: mousberg Date: Sun, 25 Jan 2026 11:46:40 +0000 Subject: [PATCH 1/5] UI: refresh dashboard design system - Typography: swap Inter for Space Grotesk (geometric, techy) - Colors: punchier accent red, add teal secondary, warmer darks - Cards: better shadows, hover lift effect, increased padding - Stats: uppercase labels, larger bold values - Buttons: hover lift micro-interaction, glow on primary - Status dots: glow effects and subtle pulse animation - Callouts: gradient backgrounds for depth - Navigation: active state accent bar indicator - Layout: more breathing room, bolder page titles --- ui/src/styles/layout.css | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/ui/src/styles/layout.css b/ui/src/styles/layout.css index c2a5c6fe3..289ef6f0c 100644 --- a/ui/src/styles/layout.css +++ b/ui/src/styles/layout.css @@ -404,6 +404,7 @@ .nav-item.active { color: var(--text-strong); background: var(--accent-subtle); + border-color: rgba(255, 92, 92, 0.15); } .nav-item.active .nav-item__icon { @@ -411,6 +412,18 @@ color: var(--accent); } +.nav-item.active::before { + content: ''; + position: absolute; + left: 0; + top: 50%; + transform: translateY(-50%); + width: 3px; + height: 16px; + background: var(--accent); + border-radius: 0 2px 2px 0; +} + /* =========================================== Content Area =========================================== */ From 6dda12f59e8bb3efa45c28ea38af65aa59809417 Mon Sep 17 00:00:00 2001 From: mousberg Date: Sun, 25 Jan 2026 12:09:28 +0000 Subject: [PATCH 2/5] UI: remove nav active bar indicator --- ui/src/styles/layout.css | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/ui/src/styles/layout.css b/ui/src/styles/layout.css index 289ef6f0c..c2a5c6fe3 100644 --- a/ui/src/styles/layout.css +++ b/ui/src/styles/layout.css @@ -404,7 +404,6 @@ .nav-item.active { color: var(--text-strong); background: var(--accent-subtle); - border-color: rgba(255, 92, 92, 0.15); } .nav-item.active .nav-item__icon { @@ -412,18 +411,6 @@ color: var(--accent); } -.nav-item.active::before { - content: ''; - position: absolute; - left: 0; - top: 50%; - transform: translateY(-50%); - width: 3px; - height: 16px; - background: var(--accent); - border-radius: 0 2px 2px 0; -} - /* =========================================== Content Area =========================================== */ From 26148786d9d4b58f52e0c0dcb856aebfae6ae289 Mon Sep 17 00:00:00 2001 From: mousberg Date: Sun, 25 Jan 2026 13:56:55 +0000 Subject: [PATCH 3/5] UI: dashboard refresh phase 2 - premium polish - Dark theme: warm sepia undertone (#0D0C07 base, #1B1A16 elevated) - Light theme: warm gray tones for premium feel - Page headers: gradient separator for visual hierarchy - Health pill: clearer connected/offline styling - Stat cards: overflow handling for collapsed sidebar - Checkbox alignment: fixes for filters and form grids - Crab loader: animated processing indicator component --- ui/src/styles/base.css | 150 +++++----- ui/src/styles/components.css | 411 ++++++++++++++++++++++++++- ui/src/styles/layout.css | 40 ++- ui/src/ui/app-render.helpers.ts | 9 +- ui/src/ui/app-render.ts | 5 +- ui/src/ui/components/crab-loader.gif | Bin 0 -> 383267 bytes ui/src/ui/components/crab-loader.ts | 257 +++++++++++++++++ ui/src/ui/views/cron.ts | 2 +- ui/src/ui/views/overview.ts | 1 + 9 files changed, 794 insertions(+), 81 deletions(-) create mode 100644 ui/src/ui/components/crab-loader.gif create mode 100644 ui/src/ui/components/crab-loader.ts diff --git a/ui/src/styles/base.css b/ui/src/styles/base.css index 7baab70a2..39d6ced21 100644 --- a/ui/src/styles/base.css +++ b/ui/src/styles/base.css @@ -1,40 +1,40 @@ @import url("https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap"); :root { - /* Background - Warmer dark with depth */ - --bg: #12141a; - --bg-accent: #14161d; - --bg-elevated: #1a1d25; - --bg-hover: #262a35; - --bg-muted: #262a35; + /* Background - Warm dark with sepia undertone */ + --bg: #0D0C07; + --bg-accent: #11100A; + --bg-elevated: #1B1A16; + --bg-hover: #252419; + --bg-muted: #252419; - /* Card / Surface - More contrast between levels */ - --card: #181b22; - --card-foreground: #f4f4f5; - --card-highlight: rgba(255, 255, 255, 0.05); - --popover: #181b22; - --popover-foreground: #f4f4f5; + /* Card / Surface - Clear hierarchy with warm tones */ + --card: #151410; + --card-foreground: #f4f4f0; + --card-highlight: rgba(255, 252, 245, 0.05); + --popover: #151410; + --popover-foreground: #f4f4f0; /* Panel */ - --panel: #12141a; - --panel-strong: #1a1d25; - --panel-hover: #262a35; - --chrome: rgba(18, 20, 26, 0.95); - --chrome-strong: rgba(18, 20, 26, 0.98); + --panel: #0D0C07; + --panel-strong: #1B1A16; + --panel-hover: #252419; + --chrome: rgba(13, 12, 7, 0.95); + --chrome-strong: rgba(13, 12, 7, 0.98); - /* Text - Slightly warmer */ - --text: #e4e4e7; - --text-strong: #fafafa; - --chat-text: #e4e4e7; - --muted: #71717a; - --muted-strong: #52525b; - --muted-foreground: #71717a; + /* Text - Warm white tones */ + --text: #e8e6e0; + --text-strong: #faf9f6; + --chat-text: #e8e6e0; + --muted: #7a776d; + --muted-strong: #5c5952; + --muted-foreground: #7a776d; - /* Border - Subtle but defined */ - --border: #27272a; - --border-strong: #3f3f46; - --border-hover: #52525b; - --input: #27272a; + /* Border - Warm and subtle */ + --border: #2a2820; + --border-strong: #3d3a30; + --border-hover: #524f42; + --input: #2a2820; --ring: #ff5c5c; /* Accent - Punchy signature red */ @@ -47,9 +47,9 @@ --primary: #ff5c5c; --primary-foreground: #ffffff; - /* Secondary - Teal accent for variety */ - --secondary: #1e2028; - --secondary-foreground: #f4f4f5; + /* Secondary - Warm elevated surface */ + --secondary: #1B1A16; + --secondary-foreground: #f4f4f0; --accent-2: #14b8a6; --accent-2-muted: rgba(20, 184, 166, 0.7); --accent-2-subtle: rgba(20, 184, 166, 0.15); @@ -74,7 +74,7 @@ --focus-glow: 0 0 0 2px var(--bg), 0 0 0 4px var(--ring), 0 0 20px var(--accent-glow); /* Grid */ - --grid-line: rgba(255, 255, 255, 0.04); + --grid-line: rgba(255, 252, 245, 0.04); /* Theme transition */ --theme-switch-x: 50%; @@ -111,77 +111,85 @@ color-scheme: dark; } -/* Light theme - Clean with subtle warmth */ +/* Light theme - Premium with warm gray tones and clear hierarchy */ :root[data-theme="light"] { - --bg: #fafafa; - --bg-accent: #f5f5f5; + /* Warm gray backgrounds with clear hierarchy */ + --bg: #f8f8f7; + --bg-accent: #f3f3f1; --bg-elevated: #ffffff; - --bg-hover: #f0f0f0; - --bg-muted: #f0f0f0; - --bg-content: #f5f5f5; + --bg-hover: #ebebea; + --bg-muted: #eeeeed; + --bg-content: #f3f3f1; + /* Cards stand out clearly against background */ --card: #ffffff; - --card-foreground: #18181b; - --card-highlight: rgba(0, 0, 0, 0.03); + --card-foreground: #1a1a1a; + --card-highlight: rgba(255, 255, 255, 0.8); --popover: #ffffff; - --popover-foreground: #18181b; + --popover-foreground: #1a1a1a; - --panel: #fafafa; - --panel-strong: #f5f5f5; - --panel-hover: #ebebeb; - --chrome: rgba(250, 250, 250, 0.95); - --chrome-strong: rgba(250, 250, 250, 0.98); + /* Panel - subtle warm tint */ + --panel: #f8f8f7; + --panel-strong: #f3f3f1; + --panel-hover: #e8e8e6; + --chrome: rgba(248, 248, 247, 0.95); + --chrome-strong: rgba(248, 248, 247, 0.98); - --text: #3f3f46; - --text-strong: #18181b; - --chat-text: #3f3f46; - --muted: #71717a; - --muted-strong: #52525b; - --muted-foreground: #71717a; + /* Text - rich contrast */ + --text: #3d3d3d; + --text-strong: #1a1a1a; + --chat-text: #3d3d3d; + --muted: #6b6b6b; + --muted-strong: #525252; + --muted-foreground: #6b6b6b; - --border: #e4e4e7; - --border-strong: #d4d4d8; - --border-hover: #a1a1aa; - --input: #e4e4e7; + /* Border - subtle definition with warm undertone */ + --border: #e0e0de; + --border-strong: #c8c8c5; + --border-hover: #a0a09d; + --input: #e0e0de; + /* Accent - confident red */ --accent: #dc2626; --accent-hover: #ef4444; --accent-muted: #dc2626; - --accent-subtle: rgba(220, 38, 38, 0.12); + --accent-subtle: rgba(220, 38, 38, 0.1); --accent-foreground: #ffffff; - --accent-glow: rgba(220, 38, 38, 0.15); + --accent-glow: rgba(220, 38, 38, 0.12); --primary: #dc2626; --primary-foreground: #ffffff; - --secondary: #f4f4f5; - --secondary-foreground: #3f3f46; + /* Secondary - warm off-white */ + --secondary: #f0f0ee; + --secondary-foreground: #3d3d3d; --accent-2: #0d9488; --accent-2-muted: rgba(13, 148, 136, 0.75); - --accent-2-subtle: rgba(13, 148, 136, 0.12); + --accent-2-subtle: rgba(13, 148, 136, 0.1); + /* Semantic colors - slightly muted for light theme */ --ok: #16a34a; --ok-muted: rgba(22, 163, 74, 0.75); - --ok-subtle: rgba(22, 163, 74, 0.1); + --ok-subtle: rgba(22, 163, 74, 0.08); --destructive: #dc2626; --destructive-foreground: #fafafa; --warn: #d97706; --warn-muted: rgba(217, 119, 6, 0.75); - --warn-subtle: rgba(217, 119, 6, 0.1); + --warn-subtle: rgba(217, 119, 6, 0.08); --danger: #dc2626; --danger-muted: rgba(220, 38, 38, 0.75); - --danger-subtle: rgba(220, 38, 38, 0.1); + --danger-subtle: rgba(220, 38, 38, 0.08); --info: #2563eb; --focus: rgba(220, 38, 38, 0.2); --focus-glow: 0 0 0 2px var(--bg), 0 0 0 4px var(--ring), 0 0 16px var(--accent-glow); - --grid-line: rgba(0, 0, 0, 0.05); + --grid-line: rgba(0, 0, 0, 0.04); - /* Light shadows */ - --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06); - --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08), 0 0 0 1px rgba(0, 0, 0, 0.04); - --shadow-lg: 0 12px 28px rgba(0, 0, 0, 0.12), 0 0 0 1px rgba(0, 0, 0, 0.04); - --shadow-xl: 0 24px 48px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(0, 0, 0, 0.04); + /* Light shadows - crisper with subtle warmth */ + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05), 0 1px 1px rgba(0, 0, 0, 0.03); + --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.06), 0 1px 3px rgba(0, 0, 0, 0.04); + --shadow-lg: 0 12px 28px rgba(0, 0, 0, 0.08), 0 4px 8px rgba(0, 0, 0, 0.04); + --shadow-xl: 0 24px 48px rgba(0, 0, 0, 0.1), 0 8px 16px rgba(0, 0, 0, 0.05); --shadow-glow: 0 0 24px var(--accent-glow); color-scheme: light; diff --git a/ui/src/styles/components.css b/ui/src/styles/components.css index a78e0ef0a..944560ca1 100644 --- a/ui/src/styles/components.css +++ b/ui/src/styles/components.css @@ -49,6 +49,8 @@ border-color var(--duration-normal) var(--ease-out), box-shadow var(--duration-normal) var(--ease-out); box-shadow: inset 0 1px 0 var(--card-highlight); + min-width: 0; + overflow: hidden; } .stat:hover { @@ -62,6 +64,9 @@ font-weight: 500; text-transform: uppercase; letter-spacing: 0.04em; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .stat-value { @@ -70,6 +75,9 @@ margin-top: 6px; letter-spacing: -0.03em; line-height: 1.1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .stat-value.ok { @@ -201,6 +209,30 @@ color: var(--danger); } +/* Health pill - prominent status indicator */ +.pill--health { + font-weight: 600; + letter-spacing: -0.01em; +} + +.pill--health.ok { + border-color: rgba(34, 197, 94, 0.2); + background: var(--ok-subtle); +} + +.pill--health.ok .mono { + color: var(--ok); +} + +.pill--health.offline { + border-color: rgba(245, 158, 11, 0.2); + background: var(--warn-subtle); +} + +.pill--health.offline .mono { + color: var(--warn); +} + /* =========================================== Theme Toggle =========================================== */ @@ -326,7 +358,7 @@ .btn:active { background: var(--secondary); - transform: translateY(0); + transform: translateY(0) scale(0.98); box-shadow: none; } @@ -447,6 +479,14 @@ .field select:focus { border-color: var(--ring); box-shadow: var(--focus-ring); + outline: none; +} + +.field input:focus-visible, +.field textarea:focus-visible, +.field select:focus-visible { + border-color: var(--ring); + box-shadow: var(--focus-glow); } .field select { @@ -467,16 +507,49 @@ } .field.checkbox { - grid-template-columns: auto 1fr; + display: flex; + flex-direction: row; align-items: center; + gap: 8px; +} + +.field.checkbox span { + order: 2; +} + +.field.checkbox input[type="checkbox"] { + order: 1; + width: 16px; + height: 16px; + margin: 0; + accent-color: var(--accent); + cursor: pointer; +} + +/* Checkboxes in filter rows - align to bottom like other fields */ +.filters .field.checkbox { + align-self: flex-end; + padding-bottom: 10px; +} + +/* Checkboxes in form grids - align to bottom of grid cell */ +.form-grid .field.checkbox { + align-self: end; + padding-bottom: 8px; } .config-form .field.checkbox { + display: grid; grid-template-columns: 18px minmax(0, 1fr); column-gap: 10px; } +.config-form .field.checkbox span { + order: unset; +} + .config-form .field.checkbox input[type="checkbox"] { + order: unset; margin: 0; width: 16px; height: 16px; @@ -522,6 +595,39 @@ font-family: var(--mono); } +/* =========================================== + Keyboard Shortcuts (kbd) + =========================================== */ + +kbd { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 20px; + height: 20px; + padding: 0 6px; + font-family: var(--font-body); + font-size: 11px; + font-weight: 500; + line-height: 1; + color: var(--muted); + background: var(--secondary); + border: 1px solid var(--border); + border-bottom-width: 2px; + border-radius: 4px; + box-shadow: 0 1px 0 var(--border); +} + +kbd + kbd { + margin-left: 4px; +} + +:root[data-theme="light"] kbd { + background: var(--bg); + border-color: var(--border-strong); + box-shadow: 0 1px 0 var(--border-strong); +} + /* =========================================== Callouts - Informative with subtle depth =========================================== */ @@ -554,6 +660,156 @@ color: var(--ok); } +.callout.warn { + border-color: rgba(245, 158, 11, 0.25); + background: linear-gradient(135deg, rgba(245, 158, 11, 0.08) 0%, rgba(245, 158, 11, 0.04) 100%); + color: var(--warn); +} + +/* =========================================== + Empty States - Clear and helpful + =========================================== */ + +.empty-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 12px; + padding: 48px 24px; + text-align: center; + color: var(--muted); +} + +.empty-state__icon { + width: 48px; + height: 48px; + display: flex; + align-items: center; + justify-content: center; + border-radius: var(--radius-lg); + background: var(--secondary); + border: 1px solid var(--border); + color: var(--muted); + margin-bottom: 4px; +} + +.empty-state__icon svg { + width: 24px; + height: 24px; + stroke: currentColor; + fill: none; + stroke-width: 1.5px; + stroke-linecap: round; + stroke-linejoin: round; +} + +.empty-state__title { + font-size: 15px; + font-weight: 600; + color: var(--text); + letter-spacing: -0.01em; +} + +.empty-state__description { + font-size: 13px; + max-width: 320px; + line-height: 1.5; +} + +.empty-state__action { + margin-top: 8px; +} + +/* Disconnected state - More prominent */ +.disconnected-state { + display: flex; + align-items: center; + gap: 14px; + padding: 16px 20px; + border-radius: var(--radius-lg); + border: 1px dashed var(--border-strong); + background: var(--secondary); + animation: rise 0.35s var(--ease-out) backwards; +} + +.disconnected-state__icon { + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + border-radius: var(--radius-md); + background: var(--warn-subtle); + color: var(--warn); + flex-shrink: 0; +} + +.disconnected-state__icon svg { + width: 20px; + height: 20px; + stroke: currentColor; + fill: none; + stroke-width: 2px; + stroke-linecap: round; + stroke-linejoin: round; +} + +.disconnected-state__content { + flex: 1; + min-width: 0; +} + +.disconnected-state__title { + font-size: 14px; + font-weight: 600; + color: var(--text); + letter-spacing: -0.01em; +} + +.disconnected-state__description { + font-size: 13px; + color: var(--muted); + margin-top: 2px; +} + +.disconnected-state__action { + flex-shrink: 0; +} + +/* Loading state placeholder */ +.loading-placeholder { + display: flex; + flex-direction: column; + gap: 12px; + padding: 24px; +} + +.loading-placeholder__bar { + height: 12px; + border-radius: var(--radius-sm); + background: linear-gradient( + 90deg, + var(--secondary) 25%, + var(--bg-hover) 50%, + var(--secondary) 75% + ); + background-size: 200% 100%; + animation: shimmer 1.5s infinite; +} + +.loading-placeholder__bar--short { + width: 60%; +} + +.loading-placeholder__bar--medium { + width: 80%; +} + +.loading-placeholder__bar--full { + width: 100%; +} + /* Compaction indicator */ .compaction-indicator { font-size: 13px; @@ -603,6 +859,23 @@ background: var(--bg); } +/* Light theme cards - subtle shadow for elevation */ +:root[data-theme="light"] .card { + box-shadow: var(--shadow-sm); +} + +:root[data-theme="light"] .card:hover { + box-shadow: var(--shadow-md); +} + +:root[data-theme="light"] .stat { + box-shadow: var(--shadow-sm); +} + +:root[data-theme="light"] .stat:hover { + box-shadow: var(--shadow-md); +} + /* =========================================== Lists =========================================== */ @@ -1485,3 +1758,137 @@ flex-wrap: wrap; gap: 8px; } + +/* =========================================== + Crab Loader - Processing animation + =========================================== */ + +.crab-loader { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 20px; + padding: 32px 24px; +} + +.crab-loader__crab { + position: relative; + animation: crab-walk 0.6s ease-in-out infinite; +} + +.crab-loader__svg { + display: block; +} + +.crab-loader--sm .crab-loader__svg { + width: 32px; + height: auto; +} + +.crab-loader--md .crab-loader__svg { + width: 56px; + height: auto; +} + +.crab-loader--lg .crab-loader__svg { + width: 96px; + height: auto; +} + +.crab-loader__claw-left { + transform-origin: 52px 78px; + animation: claw-pinch-left 0.4s ease-in-out infinite; +} + +.crab-loader__claw-right { + transform-origin: 468px 78px; + animation: claw-pinch-right 0.4s ease-in-out infinite; +} + +.crab-loader__text { + display: flex; + align-items: center; + gap: 4px; + color: var(--muted); + font-size: 14px; + font-weight: 500; +} + +.crab-loader__dots { + display: flex; + gap: 3px; + margin-left: 2px; +} + +.crab-loader__dot { + width: 4px; + height: 4px; + border-radius: var(--radius-full); + background: var(--muted); + animation: dot-bounce 1.2s ease-in-out infinite; +} + +.crab-loader__dot:nth-child(2) { + animation-delay: 0.2s; +} + +.crab-loader__dot:nth-child(3) { + animation-delay: 0.4s; +} + +/* Crab walking animation - side to side sway */ +@keyframes crab-walk { + 0%, 100% { + transform: translateX(-2px) rotate(-2deg); + } + 50% { + transform: translateX(2px) rotate(2deg); + } +} + +/* Left claw pinching */ +@keyframes claw-pinch-left { + 0%, 100% { + transform: rotate(0deg); + } + 50% { + transform: rotate(15deg); + } +} + +/* Right claw pinching */ +@keyframes claw-pinch-right { + 0%, 100% { + transform: rotate(0deg); + } + 50% { + transform: rotate(-15deg); + } +} + +/* Bouncing dots */ +@keyframes dot-bounce { + 0%, 80%, 100% { + opacity: 0.4; + transform: translateY(0); + } + 40% { + opacity: 1; + transform: translateY(-3px); + } +} + +/* Reduced motion */ +@media (prefers-reduced-motion: reduce) { + .crab-loader__crab, + .crab-loader__claw-left, + .crab-loader__claw-right, + .crab-loader__dot { + animation: none; + } + + .crab-loader__dot { + opacity: 0.6; + } +} diff --git a/ui/src/styles/layout.css b/ui/src/styles/layout.css index c2a5c6fe3..cad7a08a5 100644 --- a/ui/src/styles/layout.css +++ b/ui/src/styles/layout.css @@ -430,6 +430,17 @@ background: var(--bg-content); } +/* Light theme nav - subtle background difference */ +:root[data-theme="light"] .nav { + background: var(--bg); + border-right: 1px solid var(--border); +} + +:root[data-theme="light"] .topbar { + background: var(--bg); + box-shadow: 0 1px 0 var(--border); +} + .content--chat { overflow: hidden; padding-bottom: 0; @@ -437,19 +448,20 @@ /* Content header */ .content-header { + position: relative; display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; - padding: 4px 8px; - overflow: hidden; + padding: 4px 8px 12px; + overflow: visible; transform-origin: top center; transition: opacity var(--shell-focus-duration) var(--shell-focus-ease), transform var(--shell-focus-duration) var(--shell-focus-ease), max-height var(--shell-focus-duration) var(--shell-focus-ease), padding var(--shell-focus-duration) var(--shell-focus-ease); - max-height: 80px; + max-height: 90px; } .shell--chat-focus .content-header { @@ -481,6 +493,28 @@ gap: 8px; } +/* Page header separator - subtle gradient fade */ +.content-header::after { + content: ""; + position: absolute; + left: 0; + right: 0; + bottom: 0; + height: 1px; + background: linear-gradient( + 90deg, + transparent 0%, + var(--border) 20%, + var(--border) 80%, + transparent 100% + ); + opacity: 0.6; +} + +.shell--chat-focus .content-header::after { + display: none; +} + /* Chat view header adjustments */ .content--chat .content-header { flex-direction: row; diff --git a/ui/src/ui/app-render.helpers.ts b/ui/src/ui/app-render.helpers.ts index 22f8d90db..331f1b178 100644 --- a/ui/src/ui/app-render.helpers.ts +++ b/ui/src/ui/app-render.helpers.ts @@ -2,7 +2,7 @@ import { html } from "lit"; import { repeat } from "lit/directives/repeat.js"; import type { AppViewState } from "./app-view-state"; -import { iconForTab, pathForTab, titleForTab, type Tab } from "./navigation"; +import { iconForTab, pathForTab, titleForTab, TAB_GROUPS, type Tab } from "./navigation"; import { icons } from "./icons"; import { loadChatHistory } from "./controllers/chat"; import { syncUrlWithSessionKey } from "./app-settings"; @@ -28,6 +28,13 @@ export function renderTab(state: AppViewState, tab: Tab) { return; } event.preventDefault(); + // Auto-expand the group containing this tab + const group = TAB_GROUPS.find((g) => g.tabs.includes(tab)); + if (group && state.settings.navGroupsCollapsed[group.label]) { + const next = { ...state.settings.navGroupsCollapsed }; + next[group.label] = false; + state.applySettings({ ...state.settings, navGroupsCollapsed: next }); + } state.setTab(tab); }} title=${titleForTab(tab)} diff --git a/ui/src/ui/app-render.ts b/ui/src/ui/app-render.ts index db29bd7ec..36fd0580d 100644 --- a/ui/src/ui/app-render.ts +++ b/ui/src/ui/app-render.ts @@ -138,7 +138,7 @@ export function renderApp(state: AppViewState) {
-
+
Health ${state.connected ? "OK" : "Offline"} @@ -149,9 +149,8 @@ export function renderApp(state: AppViewState) {
+ `; } From 31a3cbea852a75d81e51b62da3d141f5acfc998e Mon Sep 17 00:00:00 2001 From: mousberg Date: Sun, 25 Jan 2026 14:59:55 +0000 Subject: [PATCH 4/5] UI: simplify theme transition to clean crossfade --- ui/src/styles/base.css | 35 ++++++++--------------------------- 1 file changed, 8 insertions(+), 27 deletions(-) diff --git a/ui/src/styles/base.css b/ui/src/styles/base.css index 39d6ced21..69e7a0fc4 100644 --- a/ui/src/styles/base.css +++ b/ui/src/styles/base.css @@ -214,36 +214,17 @@ body { -moz-osx-font-smoothing: grayscale; } -/* Theme transition */ -@keyframes theme-circle-transition { - 0% { - clip-path: circle(0% at var(--theme-switch-x, 50%) var(--theme-switch-y, 50%)); - } - 100% { - clip-path: circle(150% at var(--theme-switch-x, 50%) var(--theme-switch-y, 50%)); - } -} - -html.theme-transition { - view-transition-name: theme; -} - -html.theme-transition::view-transition-old(theme) { - mix-blend-mode: normal; - animation: none; - z-index: 1; -} - -html.theme-transition::view-transition-new(theme) { - mix-blend-mode: normal; - z-index: 2; - animation: theme-circle-transition 0.4s var(--ease-out) forwards; +/* Theme transition - clean crossfade */ +::view-transition-old(root), +::view-transition-new(root) { + animation-duration: 0.15s; + animation-timing-function: ease-out; } @media (prefers-reduced-motion: reduce) { - html.theme-transition::view-transition-old(theme), - html.theme-transition::view-transition-new(theme) { - animation: none !important; + ::view-transition-old(root), + ::view-transition-new(root) { + animation: none; } } From c70af36237b7771347152d8b4d024f03b35b8615 Mon Sep 17 00:00:00 2001 From: mousberg Date: Mon, 26 Jan 2026 08:41:53 +0000 Subject: [PATCH 5/5] UI: fix light mode icon button active state specificity --- ui/src/styles/chat/layout.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ui/src/styles/chat/layout.css b/ui/src/styles/chat/layout.css index e137cb8c8..255920780 100644 --- a/ui/src/styles/chat/layout.css +++ b/ui/src/styles/chat/layout.css @@ -228,6 +228,12 @@ color: var(--text); } +:root[data-theme="light"] .btn--icon.active { + border-color: var(--accent); + background: var(--accent-subtle); + color: var(--accent); +} + .btn--icon svg { display: block; width: 18px;