detached cd2db31

fix(ui): use theme semantic colors for toasts and filter status message

- Changed filter toast from ToastSuccess (hardcoded green) to ToastInfo (uses accent color)
- Updated filter toast message from "Filters active" to "Ad blocker ready" for clarity
- Refactored theme system to define semantic colors (success, warning, destructive) as CSS variables
- Replaced all hardcoded status colors with theme variables across webui components
- Ensures toast and status indicators follow user's theme preferences consistently
commit
cd2db314138341b70fbfbf890bba882284f9c122
author
bnema · · 2025-12-21 21:56 UTC
parent
9b5fe54
signature
unsigned
14 files · +152 -58
M internal/ui/app.go +1 -1
@@ -862,7 +862,7 @@ func (a *App) showFilterStatus(ctx context.Context, status filtering.FilterStatu
862862 log.Debug().Msg("applied filters to all existing webviews")
863863 }
864864 if a.appToaster != nil {
865- a.appToaster.Show(ctx, fmt.Sprintf("Filters active (%s)", status.Version), component.ToastSuccess)
865+ a.appToaster.Show(ctx, fmt.Sprintf("Ad blocker ready (%s)", status.Version), component.ToastInfo)
866866 }
867867 case filtering.StateError:
868868 if a.appToaster != nil {
@@ -862,7 +862,7 @@ func (a *App) showFilterStatus(ctx context.Context, status filtering.FilterStatu
862 log.Debug().Msg("applied filters to all existing webviews")862 log.Debug().Msg("applied filters to all existing webviews")
863 }863 }
864 if a.appToaster != nil {864 if a.appToaster != nil {
865- a.appToaster.Show(ctx, fmt.Sprintf("Filters active (%s)", status.Version), component.ToastSuccess)865+ a.appToaster.Show(ctx, fmt.Sprintf("Ad blocker ready (%s)", status.Version), component.ToastInfo)
866 }866 }
867 case filtering.StateError:867 case filtering.StateError:
868 if a.appToaster != nil {868 if a.appToaster != nil {
M internal/ui/theme/css.go +7 -7
@@ -518,22 +518,22 @@ func generateToasterCSS(p Palette) string {
518518 color: var(--bg);
519519 }
520520
521-/* Toast level: success (green) */
521+/* Toast level: success */
522522 .toast-success {
523- background-color: alpha(#4ade80, 0.9);
523+ background-color: alpha(var(--success), 0.9);
524524 color: var(--bg);
525525 }
526526
527-/* Toast level: warning (yellow/amber) */
527+/* Toast level: warning */
528528 .toast-warning {
529- background-color: alpha(#fbbf24, 0.9);
529+ background-color: alpha(var(--warning), 0.9);
530530 color: var(--bg);
531531 }
532532
533-/* Toast level: error (red) */
533+/* Toast level: error */
534534 .toast-error {
535- background-color: alpha(#ef4444, 0.9);
536- color: white;
535+ background-color: alpha(var(--destructive), 0.9);
536+ color: var(--bg);
537537 }
538538 `
539539 }
@@ -518,22 +518,22 @@ func generateToasterCSS(p Palette) string {
518 color: var(--bg);518 color: var(--bg);
519 }519 }
520 520
521-/* Toast level: success (green) */521+/* Toast level: success */
522 .toast-success {522 .toast-success {
523- background-color: alpha(#4ade80, 0.9);523+ background-color: alpha(var(--success), 0.9);
524 color: var(--bg);524 color: var(--bg);
525 }525 }
526 526
527-/* Toast level: warning (yellow/amber) */527+/* Toast level: warning */
528 .toast-warning {528 .toast-warning {
529- background-color: alpha(#fbbf24, 0.9);529+ background-color: alpha(var(--warning), 0.9);
530 color: var(--bg);530 color: var(--bg);
531 }531 }
532 532
533-/* Toast level: error (red) */533+/* Toast level: error */
534 .toast-error {534 .toast-error {
535- background-color: alpha(#ef4444, 0.9);535+ background-color: alpha(var(--destructive), 0.9);
536- color: white;536+ color: var(--bg);
537 }537 }
538 `538 `
539 }539 }
M internal/ui/theme/palette.go +26 -0
@@ -18,6 +18,10 @@ type Palette struct {
1818 Muted string // Secondary/disabled text
1919 Accent string // Primary accent color (actions, highlights)
2020 Border string // Border and divider lines
21+ // Semantic status colors (not user-editable, derived defaults)
22+ Success string // Success/positive feedback
23+ Warning string // Warning/caution feedback
24+ Destructive string // Error/destructive actions
2125 }
2226
2327 // DefaultDarkPalette returns the default dark theme palette.
@@ -30,6 +34,9 @@ func DefaultDarkPalette() Palette {
3034 Muted: "#909090",
3135 Accent: "#4ade80",
3236 Border: "#333333",
37+ Success: "#4ade80",
38+ Warning: "#fbbf24",
39+ Destructive: "#ef4444",
3340 }
3441 }
3542
@@ -43,6 +50,9 @@ func DefaultLightPalette() Palette {
4350 Muted: "#666666",
4451 Accent: "#22c55e",
4552 Border: "#dddddd",
53+ Success: "#22c55e",
54+ Warning: "#f59e0b",
55+ Destructive: "#dc2626",
4656 }
4757 }
4858
@@ -67,6 +77,10 @@ func PaletteFromConfig(cfg *config.ColorPalette, isDark bool) Palette {
6777 Muted: coalesce(cfg.Muted, defaults.Muted),
6878 Accent: coalesce(cfg.Accent, defaults.Accent),
6979 Border: coalesce(cfg.Border, defaults.Border),
80+ // Semantic colors always use defaults (not user-editable)
81+ Success: defaults.Success,
82+ Warning: defaults.Warning,
83+ Destructive: defaults.Destructive,
7084 }
7185 }
7286
@@ -124,6 +138,9 @@ func (p Palette) ToCSSVars() string {
124138 sb.WriteString(" --muted: " + p.Muted + ";\n")
125139 sb.WriteString(" --accent: " + p.Accent + ";\n")
126140 sb.WriteString(" --border: " + p.Border + ";\n")
141+ sb.WriteString(" --success: " + p.Success + ";\n")
142+ sb.WriteString(" --warning: " + p.Warning + ";\n")
143+ sb.WriteString(" --destructive: " + p.Destructive + ";\n")
127144 return sb.String()
128145 }
129146
@@ -141,6 +158,15 @@ func (p Palette) ToWebCSSVars() string {
141158 sb.WriteString(" --muted: " + p.SurfaceVariant + " !important;\n")
142159 sb.WriteString(" --muted-foreground: " + p.Muted + " !important;\n")
143160 sb.WriteString(" --border: " + p.Border + " !important;\n")
161+ // Ring follows primary (accent)
162+ sb.WriteString(" --ring: " + p.Accent + " !important;\n")
163+ // Semantic colors from palette defaults
164+ sb.WriteString(" --success: " + p.Success + " !important;\n")
165+ sb.WriteString(" --success-foreground: " + p.Background + " !important;\n")
166+ sb.WriteString(" --warning: " + p.Warning + " !important;\n")
167+ sb.WriteString(" --warning-foreground: " + p.Text + " !important;\n")
168+ sb.WriteString(" --destructive: " + p.Destructive + " !important;\n")
169+ sb.WriteString(" --destructive-foreground: " + p.Background + " !important;\n")
144170 // Keep GTK names for compatibility
145171 sb.WriteString(" --bg: " + p.Background + " !important;\n")
146172 sb.WriteString(" --surface: " + p.Surface + " !important;\n")
@@ -18,6 +18,10 @@ type Palette struct {
18 Muted string // Secondary/disabled text18 Muted string // Secondary/disabled text
19 Accent string // Primary accent color (actions, highlights)19 Accent string // Primary accent color (actions, highlights)
20 Border string // Border and divider lines20 Border string // Border and divider lines
21+ // Semantic status colors (not user-editable, derived defaults)
22+ Success string // Success/positive feedback
23+ Warning string // Warning/caution feedback
24+ Destructive string // Error/destructive actions
21 }25 }
22 26
23 // DefaultDarkPalette returns the default dark theme palette.27 // DefaultDarkPalette returns the default dark theme palette.
@@ -30,6 +34,9 @@ func DefaultDarkPalette() Palette {
30 Muted: "#909090",34 Muted: "#909090",
31 Accent: "#4ade80",35 Accent: "#4ade80",
32 Border: "#333333",36 Border: "#333333",
37+ Success: "#4ade80",
38+ Warning: "#fbbf24",
39+ Destructive: "#ef4444",
33 }40 }
34 }41 }
35 42
@@ -43,6 +50,9 @@ func DefaultLightPalette() Palette {
43 Muted: "#666666",50 Muted: "#666666",
44 Accent: "#22c55e",51 Accent: "#22c55e",
45 Border: "#dddddd",52 Border: "#dddddd",
53+ Success: "#22c55e",
54+ Warning: "#f59e0b",
55+ Destructive: "#dc2626",
46 }56 }
47 }57 }
48 58
@@ -67,6 +77,10 @@ func PaletteFromConfig(cfg *config.ColorPalette, isDark bool) Palette {
67 Muted: coalesce(cfg.Muted, defaults.Muted),77 Muted: coalesce(cfg.Muted, defaults.Muted),
68 Accent: coalesce(cfg.Accent, defaults.Accent),78 Accent: coalesce(cfg.Accent, defaults.Accent),
69 Border: coalesce(cfg.Border, defaults.Border),79 Border: coalesce(cfg.Border, defaults.Border),
80+ // Semantic colors always use defaults (not user-editable)
81+ Success: defaults.Success,
82+ Warning: defaults.Warning,
83+ Destructive: defaults.Destructive,
70 }84 }
71 }85 }
72 86
@@ -124,6 +138,9 @@ func (p Palette) ToCSSVars() string {
124 sb.WriteString(" --muted: " + p.Muted + ";\n")138 sb.WriteString(" --muted: " + p.Muted + ";\n")
125 sb.WriteString(" --accent: " + p.Accent + ";\n")139 sb.WriteString(" --accent: " + p.Accent + ";\n")
126 sb.WriteString(" --border: " + p.Border + ";\n")140 sb.WriteString(" --border: " + p.Border + ";\n")
141+ sb.WriteString(" --success: " + p.Success + ";\n")
142+ sb.WriteString(" --warning: " + p.Warning + ";\n")
143+ sb.WriteString(" --destructive: " + p.Destructive + ";\n")
127 return sb.String()144 return sb.String()
128 }145 }
129 146
@@ -141,6 +158,15 @@ func (p Palette) ToWebCSSVars() string {
141 sb.WriteString(" --muted: " + p.SurfaceVariant + " !important;\n")158 sb.WriteString(" --muted: " + p.SurfaceVariant + " !important;\n")
142 sb.WriteString(" --muted-foreground: " + p.Muted + " !important;\n")159 sb.WriteString(" --muted-foreground: " + p.Muted + " !important;\n")
143 sb.WriteString(" --border: " + p.Border + " !important;\n")160 sb.WriteString(" --border: " + p.Border + " !important;\n")
161+ // Ring follows primary (accent)
162+ sb.WriteString(" --ring: " + p.Accent + " !important;\n")
163+ // Semantic colors from palette defaults
164+ sb.WriteString(" --success: " + p.Success + " !important;\n")
165+ sb.WriteString(" --success-foreground: " + p.Background + " !important;\n")
166+ sb.WriteString(" --warning: " + p.Warning + " !important;\n")
167+ sb.WriteString(" --warning-foreground: " + p.Text + " !important;\n")
168+ sb.WriteString(" --destructive: " + p.Destructive + " !important;\n")
169+ sb.WriteString(" --destructive-foreground: " + p.Background + " !important;\n")
144 // Keep GTK names for compatibility170 // Keep GTK names for compatibility
145 sb.WriteString(" --bg: " + p.Background + " !important;\n")171 sb.WriteString(" --bg: " + p.Background + " !important;\n")
146 sb.WriteString(" --surface: " + p.Surface + " !important;\n")172 sb.WriteString(" --surface: " + p.Surface + " !important;\n")
M webui/src/pages/homepage/favorites/FavoriteEditor.svelte +7 -7
@@ -455,18 +455,18 @@
455455 }
456456
457457 .btn-danger-outline:hover {
458- color: #ef4444;
459- border-color: #ef4444;
458+ color: var(--destructive);
459+ border-color: var(--destructive);
460460 }
461461
462462 .btn-danger {
463- background: #b91c1c;
464- color: #fef2f2;
465- border-color: #991b1b;
463+ background: var(--destructive);
464+ color: var(--destructive-foreground);
465+ border-color: color-mix(in srgb, var(--destructive) 80%, black 20%);
466466 }
467467
468468 .btn-danger:hover {
469- background: #dc2626;
469+ background: color-mix(in srgb, var(--destructive) 85%, white 15%);
470470 }
471471
472472 .btn-cancel {
@@ -489,7 +489,7 @@
489489 .confirm-text {
490490 flex: 1;
491491 font-size: 0.72rem;
492- color: #ef4444;
492+ color: var(--destructive);
493493 font-weight: 500;
494494 }
495495 </style>
@@ -455,18 +455,18 @@
455 }455 }
456 456
457 .btn-danger-outline:hover {457 .btn-danger-outline:hover {
458- color: #ef4444;458+ color: var(--destructive);
459- border-color: #ef4444;459+ border-color: var(--destructive);
460 }460 }
461 461
462 .btn-danger {462 .btn-danger {
463- background: #b91c1c;463+ background: var(--destructive);
464- color: #fef2f2;464+ color: var(--destructive-foreground);
465- border-color: #991b1b;465+ border-color: color-mix(in srgb, var(--destructive) 80%, black 20%);
466 }466 }
467 467
468 .btn-danger:hover {468 .btn-danger:hover {
469- background: #dc2626;469+ background: color-mix(in srgb, var(--destructive) 85%, white 15%);
470 }470 }
471 471
472 .btn-cancel {472 .btn-cancel {
@@ -489,7 +489,7 @@
489 .confirm-text {489 .confirm-text {
490 flex: 1;490 flex: 1;
491 font-size: 0.72rem;491 font-size: 0.72rem;
492- color: #ef4444;492+ color: var(--destructive);
493 font-weight: 500;493 font-weight: 500;
494 }494 }
495 </style>495 </style>
M webui/src/pages/homepage/favorites/FolderList.svelte +2 -2
@@ -204,8 +204,8 @@
204204 }
205205
206206 .add-folder-btn.cancel:hover {
207- color: #ef4444;
208- border-color: #ef4444;
207+ color: var(--destructive);
208+ border-color: var(--destructive);
209209 }
210210
211211 .create-input-row {
@@ -204,8 +204,8 @@
204 }204 }
205 205
206 .add-folder-btn.cancel:hover {206 .add-folder-btn.cancel:hover {
207- color: #ef4444;207+ color: var(--destructive);
208- border-color: #ef4444;208+ border-color: var(--destructive);
209 }209 }
210 210
211 .create-input-row {211 .create-input-row {
M webui/src/pages/homepage/favorites/TagCloud.svelte +2 -2
@@ -214,8 +214,8 @@
214214 }
215215
216216 .add-tag-btn.cancel:hover {
217- color: #ef4444;
218- border-color: #ef4444;
217+ color: var(--destructive);
218+ border-color: var(--destructive);
219219 }
220220
221221 .create-input-row {
@@ -214,8 +214,8 @@
214 }214 }
215 215
216 .add-tag-btn.cancel:hover {216 .add-tag-btn.cancel:hover {
217- color: #ef4444;217+ color: var(--destructive);
218- border-color: #ef4444;218+ border-color: var(--destructive);
219 }219 }
220 220
221 .create-input-row {221 .create-input-row {
M webui/src/pages/homepage/history/HistoryCleanup.svelte +8 -8
@@ -191,7 +191,7 @@
191191 .modal-icon::before {
192192 content: '';
193193 font-size: 0.9rem;
194- color: #fbbf24;
194+ color: var(--warning);
195195 }
196196
197197 .modal-title {
@@ -258,7 +258,7 @@
258258 }
259259
260260 .cleanup-option.destructive .option-label {
261- color: #ef4444;
261+ color: var(--destructive);
262262 }
263263
264264 .option-main {
@@ -303,7 +303,7 @@
303303 .confirm-icon::before {
304304 content: '';
305305 font-size: 2rem;
306- color: #ef4444;
306+ color: var(--destructive);
307307 }
308308
309309 .confirm-message {
@@ -314,7 +314,7 @@
314314 }
315315
316316 .confirm-message strong {
317- color: #ef4444;
317+ color: var(--destructive);
318318 }
319319
320320 .confirm-actions {
@@ -359,13 +359,13 @@
359359 }
360360
361361 .confirm-btn.destructive {
362- background: #b91c1c;
363- color: #fef2f2;
364- border-color: #991b1b;
362+ background: var(--destructive);
363+ color: var(--destructive-foreground);
364+ border-color: color-mix(in srgb, var(--destructive) 80%, black 20%);
365365 }
366366
367367 .confirm-btn.destructive:hover {
368- background: #dc2626;
368+ background: color-mix(in srgb, var(--destructive) 85%, white 15%);
369369 }
370370
371371 .modal-footer {
@@ -191,7 +191,7 @@
191 .modal-icon::before {191 .modal-icon::before {
192 content: '';192 content: '';
193 font-size: 0.9rem;193 font-size: 0.9rem;
194- color: #fbbf24;194+ color: var(--warning);
195 }195 }
196 196
197 .modal-title {197 .modal-title {
@@ -258,7 +258,7 @@
258 }258 }
259 259
260 .cleanup-option.destructive .option-label {260 .cleanup-option.destructive .option-label {
261- color: #ef4444;261+ color: var(--destructive);
262 }262 }
263 263
264 .option-main {264 .option-main {
@@ -303,7 +303,7 @@
303 .confirm-icon::before {303 .confirm-icon::before {
304 content: '';304 content: '';
305 font-size: 2rem;305 font-size: 2rem;
306- color: #ef4444;306+ color: var(--destructive);
307 }307 }
308 308
309 .confirm-message {309 .confirm-message {
@@ -314,7 +314,7 @@
314 }314 }
315 315
316 .confirm-message strong {316 .confirm-message strong {
317- color: #ef4444;317+ color: var(--destructive);
318 }318 }
319 319
320 .confirm-actions {320 .confirm-actions {
@@ -359,13 +359,13 @@
359 }359 }
360 360
361 .confirm-btn.destructive {361 .confirm-btn.destructive {
362- background: #b91c1c;362+ background: var(--destructive);
363- color: #fef2f2;363+ color: var(--destructive-foreground);
364- border-color: #991b1b;364+ border-color: color-mix(in srgb, var(--destructive) 80%, black 20%);
365 }365 }
366 366
367 .confirm-btn.destructive:hover {367 .confirm-btn.destructive:hover {
368- background: #dc2626;368+ background: color-mix(in srgb, var(--destructive) 85%, white 15%);
369 }369 }
370 370
371 .modal-footer {371 .modal-footer {
M webui/src/pages/homepage/history/HistoryFilters.svelte +2 -2
@@ -144,8 +144,8 @@
144144 }
145145
146146 .cleanup-btn:hover {
147- color: #ef4444;
148- border-color: #ef4444;
147+ color: var(--destructive);
148+ border-color: var(--destructive);
149149 }
150150
151151 .cleanup-icon::before {
@@ -144,8 +144,8 @@
144 }144 }
145 145
146 .cleanup-btn:hover {146 .cleanup-btn:hover {
147- color: #ef4444;147+ color: var(--destructive);
148- border-color: #ef4444;148+ border-color: var(--destructive);
149 }149 }
150 150
151 .cleanup-icon::before {151 .cleanup-icon::before {
M webui/src/pages/homepage/history/HistoryItem.svelte +2 -2
@@ -270,7 +270,7 @@
270270 }
271271
272272 .delete-btn:hover {
273- color: #ef4444;
274- border-color: #ef4444;
273+ color: var(--destructive);
274+ border-color: var(--destructive);
275275 }
276276 </style>
@@ -270,7 +270,7 @@
270 }270 }
271 271
272 .delete-btn:hover {272 .delete-btn:hover {
273- color: #ef4444;273+ color: var(--destructive);
274- border-color: #ef4444;274+ border-color: var(--destructive);
275 }275 }
276 </style>276 </style>
M webui/src/pages/homepage/shell/HomepageShell.svelte +5 -5
@@ -533,7 +533,7 @@
533533
534534 .modal-icon {
535535 font-size: 1rem;
536- color: #fbbf24;
536+ color: var(--warning);
537537 }
538538
539539 .modal-title {
@@ -599,13 +599,13 @@
599599 }
600600
601601 .modal-btn-confirm {
602- color: #fef2f2;
603- background: #b91c1c;
604- border-color: #991b1b;
602+ color: var(--destructive-foreground);
603+ background: var(--destructive);
604+ border-color: color-mix(in srgb, var(--destructive) 80%, black 20%);
605605 }
606606
607607 .modal-btn-confirm:hover {
608- background: #dc2626;
608+ background: color-mix(in srgb, var(--destructive) 85%, white 15%);
609609 }
610610
611611 /* Responsive */
@@ -533,7 +533,7 @@
533 533
534 .modal-icon {534 .modal-icon {
535 font-size: 1rem;535 font-size: 1rem;
536- color: #fbbf24;536+ color: var(--warning);
537 }537 }
538 538
539 .modal-title {539 .modal-title {
@@ -599,13 +599,13 @@
599 }599 }
600 600
601 .modal-btn-confirm {601 .modal-btn-confirm {
602- color: #fef2f2;602+ color: var(--destructive-foreground);
603- background: #b91c1c;603+ background: var(--destructive);
604- border-color: #991b1b;604+ border-color: color-mix(in srgb, var(--destructive) 80%, black 20%);
605 }605 }
606 606
607 .modal-btn-confirm:hover {607 .modal-btn-confirm:hover {
608- background: #dc2626;608+ background: color-mix(in srgb, var(--destructive) 85%, white 15%);
609 }609 }
610 610
611 /* Responsive */611 /* Responsive */
M webui/src/pages/homepage/shell/StatusBar.svelte +13 -13
@@ -235,15 +235,15 @@
235235 }
236236
237237 .mode-chip[data-color="warning"] {
238- background: color-mix(in srgb, #fbbf24 15%, transparent);
239- border-color: color-mix(in srgb, #fbbf24 40%, var(--border) 60%);
240- color: #fbbf24;
238+ background: color-mix(in srgb, var(--warning) 15%, transparent);
239+ border-color: color-mix(in srgb, var(--warning) 40%, var(--border) 60%);
240+ color: var(--warning);
241241 }
242242
243243 .mode-chip[data-color="info"] {
244- background: color-mix(in srgb, #60a5fa 15%, transparent);
245- border-color: color-mix(in srgb, #60a5fa 40%, var(--border) 60%);
246- color: #60a5fa;
244+ background: color-mix(in srgb, var(--info) 15%, transparent);
245+ border-color: color-mix(in srgb, var(--info) 40%, var(--border) 60%);
246+ color: var(--info);
247247 }
248248
249249 /* Pending prefix indicator */
@@ -252,17 +252,17 @@
252252 }
253253
254254 .prefix-chip[data-color="warning"] {
255- background: color-mix(in srgb, #fbbf24 15%, transparent);
256- border-color: color-mix(in srgb, #fbbf24 50%, var(--border) 50%);
257- color: #fbbf24;
255+ background: color-mix(in srgb, var(--warning) 15%, transparent);
256+ border-color: color-mix(in srgb, var(--warning) 50%, var(--border) 50%);
257+ color: var(--warning);
258258 }
259259
260260 .prefix-key {
261261 font-size: 0.7rem;
262262 font-weight: 700;
263263 padding: 0.1rem 0.25rem;
264- background: color-mix(in srgb, #fbbf24 25%, transparent);
265- border: 1px solid color-mix(in srgb, #fbbf24 40%, var(--border) 60%);
264+ background: color-mix(in srgb, var(--warning) 25%, transparent);
265+ border: 1px solid color-mix(in srgb, var(--warning) 40%, var(--border) 60%);
266266 font-family: ui-monospace, 'Fira Code', 'Cascadia Code', Menlo, Monaco, Consolas, monospace;
267267 }
268268
@@ -273,8 +273,8 @@
273273 }
274274
275275 @keyframes pulse-border {
276- 0%, 100% { border-color: color-mix(in srgb, #fbbf24 50%, var(--border) 50%); }
277- 50% { border-color: #fbbf24; }
276+ 0%, 100% { border-color: color-mix(in srgb, var(--warning) 50%, var(--border) 50%); }
277+ 50% { border-color: var(--warning); }
278278 }
279279
280280 @keyframes blink {
@@ -235,15 +235,15 @@
235 }235 }
236 236
237 .mode-chip[data-color="warning"] {237 .mode-chip[data-color="warning"] {
238- background: color-mix(in srgb, #fbbf24 15%, transparent);238+ background: color-mix(in srgb, var(--warning) 15%, transparent);
239- border-color: color-mix(in srgb, #fbbf24 40%, var(--border) 60%);239+ border-color: color-mix(in srgb, var(--warning) 40%, var(--border) 60%);
240- color: #fbbf24;240+ color: var(--warning);
241 }241 }
242 242
243 .mode-chip[data-color="info"] {243 .mode-chip[data-color="info"] {
244- background: color-mix(in srgb, #60a5fa 15%, transparent);244+ background: color-mix(in srgb, var(--info) 15%, transparent);
245- border-color: color-mix(in srgb, #60a5fa 40%, var(--border) 60%);245+ border-color: color-mix(in srgb, var(--info) 40%, var(--border) 60%);
246- color: #60a5fa;246+ color: var(--info);
247 }247 }
248 248
249 /* Pending prefix indicator */249 /* Pending prefix indicator */
@@ -252,17 +252,17 @@
252 }252 }
253 253
254 .prefix-chip[data-color="warning"] {254 .prefix-chip[data-color="warning"] {
255- background: color-mix(in srgb, #fbbf24 15%, transparent);255+ background: color-mix(in srgb, var(--warning) 15%, transparent);
256- border-color: color-mix(in srgb, #fbbf24 50%, var(--border) 50%);256+ border-color: color-mix(in srgb, var(--warning) 50%, var(--border) 50%);
257- color: #fbbf24;257+ color: var(--warning);
258 }258 }
259 259
260 .prefix-key {260 .prefix-key {
261 font-size: 0.7rem;261 font-size: 0.7rem;
262 font-weight: 700;262 font-weight: 700;
263 padding: 0.1rem 0.25rem;263 padding: 0.1rem 0.25rem;
264- background: color-mix(in srgb, #fbbf24 25%, transparent);264+ background: color-mix(in srgb, var(--warning) 25%, transparent);
265- border: 1px solid color-mix(in srgb, #fbbf24 40%, var(--border) 60%);265+ border: 1px solid color-mix(in srgb, var(--warning) 40%, var(--border) 60%);
266 font-family: ui-monospace, 'Fira Code', 'Cascadia Code', Menlo, Monaco, Consolas, monospace;266 font-family: ui-monospace, 'Fira Code', 'Cascadia Code', Menlo, Monaco, Consolas, monospace;
267 }267 }
268 268
@@ -273,8 +273,8 @@
273 }273 }
274 274
275 @keyframes pulse-border {275 @keyframes pulse-border {
276- 0%, 100% { border-color: color-mix(in srgb, #fbbf24 50%, var(--border) 50%); }276+ 0%, 100% { border-color: color-mix(in srgb, var(--warning) 50%, var(--border) 50%); }
277- 50% { border-color: #fbbf24; }277+ 50% { border-color: var(--warning); }
278 }278 }
279 279
280 @keyframes blink {280 @keyframes blink {
M webui/src/styles/app.css +24 -2
@@ -54,7 +54,15 @@
5454 /* Borders & Inputs - solid colors for WebKitGTK compatibility */
5555 --border: oklch(0.88 0.008 260);
5656 --input: oklch(0.88 0.008 260);
57- --ring: oklch(0.55 0.20 145);
57+ --ring: var(--primary);
58+
59+ /* Semantic colors - for status indicators */
60+ --success: oklch(0.65 0.20 145);
61+ --success-foreground: oklch(0.985 0 0);
62+ --warning: oklch(0.75 0.18 85);
63+ --warning-foreground: oklch(0.145 0.02 260);
64+ --info: oklch(0.65 0.15 230);
65+ --info-foreground: oklch(0.985 0 0);
5866
5967 /* Chart colors */
6068 --chart-1: oklch(0.58 0.18 145);
@@ -113,7 +121,15 @@
113121 /* Borders & Inputs - solid colors for WebKitGTK compatibility */
114122 --border: oklch(0.35 0.01 260);
115123 --input: oklch(0.35 0.01 260);
116- --ring: oklch(0.75 0.19 145);
124+ --ring: var(--primary);
125+
126+ /* Semantic colors - brighter for dark bg */
127+ --success: oklch(0.72 0.18 145);
128+ --success-foreground: oklch(0.12 0.02 145);
129+ --warning: oklch(0.80 0.16 85);
130+ --warning-foreground: oklch(0.12 0.02 85);
131+ --info: oklch(0.70 0.14 230);
132+ --info-foreground: oklch(0.12 0.02 230);
117133
118134 /* Chart colors - brighter for dark bg */
119135 --chart-1: oklch(0.72 0.18 145);
@@ -159,6 +175,12 @@
159175 --color-accent-foreground: var(--accent-foreground);
160176 --color-destructive: var(--destructive);
161177 --color-destructive-foreground: var(--destructive-foreground);
178+ --color-success: var(--success);
179+ --color-success-foreground: var(--success-foreground);
180+ --color-warning: var(--warning);
181+ --color-warning-foreground: var(--warning-foreground);
182+ --color-info: var(--info);
183+ --color-info-foreground: var(--info-foreground);
162184 --color-border: var(--border);
163185 --color-input: var(--input);
164186 --color-ring: var(--ring);
@@ -54,7 +54,15 @@
54 /* Borders & Inputs - solid colors for WebKitGTK compatibility */54 /* Borders & Inputs - solid colors for WebKitGTK compatibility */
55 --border: oklch(0.88 0.008 260);55 --border: oklch(0.88 0.008 260);
56 --input: oklch(0.88 0.008 260);56 --input: oklch(0.88 0.008 260);
57- --ring: oklch(0.55 0.20 145);57+ --ring: var(--primary);
58+
59+ /* Semantic colors - for status indicators */
60+ --success: oklch(0.65 0.20 145);
61+ --success-foreground: oklch(0.985 0 0);
62+ --warning: oklch(0.75 0.18 85);
63+ --warning-foreground: oklch(0.145 0.02 260);
64+ --info: oklch(0.65 0.15 230);
65+ --info-foreground: oklch(0.985 0 0);
58 66
59 /* Chart colors */67 /* Chart colors */
60 --chart-1: oklch(0.58 0.18 145);68 --chart-1: oklch(0.58 0.18 145);
@@ -113,7 +121,15 @@
113 /* Borders & Inputs - solid colors for WebKitGTK compatibility */121 /* Borders & Inputs - solid colors for WebKitGTK compatibility */
114 --border: oklch(0.35 0.01 260);122 --border: oklch(0.35 0.01 260);
115 --input: oklch(0.35 0.01 260);123 --input: oklch(0.35 0.01 260);
116- --ring: oklch(0.75 0.19 145);124+ --ring: var(--primary);
125+
126+ /* Semantic colors - brighter for dark bg */
127+ --success: oklch(0.72 0.18 145);
128+ --success-foreground: oklch(0.12 0.02 145);
129+ --warning: oklch(0.80 0.16 85);
130+ --warning-foreground: oklch(0.12 0.02 85);
131+ --info: oklch(0.70 0.14 230);
132+ --info-foreground: oklch(0.12 0.02 230);
117 133
118 /* Chart colors - brighter for dark bg */134 /* Chart colors - brighter for dark bg */
119 --chart-1: oklch(0.72 0.18 145);135 --chart-1: oklch(0.72 0.18 145);
@@ -159,6 +175,12 @@
159 --color-accent-foreground: var(--accent-foreground);175 --color-accent-foreground: var(--accent-foreground);
160 --color-destructive: var(--destructive);176 --color-destructive: var(--destructive);
161 --color-destructive-foreground: var(--destructive-foreground);177 --color-destructive-foreground: var(--destructive-foreground);
178+ --color-success: var(--success);
179+ --color-success-foreground: var(--success-foreground);
180+ --color-warning: var(--warning);
181+ --color-warning-foreground: var(--warning-foreground);
182+ --color-info: var(--info);
183+ --color-info-foreground: var(--info-foreground);
162 --color-border: var(--border);184 --color-border: var(--border);
163 --color-input: var(--input);185 --color-input: var(--input);
164 --color-ring: var(--ring);186 --color-ring: var(--ring);
M webui/src/styles/design-tokens.css +24 -2
@@ -58,7 +58,15 @@
5858 /* Borders & Inputs */
5959 --border: oklch(0.88 0.008 260);
6060 --input: oklch(0.88 0.008 260);
61- --ring: oklch(0.55 0.20 145);
61+ --ring: var(--primary);
62+
63+ /* Semantic colors - for status indicators */
64+ --success: oklch(0.65 0.20 145);
65+ --success-foreground: oklch(0.985 0 0);
66+ --warning: oklch(0.75 0.18 85);
67+ --warning-foreground: oklch(0.145 0.02 260);
68+ --info: oklch(0.65 0.15 230);
69+ --info-foreground: oklch(0.985 0 0);
6270
6371 /* Chart colors for analytics */
6472 --chart-1: oklch(0.58 0.18 145); /* Green */
@@ -118,7 +126,15 @@
118126 /* Borders & Inputs - more visible */
119127 --border: oklch(0.35 0.01 260);
120128 --input: oklch(0.35 0.01 260);
121- --ring: oklch(0.75 0.19 145);
129+ --ring: var(--primary);
130+
131+ /* Semantic colors - brighter for dark bg */
132+ --success: oklch(0.72 0.18 145);
133+ --success-foreground: oklch(0.12 0.02 145);
134+ --warning: oklch(0.80 0.16 85);
135+ --warning-foreground: oklch(0.12 0.02 85);
136+ --info: oklch(0.70 0.14 230);
137+ --info-foreground: oklch(0.12 0.02 230);
122138
123139 /* Chart colors for analytics - brighter for dark bg */
124140 --chart-1: oklch(0.72 0.18 145); /* Green */
@@ -159,6 +175,12 @@
159175 --color-accent-foreground: var(--accent-foreground);
160176 --color-destructive: var(--destructive);
161177 --color-destructive-foreground: var(--destructive-foreground);
178+ --color-success: var(--success);
179+ --color-success-foreground: var(--success-foreground);
180+ --color-warning: var(--warning);
181+ --color-warning-foreground: var(--warning-foreground);
182+ --color-info: var(--info);
183+ --color-info-foreground: var(--info-foreground);
162184 --color-border: var(--border);
163185 --color-input: var(--input);
164186 --color-ring: var(--ring);
@@ -58,7 +58,15 @@
58 /* Borders & Inputs */58 /* Borders & Inputs */
59 --border: oklch(0.88 0.008 260);59 --border: oklch(0.88 0.008 260);
60 --input: oklch(0.88 0.008 260);60 --input: oklch(0.88 0.008 260);
61- --ring: oklch(0.55 0.20 145);61+ --ring: var(--primary);
62+
63+ /* Semantic colors - for status indicators */
64+ --success: oklch(0.65 0.20 145);
65+ --success-foreground: oklch(0.985 0 0);
66+ --warning: oklch(0.75 0.18 85);
67+ --warning-foreground: oklch(0.145 0.02 260);
68+ --info: oklch(0.65 0.15 230);
69+ --info-foreground: oklch(0.985 0 0);
62 70
63 /* Chart colors for analytics */71 /* Chart colors for analytics */
64 --chart-1: oklch(0.58 0.18 145); /* Green */72 --chart-1: oklch(0.58 0.18 145); /* Green */
@@ -118,7 +126,15 @@
118 /* Borders & Inputs - more visible */126 /* Borders & Inputs - more visible */
119 --border: oklch(0.35 0.01 260);127 --border: oklch(0.35 0.01 260);
120 --input: oklch(0.35 0.01 260);128 --input: oklch(0.35 0.01 260);
121- --ring: oklch(0.75 0.19 145);129+ --ring: var(--primary);
130+
131+ /* Semantic colors - brighter for dark bg */
132+ --success: oklch(0.72 0.18 145);
133+ --success-foreground: oklch(0.12 0.02 145);
134+ --warning: oklch(0.80 0.16 85);
135+ --warning-foreground: oklch(0.12 0.02 85);
136+ --info: oklch(0.70 0.14 230);
137+ --info-foreground: oklch(0.12 0.02 230);
122 138
123 /* Chart colors for analytics - brighter for dark bg */139 /* Chart colors for analytics - brighter for dark bg */
124 --chart-1: oklch(0.72 0.18 145); /* Green */140 --chart-1: oklch(0.72 0.18 145); /* Green */
@@ -159,6 +175,12 @@
159 --color-accent-foreground: var(--accent-foreground);175 --color-accent-foreground: var(--accent-foreground);
160 --color-destructive: var(--destructive);176 --color-destructive: var(--destructive);
161 --color-destructive-foreground: var(--destructive-foreground);177 --color-destructive-foreground: var(--destructive-foreground);
178+ --color-success: var(--success);
179+ --color-success-foreground: var(--success-foreground);
180+ --color-warning: var(--warning);
181+ --color-warning-foreground: var(--warning-foreground);
182+ --color-info: var(--info);
183+ --color-info-foreground: var(--info-foreground);
162 --color-border: var(--border);184 --color-border: var(--border);
163 --color-input: var(--input);185 --color-input: var(--input);
164 --color-ring: var(--ring);186 --color-ring: var(--ring);
M webui/src/styles/tailwind.css +29 -5
@@ -24,6 +24,14 @@
2424 --dynamic-secondary: var(--secondary);
2525 --dynamic-destructive: var(--destructive);
2626 --dynamic-ring: var(--ring);
27+
28+ /* Semantic status colors - backend can override */
29+ --dynamic-success: var(--color-browser-success, var(--success));
30+ --dynamic-success-foreground: var(--success-foreground);
31+ --dynamic-warning: var(--color-browser-warning, var(--warning));
32+ --dynamic-warning-foreground: var(--warning-foreground);
33+ --dynamic-info: var(--color-browser-info, var(--info));
34+ --dynamic-info-foreground: var(--info-foreground);
2735 }
2836
2937 /* Dark mode border override */
@@ -53,12 +61,28 @@
5361 border: 1px solid var(--dynamic-border);
5462 }
5563
56-.toast-success,
57-.toast-error,
64+.toast-success {
65+ background-color: var(--dynamic-success);
66+ color: var(--dynamic-success-foreground);
67+ border: 1px solid color-mix(in srgb, var(--dynamic-success) 50%, var(--dynamic-border) 50%);
68+}
69+
70+.toast-error {
71+ background-color: var(--dynamic-destructive);
72+ color: var(--destructive-foreground);
73+ border: 1px solid color-mix(in srgb, var(--dynamic-destructive) 50%, var(--dynamic-border) 50%);
74+}
75+
5876 .toast-info {
59- background-color: var(--toast-bg, var(--dynamic-surface));
60- color: var(--toast-text, var(--dynamic-text));
61- border: 1px solid var(--toast-border, var(--dynamic-border));
77+ background-color: var(--dynamic-info);
78+ color: var(--dynamic-info-foreground);
79+ border: 1px solid color-mix(in srgb, var(--dynamic-info) 50%, var(--dynamic-border) 50%);
80+}
81+
82+.toast-warning {
83+ background-color: var(--dynamic-warning);
84+ color: var(--dynamic-warning-foreground);
85+ border: 1px solid color-mix(in srgb, var(--dynamic-warning) 50%, var(--dynamic-border) 50%);
6286 }
6387
6488 /* Omnibox/Find bar styles */
@@ -24,6 +24,14 @@
24 --dynamic-secondary: var(--secondary);24 --dynamic-secondary: var(--secondary);
25 --dynamic-destructive: var(--destructive);25 --dynamic-destructive: var(--destructive);
26 --dynamic-ring: var(--ring);26 --dynamic-ring: var(--ring);
27+
28+ /* Semantic status colors - backend can override */
29+ --dynamic-success: var(--color-browser-success, var(--success));
30+ --dynamic-success-foreground: var(--success-foreground);
31+ --dynamic-warning: var(--color-browser-warning, var(--warning));
32+ --dynamic-warning-foreground: var(--warning-foreground);
33+ --dynamic-info: var(--color-browser-info, var(--info));
34+ --dynamic-info-foreground: var(--info-foreground);
27 }35 }
28 36
29 /* Dark mode border override */37 /* Dark mode border override */
@@ -53,12 +61,28 @@
53 border: 1px solid var(--dynamic-border);61 border: 1px solid var(--dynamic-border);
54 }62 }
55 63
56-.toast-success,64+.toast-success {
57-.toast-error,65+ background-color: var(--dynamic-success);
66+ color: var(--dynamic-success-foreground);
67+ border: 1px solid color-mix(in srgb, var(--dynamic-success) 50%, var(--dynamic-border) 50%);
68+}
69+
70+.toast-error {
71+ background-color: var(--dynamic-destructive);
72+ color: var(--destructive-foreground);
73+ border: 1px solid color-mix(in srgb, var(--dynamic-destructive) 50%, var(--dynamic-border) 50%);
74+}
75+
58 .toast-info {76 .toast-info {
59- background-color: var(--toast-bg, var(--dynamic-surface));77+ background-color: var(--dynamic-info);
60- color: var(--toast-text, var(--dynamic-text));78+ color: var(--dynamic-info-foreground);
61- border: 1px solid var(--toast-border, var(--dynamic-border));79+ border: 1px solid color-mix(in srgb, var(--dynamic-info) 50%, var(--dynamic-border) 50%);
80+}
81+
82+.toast-warning {
83+ background-color: var(--dynamic-warning);
84+ color: var(--dynamic-warning-foreground);
85+ border: 1px solid color-mix(in srgb, var(--dynamic-warning) 50%, var(--dynamic-border) 50%);
62 }86 }
63 87
64 /* Omnibox/Find bar styles */88 /* Omnibox/Find bar styles */

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close