返回 CodeWhale
themes.rs
根目录 / crates / palette / src / themes.rs
1 //! Named theme presets and theme resolution.
2
3 use ratatui::style::Color;
4
5 use super::detect::PaletteMode;
6 use super::ids::{ThemeId, parse_hex_rgb};
7 use super::rgb::*;
8 use super::tokens::*;
9
10 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
11 pub struct UiTheme {
12 pub name: &'static str,
13 pub mode: PaletteMode,
14 // Surface hierarchy
15 pub surface_bg: Color,
16 pub panel_bg: Color,
17 pub elevated_bg: Color,
18 pub composer_bg: Color,
19 pub selection_bg: Color,
20 pub header_bg: Color,
21 pub footer_bg: Color,
22 /// Text hierarchy
23 pub text_dim: Color,
24 pub text_hint: Color,
25 pub text_muted: Color,
26 pub text_body: Color,
27 pub text_soft: Color,
28 pub border: Color,
29 // Accent roles
30 pub accent_primary: Color,
31 pub accent_secondary: Color,
32 pub accent_action: Color,
33 // Error / destructive
34 pub error_fg: Color,
35 pub error_hover: Color,
36 pub error_surface: Color,
37 pub error_border: Color,
38 pub error_text: Color,
39 // Status roles (warning / success / info)
40 pub warning: Color,
41 pub success: Color,
42 pub info: Color,
43 // Mode badge colors (act/plan/operate; mode_yolo kept for legacy theme data).
44 // These must be distinct from one another and from the general accent,
45 // human, warning, danger, and success lanes: the terminal backend receives
46 // only the final `Color` and needs that identity to perform semantic ANSI
47 // adaptation.
48 pub mode_agent: Color,
49 pub mode_yolo: Color,
50 pub mode_plan: Color,
51 pub mode_operate: Color,
52 // Permission posture colors (Ask / Auto-Review / Full Access). These are
53 // explicit theme slots because the warm permission ramp is independent
54 // from the cool mode ramp.
55 pub permission_ask: Color,
56 pub permission_auto_review: Color,
57 pub permission_full_access: Color,
58 // Footer statusline colors
59 pub status_ready: Color,
60 pub status_working: Color,
61 pub status_warning: Color,
62 // Diff colors
63 pub diff_added_fg: Color,
64 pub diff_deleted_fg: Color,
65 pub diff_added_bg: Color,
66 pub diff_deleted_bg: Color,
67 // Tool cell colors
68 pub tool_running: Color,
69 pub tool_success: Color,
70 pub tool_failed: Color,
71 }
72
73 pub const UI_THEME: UiTheme = UiTheme {
74 name: "whale",
75 mode: PaletteMode::Dark,
76 surface_bg: WHALE_BG,
77 panel_bg: WHALE_PANEL,
78 elevated_bg: SURFACE_ELEVATED,
79 composer_bg: WHALE_COMPOSER,
80 selection_bg: SELECTION_BG,
81 header_bg: WHALE_CHROME,
82 footer_bg: WHALE_CHROME,
83 text_dim: TEXT_DIM,
84 text_hint: TEXT_HINT,
85 text_muted: TEXT_MUTED,
86 text_body: TEXT_BODY,
87 text_soft: TEXT_SOFT,
88 border: BORDER_COLOR,
89 accent_primary: WHALE_ACTION,
90 accent_secondary: WHALE_LIVE,
91 accent_action: WHALE_HUMAN,
92 error_fg: WHALE_ERROR,
93 error_hover: Color::Rgb(
94 WHALE_ERROR_HOVER_RGB.0,
95 WHALE_ERROR_HOVER_RGB.1,
96 WHALE_ERROR_HOVER_RGB.2,
97 ),
98 error_surface: Color::Rgb(
99 WHALE_ERROR_SURFACE_RGB.0,
100 WHALE_ERROR_SURFACE_RGB.1,
101 WHALE_ERROR_SURFACE_RGB.2,
102 ),
103 error_border: Color::Rgb(
104 WHALE_ERROR_BORDER_RGB.0,
105 WHALE_ERROR_BORDER_RGB.1,
106 WHALE_ERROR_BORDER_RGB.2,
107 ),
108 error_text: Color::Rgb(
109 WHALE_ERROR_TEXT_RGB.0,
110 WHALE_ERROR_TEXT_RGB.1,
111 WHALE_ERROR_TEXT_RGB.2,
112 ),
113 warning: STATUS_WARNING,
114 success: Color::Rgb(
115 WHALE_SUCCESS_RGB.0,
116 WHALE_SUCCESS_RGB.1,
117 WHALE_SUCCESS_RGB.2,
118 ),
119 info: WHALE_ACTION,
120 mode_agent: MODE_AGENT,
121 mode_yolo: MODE_YOLO,
122 mode_plan: MODE_PLAN,
123 mode_operate: MODE_OPERATE,
124 permission_ask: TEXT_REASONING,
125 permission_auto_review: WHALE_HUMAN,
126 permission_full_access: STATUS_WARNING,
127 status_ready: TEXT_MUTED,
128 status_working: WHALE_LIVE,
129 status_warning: STATUS_WARNING,
130 diff_added_fg: DIFF_ADDED,
131 diff_deleted_fg: WHALE_ERROR,
132 diff_added_bg: DIFF_ADDED_BG,
133 diff_deleted_bg: DIFF_DELETED_BG,
134 tool_running: WHALE_LIVE,
135 tool_success: Color::Rgb(
136 WHALE_WORKING_GREEN_RGB.0,
137 WHALE_WORKING_GREEN_RGB.1,
138 WHALE_WORKING_GREEN_RGB.2,
139 ),
140 tool_failed: WHALE_ERROR,
141 }
142 .with_terminal_native_shell();
143
144 /// The underwater theme: the one theme that owns a painted ground. The ocean
145 /// ramp (`crate::tui::ocean::OceanRamp`) paints the water column over these
146 /// surfaces; the values below are what shows through before the first paint
147 /// and on rows the ramp does not reach. Ink/accent roles match the dark whale
148 /// pair so text contrast is identical.
149 pub const UNDERWATER_UI_THEME: UiTheme = UiTheme {
150 name: "underwater",
151 mode: PaletteMode::Dark,
152 surface_bg: Color::Rgb(0x0a, 0x1e, 0x33),
153 panel_bg: Color::Rgb(0x0d, 0x22, 0x3a),
154 elevated_bg: Color::Rgb(0x10, 0x2a, 0x45),
155 composer_bg: Color::Rgb(0x0d, 0x22, 0x3a),
156 selection_bg: SELECTION_BG,
157 header_bg: Color::Rgb(0x06, 0x13, 0x20),
158 footer_bg: Color::Rgb(0x06, 0x13, 0x20),
159 text_dim: TEXT_DIM,
160 text_hint: TEXT_HINT,
161 text_muted: TEXT_MUTED,
162 text_body: TEXT_BODY,
163 text_soft: TEXT_SOFT,
164 border: BORDER_COLOR,
165 accent_primary: WHALE_ACTION,
166 accent_secondary: WHALE_LIVE,
167 accent_action: WHALE_HUMAN,
168 error_fg: WHALE_ERROR,
169 error_hover: Color::Rgb(
170 WHALE_ERROR_HOVER_RGB.0,
171 WHALE_ERROR_HOVER_RGB.1,
172 WHALE_ERROR_HOVER_RGB.2,
173 ),
174 error_surface: Color::Rgb(
175 WHALE_ERROR_SURFACE_RGB.0,
176 WHALE_ERROR_SURFACE_RGB.1,
177 WHALE_ERROR_SURFACE_RGB.2,
178 ),
179 error_border: Color::Rgb(
180 WHALE_ERROR_BORDER_RGB.0,
181 WHALE_ERROR_BORDER_RGB.1,
182 WHALE_ERROR_BORDER_RGB.2,
183 ),
184 error_text: Color::Rgb(
185 WHALE_ERROR_TEXT_RGB.0,
186 WHALE_ERROR_TEXT_RGB.1,
187 WHALE_ERROR_TEXT_RGB.2,
188 ),
189 warning: STATUS_WARNING,
190 success: Color::Rgb(
191 WHALE_SUCCESS_RGB.0,
192 WHALE_SUCCESS_RGB.1,
193 WHALE_SUCCESS_RGB.2,
194 ),
195 info: WHALE_ACTION,
196 mode_agent: MODE_AGENT,
197 mode_yolo: MODE_YOLO,
198 mode_plan: MODE_PLAN,
199 mode_operate: MODE_OPERATE,
200 permission_ask: TEXT_REASONING,
201 permission_auto_review: WHALE_HUMAN,
202 permission_full_access: STATUS_WARNING,
203 status_ready: TEXT_MUTED,
204 status_working: WHALE_LIVE,
205 status_warning: STATUS_WARNING,
206 diff_added_fg: DIFF_ADDED,
207 diff_deleted_fg: WHALE_ERROR,
208 diff_added_bg: DIFF_ADDED_BG,
209 diff_deleted_bg: DIFF_DELETED_BG,
210 tool_running: WHALE_LIVE,
211 tool_success: Color::Rgb(
212 WHALE_WORKING_GREEN_RGB.0,
213 WHALE_WORKING_GREEN_RGB.1,
214 WHALE_WORKING_GREEN_RGB.2,
215 ),
216 tool_failed: WHALE_ERROR,
217 };
218
219 /// The underwater-retro theme: the flat phosphor-teal look of the legacy
220 /// deepsea era as a selectable theme.
221 /// Same ink and accent roles as underwater so text contrast is identical;
222 /// the ground is flat near-black blue with no ombre paint, so the ocean ramp
223 /// leaves it alone.
224 pub const UNDERWATER_RETRO_UI_THEME: UiTheme = UiTheme {
225 name: "underwater-retro",
226 mode: PaletteMode::Dark,
227 surface_bg: Color::Rgb(0x05, 0x10, 0x19),
228 panel_bg: Color::Rgb(0x07, 0x15, 0x22),
229 elevated_bg: Color::Rgb(0x0a, 0x1c, 0x2c),
230 composer_bg: Color::Rgb(0x07, 0x15, 0x22),
231 selection_bg: SELECTION_BG,
232 header_bg: Color::Rgb(0x03, 0x0c, 0x13),
233 footer_bg: Color::Rgb(0x03, 0x0c, 0x13),
234 text_dim: TEXT_DIM,
235 text_hint: TEXT_HINT,
236 text_muted: TEXT_MUTED,
237 text_body: TEXT_BODY,
238 text_soft: TEXT_SOFT,
239 border: Color::Rgb(0x14, 0x50, 0x5e),
240 accent_primary: WHALE_ACTION,
241 accent_secondary: WHALE_LIVE,
242 accent_action: WHALE_HUMAN,
243 error_fg: WHALE_ERROR,
244 error_hover: Color::Rgb(
245 WHALE_ERROR_HOVER_RGB.0,
246 WHALE_ERROR_HOVER_RGB.1,
247 WHALE_ERROR_HOVER_RGB.2,
248 ),
249 error_surface: Color::Rgb(
250 WHALE_ERROR_SURFACE_RGB.0,
251 WHALE_ERROR_SURFACE_RGB.1,
252 WHALE_ERROR_SURFACE_RGB.2,
253 ),
254 error_border: Color::Rgb(
255 WHALE_ERROR_BORDER_RGB.0,
256 WHALE_ERROR_BORDER_RGB.1,
257 WHALE_ERROR_BORDER_RGB.2,
258 ),
259 error_text: Color::Rgb(
260 WHALE_ERROR_TEXT_RGB.0,
261 WHALE_ERROR_TEXT_RGB.1,
262 WHALE_ERROR_TEXT_RGB.2,
263 ),
264 warning: STATUS_WARNING,
265 success: Color::Rgb(
266 WHALE_SUCCESS_RGB.0,
267 WHALE_SUCCESS_RGB.1,
268 WHALE_SUCCESS_RGB.2,
269 ),
270 info: WHALE_ACTION,
271 mode_agent: MODE_AGENT,
272 mode_yolo: MODE_YOLO,
273 mode_plan: MODE_PLAN,
274 mode_operate: MODE_OPERATE,
275 permission_ask: TEXT_REASONING,
276 permission_auto_review: WHALE_HUMAN,
277 permission_full_access: STATUS_WARNING,
278 status_ready: TEXT_MUTED,
279 status_working: WHALE_LIVE,
280 status_warning: STATUS_WARNING,
281 diff_added_fg: DIFF_ADDED,
282 diff_deleted_fg: WHALE_ERROR,
283 diff_added_bg: DIFF_ADDED_BG,
284 diff_deleted_bg: DIFF_DELETED_BG,
285 tool_running: WHALE_LIVE,
286 tool_success: Color::Rgb(
287 WHALE_WORKING_GREEN_RGB.0,
288 WHALE_WORKING_GREEN_RGB.1,
289 WHALE_WORKING_GREEN_RGB.2,
290 ),
291 tool_failed: WHALE_ERROR,
292 };
293
294 pub const LIGHT_UI_THEME: UiTheme = UiTheme {
295 name: "whale-light",
296 mode: PaletteMode::Light,
297 surface_bg: LIGHT_SURFACE,
298 panel_bg: LIGHT_PANEL,
299 elevated_bg: LIGHT_ELEVATED,
300 composer_bg: LIGHT_PANEL,
301 selection_bg: LIGHT_SELECTION_BG,
302 header_bg: LIGHT_SURFACE,
303 footer_bg: LIGHT_SURFACE,
304 text_dim: LIGHT_TEXT_HINT,
305 text_hint: LIGHT_TEXT_HINT,
306 text_muted: LIGHT_TEXT_MUTED,
307 text_body: LIGHT_TEXT_BODY,
308 text_soft: LIGHT_TEXT_SOFT,
309 border: LIGHT_BORDER,
310 accent_primary: LIGHT_ACTION,
311 accent_secondary: LIGHT_LIVE,
312 accent_action: LIGHT_HUMAN,
313 error_fg: LIGHT_DANGER,
314 error_hover: Color::Rgb(201, 71, 120), // #C94778
315 error_surface: LIGHT_ERROR,
316 error_border: LIGHT_DANGER,
317 error_text: Color::Rgb(109, 22, 56), // #6D1638
318 warning: LIGHT_WARNING,
319 success: LIGHT_SUCCESS_FG,
320 info: LIGHT_ACTION,
321 mode_agent: LIGHT_MODE_AGENT,
322 mode_yolo: LIGHT_MODE_YOLO,
323 mode_plan: LIGHT_MODE_PLAN,
324 mode_operate: LIGHT_OPERATE,
325 permission_ask: Color::Rgb(146, 64, 14),
326 permission_auto_review: LIGHT_HUMAN,
327 permission_full_access: LIGHT_WARNING,
328 status_ready: LIGHT_TEXT_MUTED,
329 status_working: LIGHT_LIVE,
330 status_warning: LIGHT_WARNING,
331 diff_added_fg: Color::Rgb(22, 101, 52), // green
332 diff_deleted_fg: LIGHT_DANGER,
333 diff_added_bg: Color::Rgb(223, 247, 231), // light green
334 diff_deleted_bg: LIGHT_ERROR,
335 tool_running: LIGHT_LIVE,
336 tool_success: LIGHT_SUCCESS_FG,
337 tool_failed: LIGHT_DANGER,
338 }
339 .with_terminal_native_shell();
340
341 /// Shoreline — the warm charcoal alternative and the terminal's half of the
342 /// product-client palette. Fresh 0.10.0 terminal installs use Underwater.
343 ///
344 /// The GPUI desktop window and this terminal now cite the same slots, so the
345 /// two clients read as one product: a warm charcoal ground, one raised plate
346 /// for cards and the composer, one restrained blue for action and selection,
347 /// and the whale's ivory ink. It does **not** call
348 /// [`UiTheme::with_terminal_native_shell`] — the field is painted, the way the
349 /// desktop window paints it — and `adapt::theme_remap_active` remaps every
350 /// direct terminal constant onto these slots, so nothing navy-tuned leaks
351 /// through.
352 ///
353 /// The terminal's `underwater` default keeps the painted
354 /// water column, the ambient life and the ombre.
355 ///
356 /// Every pair `contrast::theme_contrast_violations` audits clears its floor.
357 pub const SHORELINE_UI_THEME: UiTheme = UiTheme {
358 name: "shoreline",
359 mode: PaletteMode::Dark,
360 surface_bg: Color::Rgb(
361 SHORELINE_SURFACE_RGB.0,
362 SHORELINE_SURFACE_RGB.1,
363 SHORELINE_SURFACE_RGB.2,
364 ),
365 panel_bg: Color::Rgb(
366 SHORELINE_PANEL_RGB.0,
367 SHORELINE_PANEL_RGB.1,
368 SHORELINE_PANEL_RGB.2,
369 ),
370 elevated_bg: Color::Rgb(
371 SHORELINE_ELEVATED_RGB.0,
372 SHORELINE_ELEVATED_RGB.1,
373 SHORELINE_ELEVATED_RGB.2,
374 ),
375 composer_bg: Color::Rgb(
376 SHORELINE_COMPOSER_RGB.0,
377 SHORELINE_COMPOSER_RGB.1,
378 SHORELINE_COMPOSER_RGB.2,
379 ),
380 selection_bg: Color::Rgb(
381 SHORELINE_SELECTION_RGB.0,
382 SHORELINE_SELECTION_RGB.1,
383 SHORELINE_SELECTION_RGB.2,
384 ),
385 header_bg: Color::Rgb(
386 SHORELINE_CHROME_RGB.0,
387 SHORELINE_CHROME_RGB.1,
388 SHORELINE_CHROME_RGB.2,
389 ),
390 footer_bg: Color::Rgb(
391 SHORELINE_CHROME_RGB.0,
392 SHORELINE_CHROME_RGB.1,
393 SHORELINE_CHROME_RGB.2,
394 ),
395 text_dim: Color::Rgb(
396 SHORELINE_TEXT_DIM_RGB.0,
397 SHORELINE_TEXT_DIM_RGB.1,
398 SHORELINE_TEXT_DIM_RGB.2,
399 ),
400 text_hint: Color::Rgb(
401 SHORELINE_TEXT_HINT_RGB.0,
402 SHORELINE_TEXT_HINT_RGB.1,
403 SHORELINE_TEXT_HINT_RGB.2,
404 ),
405 text_muted: Color::Rgb(
406 SHORELINE_TEXT_MUTED_RGB.0,
407 SHORELINE_TEXT_MUTED_RGB.1,
408 SHORELINE_TEXT_MUTED_RGB.2,
409 ),
410 text_body: Color::Rgb(
411 SHORELINE_TEXT_BODY_RGB.0,
412 SHORELINE_TEXT_BODY_RGB.1,
413 SHORELINE_TEXT_BODY_RGB.2,
414 ),
415 text_soft: Color::Rgb(
416 SHORELINE_TEXT_SOFT_RGB.0,
417 SHORELINE_TEXT_SOFT_RGB.1,
418 SHORELINE_TEXT_SOFT_RGB.2,
419 ),
420 border: Color::Rgb(
421 SHORELINE_BORDER_RGB.0,
422 SHORELINE_BORDER_RGB.1,
423 SHORELINE_BORDER_RGB.2,
424 ),
425 accent_primary: Color::Rgb(
426 SHORELINE_ACTION_RGB.0,
427 SHORELINE_ACTION_RGB.1,
428 SHORELINE_ACTION_RGB.2,
429 ),
430 accent_secondary: Color::Rgb(
431 SHORELINE_LIVE_RGB.0,
432 SHORELINE_LIVE_RGB.1,
433 SHORELINE_LIVE_RGB.2,
434 ),
435 accent_action: Color::Rgb(
436 SHORELINE_HUMAN_RGB.0,
437 SHORELINE_HUMAN_RGB.1,
438 SHORELINE_HUMAN_RGB.2,
439 ),
440 error_fg: Color::Rgb(
441 SHORELINE_ERROR_RGB.0,
442 SHORELINE_ERROR_RGB.1,
443 SHORELINE_ERROR_RGB.2,
444 ),
445 error_hover: Color::Rgb(
446 SHORELINE_ERROR_HOVER_RGB.0,
447 SHORELINE_ERROR_HOVER_RGB.1,
448 SHORELINE_ERROR_HOVER_RGB.2,
449 ),
450 error_surface: Color::Rgb(
451 SHORELINE_ERROR_SURFACE_RGB.0,
452 SHORELINE_ERROR_SURFACE_RGB.1,
453 SHORELINE_ERROR_SURFACE_RGB.2,
454 ),
455 error_border: Color::Rgb(
456 SHORELINE_ERROR_RGB.0,
457 SHORELINE_ERROR_RGB.1,
458 SHORELINE_ERROR_RGB.2,
459 ),
460 error_text: Color::Rgb(
461 SHORELINE_ERROR_TEXT_RGB.0,
462 SHORELINE_ERROR_TEXT_RGB.1,
463 SHORELINE_ERROR_TEXT_RGB.2,
464 ),
465 warning: Color::Rgb(
466 SHORELINE_WARNING_RGB.0,
467 SHORELINE_WARNING_RGB.1,
468 SHORELINE_WARNING_RGB.2,
469 ),
470 success: Color::Rgb(
471 SHORELINE_SUCCESS_RGB.0,
472 SHORELINE_SUCCESS_RGB.1,
473 SHORELINE_SUCCESS_RGB.2,
474 ),
475 info: Color::Rgb(
476 SHORELINE_ACTION_RGB.0,
477 SHORELINE_ACTION_RGB.1,
478 SHORELINE_ACTION_RGB.2,
479 ),
480 mode_agent: Color::Rgb(
481 SHORELINE_MODE_AGENT_RGB.0,
482 SHORELINE_MODE_AGENT_RGB.1,
483 SHORELINE_MODE_AGENT_RGB.2,
484 ),
485 mode_yolo: Color::Rgb(
486 SHORELINE_MODE_YOLO_RGB.0,
487 SHORELINE_MODE_YOLO_RGB.1,
488 SHORELINE_MODE_YOLO_RGB.2,
489 ),
490 mode_plan: Color::Rgb(
491 SHORELINE_MODE_PLAN_RGB.0,
492 SHORELINE_MODE_PLAN_RGB.1,
493 SHORELINE_MODE_PLAN_RGB.2,
494 ),
495 mode_operate: Color::Rgb(
496 SHORELINE_MODE_OPERATE_RGB.0,
497 SHORELINE_MODE_OPERATE_RGB.1,
498 SHORELINE_MODE_OPERATE_RGB.2,
499 ),
500 permission_ask: Color::Rgb(
501 SHORELINE_TEXT_MUTED_RGB.0,
502 SHORELINE_TEXT_MUTED_RGB.1,
503 SHORELINE_TEXT_MUTED_RGB.2,
504 ),
505 permission_auto_review: Color::Rgb(
506 SHORELINE_HUMAN_RGB.0,
507 SHORELINE_HUMAN_RGB.1,
508 SHORELINE_HUMAN_RGB.2,
509 ),
510 permission_full_access: Color::Rgb(
511 SHORELINE_WARNING_RGB.0,
512 SHORELINE_WARNING_RGB.1,
513 SHORELINE_WARNING_RGB.2,
514 ),
515 status_ready: Color::Rgb(
516 SHORELINE_SUCCESS_RGB.0,
517 SHORELINE_SUCCESS_RGB.1,
518 SHORELINE_SUCCESS_RGB.2,
519 ),
520 status_working: Color::Rgb(
521 SHORELINE_STATUS_WORKING_RGB.0,
522 SHORELINE_STATUS_WORKING_RGB.1,
523 SHORELINE_STATUS_WORKING_RGB.2,
524 ),
525 status_warning: Color::Rgb(
526 SHORELINE_WARNING_RGB.0,
527 SHORELINE_WARNING_RGB.1,
528 SHORELINE_WARNING_RGB.2,
529 ),
530 diff_added_fg: Color::Rgb(
531 SHORELINE_DIFF_ADDED_FG_RGB.0,
532 SHORELINE_DIFF_ADDED_FG_RGB.1,
533 SHORELINE_DIFF_ADDED_FG_RGB.2,
534 ),
535 diff_deleted_fg: Color::Rgb(
536 SHORELINE_DIFF_DELETED_FG_RGB.0,
537 SHORELINE_DIFF_DELETED_FG_RGB.1,
538 SHORELINE_DIFF_DELETED_FG_RGB.2,
539 ),
540 diff_added_bg: Color::Rgb(
541 SHORELINE_DIFF_ADDED_BG_RGB.0,
542 SHORELINE_DIFF_ADDED_BG_RGB.1,
543 SHORELINE_DIFF_ADDED_BG_RGB.2,
544 ),
545 diff_deleted_bg: Color::Rgb(
546 SHORELINE_DIFF_DELETED_BG_RGB.0,
547 SHORELINE_DIFF_DELETED_BG_RGB.1,
548 SHORELINE_DIFF_DELETED_BG_RGB.2,
549 ),
550 tool_running: Color::Rgb(
551 SHORELINE_LIVE_RGB.0,
552 SHORELINE_LIVE_RGB.1,
553 SHORELINE_LIVE_RGB.2,
554 ),
555 tool_success: Color::Rgb(
556 SHORELINE_SUCCESS_RGB.0,
557 SHORELINE_SUCCESS_RGB.1,
558 SHORELINE_SUCCESS_RGB.2,
559 ),
560 tool_failed: Color::Rgb(
561 SHORELINE_ERROR_RGB.0,
562 SHORELINE_ERROR_RGB.1,
563 SHORELINE_ERROR_RGB.2,
564 ),
565 };
566
567 /// Shoreline Light — Shoreline on warm paper, for light terminals.
568 pub const SHORELINE_LIGHT_UI_THEME: UiTheme = UiTheme {
569 name: "shoreline-light",
570 mode: PaletteMode::Light,
571 surface_bg: Color::Rgb(
572 SHORELINE_LIGHT_SURFACE_RGB.0,
573 SHORELINE_LIGHT_SURFACE_RGB.1,
574 SHORELINE_LIGHT_SURFACE_RGB.2,
575 ),
576 panel_bg: Color::Rgb(
577 SHORELINE_LIGHT_PANEL_RGB.0,
578 SHORELINE_LIGHT_PANEL_RGB.1,
579 SHORELINE_LIGHT_PANEL_RGB.2,
580 ),
581 elevated_bg: Color::Rgb(
582 SHORELINE_LIGHT_ELEVATED_RGB.0,
583 SHORELINE_LIGHT_ELEVATED_RGB.1,
584 SHORELINE_LIGHT_ELEVATED_RGB.2,
585 ),
586 composer_bg: Color::Rgb(
587 SHORELINE_LIGHT_COMPOSER_RGB.0,
588 SHORELINE_LIGHT_COMPOSER_RGB.1,
589 SHORELINE_LIGHT_COMPOSER_RGB.2,
590 ),
591 selection_bg: Color::Rgb(
592 SHORELINE_LIGHT_SELECTION_RGB.0,
593 SHORELINE_LIGHT_SELECTION_RGB.1,
594 SHORELINE_LIGHT_SELECTION_RGB.2,
595 ),
596 header_bg: Color::Rgb(
597 SHORELINE_LIGHT_CHROME_RGB.0,
598 SHORELINE_LIGHT_CHROME_RGB.1,
599 SHORELINE_LIGHT_CHROME_RGB.2,
600 ),
601 footer_bg: Color::Rgb(
602 SHORELINE_LIGHT_CHROME_RGB.0,
603 SHORELINE_LIGHT_CHROME_RGB.1,
604 SHORELINE_LIGHT_CHROME_RGB.2,
605 ),
606 text_dim: Color::Rgb(
607 SHORELINE_LIGHT_TEXT_DIM_RGB.0,
608 SHORELINE_LIGHT_TEXT_DIM_RGB.1,
609 SHORELINE_LIGHT_TEXT_DIM_RGB.2,
610 ),
611 text_hint: Color::Rgb(
612 SHORELINE_LIGHT_TEXT_HINT_RGB.0,
613 SHORELINE_LIGHT_TEXT_HINT_RGB.1,
614 SHORELINE_LIGHT_TEXT_HINT_RGB.2,
615 ),
616 text_muted: Color::Rgb(
617 SHORELINE_LIGHT_TEXT_MUTED_RGB.0,
618 SHORELINE_LIGHT_TEXT_MUTED_RGB.1,
619 SHORELINE_LIGHT_TEXT_MUTED_RGB.2,
620 ),
621 text_body: Color::Rgb(
622 SHORELINE_LIGHT_TEXT_BODY_RGB.0,
623 SHORELINE_LIGHT_TEXT_BODY_RGB.1,
624 SHORELINE_LIGHT_TEXT_BODY_RGB.2,
625 ),
626 text_soft: Color::Rgb(
627 SHORELINE_LIGHT_TEXT_SOFT_RGB.0,
628 SHORELINE_LIGHT_TEXT_SOFT_RGB.1,
629 SHORELINE_LIGHT_TEXT_SOFT_RGB.2,
630 ),
631 border: Color::Rgb(
632 SHORELINE_LIGHT_BORDER_RGB.0,
633 SHORELINE_LIGHT_BORDER_RGB.1,
634 SHORELINE_LIGHT_BORDER_RGB.2,
635 ),
636 accent_primary: Color::Rgb(
637 SHORELINE_LIGHT_ACTION_RGB.0,
638 SHORELINE_LIGHT_ACTION_RGB.1,
639 SHORELINE_LIGHT_ACTION_RGB.2,
640 ),
641 accent_secondary: Color::Rgb(
642 SHORELINE_LIGHT_LIVE_RGB.0,
643 SHORELINE_LIGHT_LIVE_RGB.1,
644 SHORELINE_LIGHT_LIVE_RGB.2,
645 ),
646 accent_action: Color::Rgb(
647 SHORELINE_LIGHT_HUMAN_RGB.0,
648 SHORELINE_LIGHT_HUMAN_RGB.1,
649 SHORELINE_LIGHT_HUMAN_RGB.2,
650 ),
651 error_fg: Color::Rgb(
652 SHORELINE_LIGHT_ERROR_RGB.0,
653 SHORELINE_LIGHT_ERROR_RGB.1,
654 SHORELINE_LIGHT_ERROR_RGB.2,
655 ),
656 error_hover: Color::Rgb(
657 SHORELINE_LIGHT_ERROR_RGB.0,
658 SHORELINE_LIGHT_ERROR_RGB.1,
659 SHORELINE_LIGHT_ERROR_RGB.2,
660 ),
661 error_surface: Color::Rgb(
662 SHORELINE_LIGHT_ERROR_SURFACE_RGB.0,
663 SHORELINE_LIGHT_ERROR_SURFACE_RGB.1,
664 SHORELINE_LIGHT_ERROR_SURFACE_RGB.2,
665 ),
666 error_border: Color::Rgb(
667 SHORELINE_LIGHT_ERROR_RGB.0,
668 SHORELINE_LIGHT_ERROR_RGB.1,
669 SHORELINE_LIGHT_ERROR_RGB.2,
670 ),
671 error_text: Color::Rgb(
672 SHORELINE_LIGHT_ERROR_TEXT_RGB.0,
673 SHORELINE_LIGHT_ERROR_TEXT_RGB.1,
674 SHORELINE_LIGHT_ERROR_TEXT_RGB.2,
675 ),
676 warning: Color::Rgb(
677 SHORELINE_LIGHT_WARNING_RGB.0,
678 SHORELINE_LIGHT_WARNING_RGB.1,
679 SHORELINE_LIGHT_WARNING_RGB.2,
680 ),
681 success: Color::Rgb(
682 SHORELINE_LIGHT_SUCCESS_RGB.0,
683 SHORELINE_LIGHT_SUCCESS_RGB.1,
684 SHORELINE_LIGHT_SUCCESS_RGB.2,
685 ),
686 info: Color::Rgb(
687 SHORELINE_LIGHT_ACTION_RGB.0,
688 SHORELINE_LIGHT_ACTION_RGB.1,
689 SHORELINE_LIGHT_ACTION_RGB.2,
690 ),
691 mode_agent: Color::Rgb(
692 SHORELINE_LIGHT_MODE_AGENT_RGB.0,
693 SHORELINE_LIGHT_MODE_AGENT_RGB.1,
694 SHORELINE_LIGHT_MODE_AGENT_RGB.2,
695 ),
696 mode_yolo: Color::Rgb(
697 SHORELINE_LIGHT_MODE_YOLO_RGB.0,
698 SHORELINE_LIGHT_MODE_YOLO_RGB.1,
699 SHORELINE_LIGHT_MODE_YOLO_RGB.2,
700 ),
701 mode_plan: Color::Rgb(
702 SHORELINE_LIGHT_MODE_PLAN_RGB.0,
703 SHORELINE_LIGHT_MODE_PLAN_RGB.1,
704 SHORELINE_LIGHT_MODE_PLAN_RGB.2,
705 ),
706 mode_operate: Color::Rgb(
707 SHORELINE_LIGHT_MODE_OPERATE_RGB.0,
708 SHORELINE_LIGHT_MODE_OPERATE_RGB.1,
709 SHORELINE_LIGHT_MODE_OPERATE_RGB.2,
710 ),
711 permission_ask: Color::Rgb(
712 SHORELINE_LIGHT_TEXT_MUTED_RGB.0,
713 SHORELINE_LIGHT_TEXT_MUTED_RGB.1,
714 SHORELINE_LIGHT_TEXT_MUTED_RGB.2,
715 ),
716 permission_auto_review: Color::Rgb(
717 SHORELINE_LIGHT_HUMAN_RGB.0,
718 SHORELINE_LIGHT_HUMAN_RGB.1,
719 SHORELINE_LIGHT_HUMAN_RGB.2,
720 ),
721 permission_full_access: Color::Rgb(
722 SHORELINE_LIGHT_WARNING_RGB.0,
723 SHORELINE_LIGHT_WARNING_RGB.1,
724 SHORELINE_LIGHT_WARNING_RGB.2,
725 ),
726 status_ready: Color::Rgb(
727 SHORELINE_LIGHT_SUCCESS_RGB.0,
728 SHORELINE_LIGHT_SUCCESS_RGB.1,
729 SHORELINE_LIGHT_SUCCESS_RGB.2,
730 ),
731 status_working: Color::Rgb(
732 SHORELINE_LIGHT_LIVE_RGB.0,
733 SHORELINE_LIGHT_LIVE_RGB.1,
734 SHORELINE_LIGHT_LIVE_RGB.2,
735 ),
736 status_warning: Color::Rgb(
737 SHORELINE_LIGHT_WARNING_RGB.0,
738 SHORELINE_LIGHT_WARNING_RGB.1,
739 SHORELINE_LIGHT_WARNING_RGB.2,
740 ),
741 diff_added_fg: Color::Rgb(
742 SHORELINE_LIGHT_DIFF_ADDED_FG_RGB.0,
743 SHORELINE_LIGHT_DIFF_ADDED_FG_RGB.1,
744 SHORELINE_LIGHT_DIFF_ADDED_FG_RGB.2,
745 ),
746 diff_deleted_fg: Color::Rgb(
747 SHORELINE_LIGHT_DIFF_DELETED_FG_RGB.0,
748 SHORELINE_LIGHT_DIFF_DELETED_FG_RGB.1,
749 SHORELINE_LIGHT_DIFF_DELETED_FG_RGB.2,
750 ),
751 diff_added_bg: Color::Rgb(
752 SHORELINE_LIGHT_DIFF_ADDED_BG_RGB.0,
753 SHORELINE_LIGHT_DIFF_ADDED_BG_RGB.1,
754 SHORELINE_LIGHT_DIFF_ADDED_BG_RGB.2,
755 ),
756 diff_deleted_bg: Color::Rgb(
757 SHORELINE_LIGHT_DIFF_DELETED_BG_RGB.0,
758 SHORELINE_LIGHT_DIFF_DELETED_BG_RGB.1,
759 SHORELINE_LIGHT_DIFF_DELETED_BG_RGB.2,
760 ),
761 tool_running: Color::Rgb(
762 SHORELINE_LIGHT_LIVE_RGB.0,
763 SHORELINE_LIGHT_LIVE_RGB.1,
764 SHORELINE_LIGHT_LIVE_RGB.2,
765 ),
766 tool_success: Color::Rgb(
767 SHORELINE_LIGHT_SUCCESS_RGB.0,
768 SHORELINE_LIGHT_SUCCESS_RGB.1,
769 SHORELINE_LIGHT_SUCCESS_RGB.2,
770 ),
771 tool_failed: Color::Rgb(
772 SHORELINE_LIGHT_ERROR_RGB.0,
773 SHORELINE_LIGHT_ERROR_RGB.1,
774 SHORELINE_LIGHT_ERROR_RGB.2,
775 ),
776 };
777
778 pub const SOLARIZED_LIGHT_UI_THEME: UiTheme = UiTheme {
779 name: "solarized-light",
780 mode: PaletteMode::SolarizedLight,
781 surface_bg: SOLARIZED_SURFACE,
782 panel_bg: SOLARIZED_PANEL,
783 elevated_bg: SOLARIZED_ELEVATED,
784 composer_bg: SOLARIZED_COMPOSER,
785 selection_bg: SOLARIZED_SELECT_BG,
786 header_bg: SOLARIZED_SURFACE,
787 footer_bg: SOLARIZED_SURFACE,
788 text_dim: SOLARIZED_TEXT_DIM,
789 text_hint: SOLARIZED_TEXT_HINT,
790 text_muted: SOLARIZED_TEXT_MUTED,
791 text_body: SOLARIZED_TEXT_BODY,
792 text_soft: SOLARIZED_TEXT_SOFT,
793 border: SOLARIZED_BORDER,
794 accent_primary: SOLARIZED_BLUE,
795 accent_secondary: SOLARIZED_CYAN,
796 accent_action: SOLARIZED_ORANGE,
797 error_fg: SOLARIZED_RED,
798 error_hover: SOLARIZED_ERROR_HOVER,
799 error_surface: SOLARIZED_ERROR_SURFACE,
800 error_border: SOLARIZED_RED,
801 error_text: SOLARIZED_ERROR_TEXT,
802 warning: SOLARIZED_YELLOW,
803 success: SOLARIZED_GREEN,
804 info: SOLARIZED_BLUE,
805 mode_agent: Color::Rgb(0x27, 0x8B, 0xD2),
806 mode_yolo: Color::Rgb(0xDD, 0x32, 0x2F),
807 mode_plan: Color::Rgb(0xCC, 0x4B, 0x16),
808 mode_operate: Color::Rgb(0x6C, 0x71, 0xC4), // solarized violet
809 permission_ask: SOLARIZED_YELLOW,
810 permission_auto_review: SOLARIZED_ORANGE,
811 permission_full_access: SOLARIZED_RED,
812 status_ready: SOLARIZED_CYAN,
813 status_working: SOLARIZED_CYAN,
814 status_warning: SOLARIZED_YELLOW,
815 diff_added_fg: SOLARIZED_GREEN,
816 diff_deleted_fg: SOLARIZED_RED,
817 diff_added_bg: SOLARIZED_DIFF_ADDED_BG,
818 diff_deleted_bg: SOLARIZED_DIFF_DELETED_BG,
819 tool_running: SOLARIZED_BLUE,
820 tool_success: SOLARIZED_GREEN,
821 tool_failed: SOLARIZED_RED,
822 };
823
824 pub const GRAYSCALE_UI_THEME: UiTheme = UiTheme {
825 name: "grayscale",
826 mode: PaletteMode::Grayscale,
827 surface_bg: GRAYSCALE_SURFACE,
828 panel_bg: GRAYSCALE_PANEL,
829 elevated_bg: GRAYSCALE_ELEVATED,
830 composer_bg: GRAYSCALE_PANEL,
831 selection_bg: GRAYSCALE_SELECTION_BG,
832 header_bg: GRAYSCALE_SURFACE,
833 footer_bg: GRAYSCALE_SURFACE,
834 text_dim: GRAYSCALE_TEXT_HINT,
835 text_hint: GRAYSCALE_TEXT_HINT,
836 text_muted: GRAYSCALE_TEXT_MUTED,
837 text_body: GRAYSCALE_TEXT_BODY,
838 text_soft: GRAYSCALE_TEXT_SOFT,
839 border: GRAYSCALE_BORDER,
840 accent_primary: GRAYSCALE_TEXT_SOFT,
841 accent_secondary: GRAYSCALE_TEXT_MUTED,
842 accent_action: Color::Rgb(210, 210, 210),
843 error_fg: GRAYSCALE_TEXT_BODY,
844 error_hover: GRAYSCALE_TEXT_SOFT,
845 error_surface: GRAYSCALE_ERROR,
846 error_border: GRAYSCALE_BORDER,
847 error_text: GRAYSCALE_TEXT_SOFT,
848 warning: GRAYSCALE_TEXT_MUTED,
849 success: GRAYSCALE_TEXT_SOFT,
850 info: GRAYSCALE_TEXT_MUTED,
851 mode_agent: Color::Rgb(200, 200, 200),
852 mode_yolo: Color::Rgb(237, 237, 237),
853 mode_plan: Color::Rgb(181, 181, 181),
854 // Near-white stays unmistakably distinct without claiming arbitrary pure
855 // white content as an already-resolved mode slot.
856 mode_operate: Color::Rgb(250, 250, 250),
857 permission_ask: Color::Rgb(181, 181, 181),
858 permission_auto_review: Color::Rgb(210, 210, 210),
859 permission_full_access: Color::Rgb(237, 237, 237),
860 status_ready: GRAYSCALE_TEXT_MUTED,
861 status_working: GRAYSCALE_TEXT_SOFT,
862 status_warning: GRAYSCALE_TEXT_BODY,
863 diff_added_fg: GRAYSCALE_TEXT_SOFT,
864 diff_deleted_fg: GRAYSCALE_TEXT_BODY,
865 diff_added_bg: GRAYSCALE_SUCCESS,
866 diff_deleted_bg: GRAYSCALE_ERROR,
867 tool_running: GRAYSCALE_TEXT_SOFT,
868 tool_success: GRAYSCALE_TEXT_HINT,
869 tool_failed: GRAYSCALE_TEXT_BODY,
870 };
871
872 pub const CATPPUCCIN_MOCHA_UI_THEME: UiTheme = UiTheme {
873 name: "catppuccin-mocha",
874 mode: PaletteMode::Dark,
875 surface_bg: Color::Rgb(0x1e, 0x1e, 0x2e), // base
876 panel_bg: Color::Rgb(0x18, 0x18, 0x25), // mantle
877 elevated_bg: Color::Rgb(0x31, 0x32, 0x44), // surface0
878 composer_bg: Color::Rgb(0x18, 0x18, 0x25),
879 selection_bg: Color::Rgb(0x45, 0x47, 0x5a), // surface1
880 header_bg: Color::Rgb(0x11, 0x11, 0x1b), // crust
881 footer_bg: Color::Rgb(0x11, 0x11, 0x1b),
882 text_dim: Color::Rgb(0x6c, 0x70, 0x86), // overlay0
883 text_hint: Color::Rgb(0x7f, 0x84, 0x9c), // overlay1
884 text_muted: Color::Rgb(0xa6, 0xad, 0xc8), // subtext0
885 text_body: Color::Rgb(0xcd, 0xd6, 0xf4), // text
886 text_soft: Color::Rgb(0xba, 0xc2, 0xde), // subtext1
887 border: Color::Rgb(0x45, 0x47, 0x5a), // surface1
888 accent_primary: Color::Rgb(0x89, 0xb4, 0xfa), // blue
889 accent_secondary: Color::Rgb(0x74, 0xc7, 0xec), // sapphire
890 accent_action: Color::Rgb(0xfa, 0xb3, 0x87), // peach
891 error_fg: Color::Rgb(0xf3, 0x8b, 0xa8), // red
892 error_hover: Color::Rgb(0xf5, 0xa2, 0xbc),
893 error_surface: Color::Rgb(0x3a, 0x1f, 0x2a),
894 error_border: Color::Rgb(0xf3, 0x8b, 0xa8),
895 error_text: Color::Rgb(0xf5, 0xc2, 0xd0),
896 warning: Color::Rgb(0xf9, 0xe2, 0xaf), // yellow
897 success: Color::Rgb(0xa6, 0xe3, 0xa1), // green
898 info: Color::Rgb(0x89, 0xd9, 0xeb), // sky
899 mode_agent: Color::Rgb(0x8a, 0xb4, 0xfa), // blue
900 mode_yolo: Color::Rgb(0xf3, 0x8c, 0xa8), // red
901 mode_plan: Color::Rgb(0xfa, 0xb4, 0x87), // peach
902 mode_operate: Color::Rgb(0xcb, 0xa6, 0xf7), // mauve
903 permission_ask: Color::Rgb(0xf9, 0xe2, 0xaf),
904 permission_auto_review: Color::Rgb(0xfa, 0xb3, 0x87),
905 permission_full_access: Color::Rgb(0xf3, 0x8b, 0xa8),
906 status_ready: Color::Rgb(0x7f, 0x84, 0x9c), // overlay1
907 status_working: Color::Rgb(0x74, 0xc7, 0xec), // sapphire
908 status_warning: Color::Rgb(0xf9, 0xe2, 0xaf), // yellow
909 diff_added_fg: Color::Rgb(0xa6, 0xe3, 0xa1), // green
910 diff_deleted_fg: Color::Rgb(0xf3, 0x8b, 0xa8), // red
911 diff_added_bg: Color::Rgb(0x1f, 0x33, 0x29),
912 diff_deleted_bg: Color::Rgb(0x3a, 0x1f, 0x2a),
913 tool_running: Color::Rgb(0x74, 0xc7, 0xec), // sapphire
914 tool_success: Color::Rgb(0x7f, 0x84, 0x9c), // overlay1
915 tool_failed: Color::Rgb(0xf3, 0x8b, 0xa8), // red
916 };
917
918 pub const TOKYO_NIGHT_UI_THEME: UiTheme = UiTheme {
919 name: "tokyo-night",
920 mode: PaletteMode::Dark,
921 surface_bg: Color::Rgb(0x1a, 0x1b, 0x26), // bg
922 panel_bg: Color::Rgb(0x16, 0x16, 0x1e), // bg_dark
923 elevated_bg: Color::Rgb(0x29, 0x2e, 0x42), // bg_highlight
924 composer_bg: Color::Rgb(0x16, 0x16, 0x1e),
925 selection_bg: Color::Rgb(0x28, 0x34, 0x57), // visual selection
926 header_bg: Color::Rgb(0x16, 0x16, 0x1e),
927 footer_bg: Color::Rgb(0x16, 0x16, 0x1e),
928 text_dim: Color::Rgb(0x5c, 0x65, 0x8d), // comment, lifted to 3:1 on bg
929 text_hint: Color::Rgb(0x73, 0x7a, 0xa2), // dark5
930 text_muted: Color::Rgb(0xa9, 0xb1, 0xd6), // fg_dark
931 text_body: Color::Rgb(0xc0, 0xca, 0xf5), // fg
932 text_soft: Color::Rgb(0xbb, 0xc2, 0xe0),
933 border: Color::Rgb(0x41, 0x48, 0x68), // terminal_black
934 accent_primary: Color::Rgb(0x7a, 0xa2, 0xf7), // blue
935 accent_secondary: Color::Rgb(0x7d, 0xcf, 0xff), // cyan
936 accent_action: Color::Rgb(0xff, 0x9e, 0x64), // orange
937 error_fg: Color::Rgb(0xf7, 0x76, 0x8e), // red
938 error_hover: Color::Rgb(0xf9, 0x92, 0xa4),
939 error_surface: Color::Rgb(0x33, 0x1c, 0x24),
940 error_border: Color::Rgb(0xf7, 0x76, 0x8e),
941 error_text: Color::Rgb(0xfa, 0xcc, 0xd4),
942 warning: Color::Rgb(0xe0, 0xaf, 0x68), // yellow
943 success: Color::Rgb(0x9e, 0xce, 0x6a), // green
944 info: Color::Rgb(0x7d, 0xcf, 0xff), // cyan
945 mode_agent: Color::Rgb(0x7b, 0xa2, 0xf7), // blue
946 mode_yolo: Color::Rgb(0xf7, 0x77, 0x8e), // red
947 mode_plan: Color::Rgb(0xff, 0x9f, 0x64), // orange
948 mode_operate: Color::Rgb(0xbb, 0x9a, 0xf7), // purple
949 permission_ask: Color::Rgb(0xe0, 0xaf, 0x68),
950 permission_auto_review: Color::Rgb(0xff, 0x9e, 0x64),
951 permission_full_access: Color::Rgb(0xf7, 0x76, 0x8e),
952 status_ready: Color::Rgb(0x5c, 0x65, 0x8d), // comment, lifted to 3:1 on bg
953 status_working: Color::Rgb(0x7d, 0xcf, 0xff), // cyan
954 status_warning: Color::Rgb(0xe0, 0xaf, 0x68), // yellow
955 diff_added_fg: Color::Rgb(0x9e, 0xce, 0x6a), // green
956 diff_deleted_fg: Color::Rgb(0xf7, 0x76, 0x8e), // red
957 diff_added_bg: Color::Rgb(0x1b, 0x2b, 0x1f),
958 diff_deleted_bg: Color::Rgb(0x33, 0x1c, 0x24),
959 tool_running: Color::Rgb(0x7d, 0xcf, 0xff), // cyan
960 tool_success: Color::Rgb(0x56, 0x5f, 0x89), // comment
961 tool_failed: Color::Rgb(0xf7, 0x76, 0x8e), // red
962 };
963
964 pub const DRACULA_UI_THEME: UiTheme = UiTheme {
965 name: "dracula",
966 mode: PaletteMode::Dark,
967 surface_bg: Color::Rgb(0x28, 0x2a, 0x36), // background
968 panel_bg: Color::Rgb(0x21, 0x22, 0x2c),
969 elevated_bg: Color::Rgb(0x34, 0x37, 0x46),
970 composer_bg: Color::Rgb(0x21, 0x22, 0x2c),
971 selection_bg: Color::Rgb(0x44, 0x47, 0x5a), // current line
972 header_bg: Color::Rgb(0x21, 0x22, 0x2c),
973 footer_bg: Color::Rgb(0x21, 0x22, 0x2c),
974 text_dim: Color::Rgb(0x62, 0x72, 0xa4), // comment
975 text_hint: Color::Rgb(0x8a, 0x8e, 0xaa),
976 text_muted: Color::Rgb(0xc0, 0xc4, 0xd6),
977 text_body: Color::Rgb(0xf8, 0xf8, 0xf2), // foreground
978 text_soft: Color::Rgb(0xe2, 0xe2, 0xdc),
979 border: Color::Rgb(0x44, 0x47, 0x5a),
980 accent_primary: Color::Rgb(0xbd, 0x93, 0xf9), // purple
981 accent_secondary: Color::Rgb(0x8b, 0xe9, 0xfd), // cyan
982 accent_action: Color::Rgb(0xff, 0xb8, 0x6c), // orange
983 error_fg: Color::Rgb(0xff, 0x55, 0x55), // red
984 error_hover: Color::Rgb(0xff, 0x7c, 0x7c),
985 error_surface: Color::Rgb(0x3a, 0x1f, 0x22),
986 error_border: Color::Rgb(0xff, 0x55, 0x55),
987 error_text: Color::Rgb(0xff, 0xbb, 0xbb),
988 warning: Color::Rgb(0xf1, 0xfa, 0x8c), // yellow
989 success: Color::Rgb(0x50, 0xfa, 0x7b), // green
990 info: Color::Rgb(0x8b, 0xe9, 0xfd), // cyan
991 mode_agent: Color::Rgb(0xbe, 0x93, 0xf9), // purple
992 mode_yolo: Color::Rgb(0xff, 0x56, 0x55), // red
993 mode_plan: Color::Rgb(0xff, 0xb9, 0x6c), // orange
994 mode_operate: Color::Rgb(0x8c, 0xe9, 0xfd), // cyan
995 permission_ask: Color::Rgb(0xf1, 0xfa, 0x8c),
996 permission_auto_review: Color::Rgb(0xff, 0xb8, 0x6c),
997 permission_full_access: Color::Rgb(0xff, 0x55, 0x55),
998 status_ready: Color::Rgb(0x62, 0x72, 0xa4), // comment
999 status_working: Color::Rgb(0x8b, 0xe9, 0xfd), // cyan
1000 status_warning: Color::Rgb(0xf1, 0xfa, 0x8c), // yellow
1001 diff_added_fg: Color::Rgb(0x50, 0xfa, 0x7b), // green
1002 diff_deleted_fg: Color::Rgb(0xff, 0x55, 0x55), // red
1003 diff_added_bg: Color::Rgb(0x21, 0x3a, 0x2a),
1004 diff_deleted_bg: Color::Rgb(0x3a, 0x1f, 0x22),
1005 tool_running: Color::Rgb(0x8b, 0xe9, 0xfd), // cyan
1006 tool_success: Color::Rgb(0x62, 0x72, 0xa4), // comment
1007 tool_failed: Color::Rgb(0xff, 0x55, 0x55), // red
1008 };
1009
1010 /// "Terminal" theme: lets the host terminal's color scheme show through
1011 /// instead of painting any RGB surface. Backgrounds use `Color::Reset`
1012 /// (the terminal's own default bg) and most text uses `Color::Reset`
1013 /// (terminal's own default fg). Accents are ANSI named colors so they
1014 /// also inherit the user's terminal palette (Solarized, Nord, custom
1015 /// schemes, etc.) rather than Codewhale brand RGB.
1016 pub const TERMINAL_UI_THEME: UiTheme = UiTheme {
1017 name: "terminal",
1018 // Mode is reported as Dark to avoid the dark→light cell remap kicking
1019 // in; the terminal-theme cell remap already normalizes everything to
1020 // `Color::Reset`, and we never want a second pass overwriting that.
1021 mode: PaletteMode::Dark,
1022 surface_bg: Color::Reset,
1023 panel_bg: Color::Reset,
1024 elevated_bg: Color::Reset,
1025 composer_bg: Color::Reset,
1026 selection_bg: Color::Reset,
1027 header_bg: Color::Reset,
1028 footer_bg: Color::Reset,
1029 text_dim: Color::Reset,
1030 text_hint: Color::Reset,
1031 text_muted: Color::Reset,
1032 text_body: Color::Reset,
1033 text_soft: Color::Reset,
1034 border: Color::Reset,
1035 accent_primary: Color::Blue,
1036 accent_secondary: Color::Cyan,
1037 accent_action: Color::Yellow,
1038 error_fg: Color::Red,
1039 error_hover: Color::Red,
1040 error_surface: Color::Reset,
1041 error_border: Color::Red,
1042 error_text: Color::Red,
1043 warning: Color::Yellow,
1044 success: Color::Green,
1045 info: Color::Cyan,
1046 mode_agent: Color::LightBlue,
1047 mode_yolo: Color::LightRed,
1048 // Magenta keeps Plan visually distinct from `status_warning` (yellow)
1049 // so the mode indicator and warning chip don't collide on themes that
1050 // render both in the status row.
1051 mode_plan: Color::Magenta,
1052 mode_operate: Color::LightCyan,
1053 permission_ask: Color::Yellow,
1054 permission_auto_review: Color::LightYellow,
1055 permission_full_access: Color::LightRed,
1056 // DarkGray gives "Ready" a low-contrast but still distinguishable hue
1057 // versus default body text (which is `Color::Reset` on this theme).
1058 status_ready: Color::DarkGray,
1059 status_working: Color::Cyan,
1060 status_warning: Color::Yellow,
1061 diff_added_fg: Color::Green,
1062 diff_deleted_fg: Color::Red,
1063 diff_added_bg: Color::Reset,
1064 diff_deleted_bg: Color::Reset,
1065 tool_running: Color::Cyan,
1066 tool_success: Color::Green,
1067 tool_failed: Color::Red,
1068 };
1069
1070 pub const GRUVBOX_DARK_UI_THEME: UiTheme = UiTheme {
1071 name: "gruvbox-dark",
1072 mode: PaletteMode::Dark,
1073 surface_bg: Color::Rgb(0x28, 0x28, 0x28), // bg0
1074 panel_bg: Color::Rgb(0x3c, 0x38, 0x36), // bg1
1075 elevated_bg: Color::Rgb(0x50, 0x49, 0x45), // bg2
1076 composer_bg: Color::Rgb(0x3c, 0x38, 0x36),
1077 selection_bg: Color::Rgb(0x66, 0x5c, 0x54), // bg3
1078 header_bg: Color::Rgb(0x1d, 0x20, 0x21), // bg0_h
1079 footer_bg: Color::Rgb(0x1d, 0x20, 0x21),
1080 text_dim: Color::Rgb(0x92, 0x83, 0x74), // gray
1081 text_hint: Color::Rgb(0xa8, 0x99, 0x84), // fg4
1082 text_muted: Color::Rgb(0xc5, 0xb8, 0xa0), // fg3, lifted to 4.5:1 on bg2
1083 text_body: Color::Rgb(0xeb, 0xdb, 0xb2), // fg1
1084 text_soft: Color::Rgb(0xd5, 0xc4, 0xa1), // fg2
1085 border: Color::Rgb(0x66, 0x5c, 0x54), // bg3
1086 accent_primary: Color::Rgb(0x83, 0xa5, 0x98), // blue
1087 accent_secondary: Color::Rgb(0x8e, 0xc0, 0x7c), // aqua/green
1088 accent_action: Color::Rgb(0xfe, 0x80, 0x19), // orange
1089 error_fg: Color::Rgb(0xfb, 0x49, 0x34), // red
1090 error_hover: Color::Rgb(0xfc, 0x7c, 0x6b),
1091 error_surface: Color::Rgb(0x35, 0x1c, 0x18),
1092 error_border: Color::Rgb(0xfb, 0x49, 0x34),
1093 error_text: Color::Rgb(0xfc, 0xc4, 0xb8),
1094 warning: Color::Rgb(0xfa, 0xbd, 0x2f), // yellow
1095 success: Color::Rgb(0x8e, 0xc0, 0x7c), // green
1096 info: Color::Rgb(0x83, 0xa5, 0x98), // blue
1097 mode_agent: Color::Rgb(0x84, 0xa5, 0x98), // blue
1098 mode_yolo: Color::Rgb(0xfb, 0x4a, 0x34), // red
1099 mode_plan: Color::Rgb(0xfe, 0x81, 0x19), // orange
1100 mode_operate: Color::Rgb(0xd3, 0x86, 0x9b), // purple
1101 permission_ask: Color::Rgb(0xfa, 0xbd, 0x2f),
1102 permission_auto_review: Color::Rgb(0xfe, 0x80, 0x19),
1103 permission_full_access: Color::Rgb(0xfb, 0x49, 0x34),
1104 status_ready: Color::Rgb(0x92, 0x83, 0x74), // gray
1105 status_working: Color::Rgb(0x8e, 0xc0, 0x7c), // aqua
1106 status_warning: Color::Rgb(0xfa, 0xbd, 0x2f), // yellow
1107 diff_added_fg: Color::Rgb(0x8e, 0xc0, 0x7c), // green
1108 diff_deleted_fg: Color::Rgb(0xfb, 0x49, 0x34), // red
1109 diff_added_bg: Color::Rgb(0x29, 0x32, 0x16),
1110 diff_deleted_bg: Color::Rgb(0x35, 0x1c, 0x18),
1111 tool_running: Color::Rgb(0x8e, 0xc0, 0x7c), // aqua
1112 tool_success: Color::Rgb(0x92, 0x83, 0x74), // gray
1113 tool_failed: Color::Rgb(0xfb, 0x49, 0x34), // red
1114 };
1115
1116 pub const CLAUDE_UI_THEME: UiTheme = UiTheme {
1117 name: "claude",
1118 mode: PaletteMode::Dark,
1119 // Claude Code product surfaces — dark navy with warm undertones
1120 surface_bg: Color::Rgb(0x18, 0x17, 0x15), // surface-dark
1121 panel_bg: Color::Rgb(0x25, 0x23, 0x20), // surface-dark-elevated
1122 elevated_bg: Color::Rgb(0x1f, 0x1e, 0x1b), // surface-dark-soft (code blocks)
1123 composer_bg: Color::Rgb(0x25, 0x23, 0x20),
1124 selection_bg: Color::Rgb(0x30, 0x2d, 0x28),
1125 header_bg: Color::Rgb(0x18, 0x17, 0x15),
1126 footer_bg: Color::Rgb(0x18, 0x17, 0x15),
1127 // Cream-tinted text hierarchy on dark
1128 text_dim: Color::Rgb(0x72, 0x70, 0x6a),
1129 text_hint: Color::Rgb(0x7d, 0x7a, 0x73),
1130 text_muted: Color::Rgb(0xa0, 0x9d, 0x96), // on-dark-soft
1131 text_body: Color::Rgb(0xfa, 0xf9, 0xf5), // on-dark (cream white)
1132 text_soft: Color::Rgb(0xd0, 0xcd, 0xc5),
1133 border: Color::Rgb(0x30, 0x2d, 0x28),
1134 // Coral primary (signature Anthropic accent), teal secondary
1135 accent_primary: Color::Rgb(0xcc, 0x78, 0x5c), // coral
1136 accent_secondary: Color::Rgb(0x5d, 0xb8, 0xa6), // accent-teal
1137 accent_action: Color::Rgb(0xe8, 0xa5, 0x5a), // amber
1138 // Error / destructive — warm red
1139 error_fg: Color::Rgb(0xe0, 0x60, 0x60),
1140 error_hover: Color::Rgb(0xd9, 0x66, 0x66),
1141 error_surface: Color::Rgb(0x2a, 0x1c, 0x1c),
1142 error_border: Color::Rgb(0xe0, 0x60, 0x60),
1143 error_text: Color::Rgb(0xe8, 0xb8, 0xb8),
1144 // Status
1145 warning: Color::Rgb(0xd4, 0xa0, 0x17), // amber
1146 success: Color::Rgb(0x5d, 0xb8, 0x72), // green
1147 info: Color::Rgb(0x5d, 0xb8, 0xa6), // teal
1148 // Mode badges
1149 mode_agent: Color::Rgb(0xcd, 0x78, 0x5c), // coral
1150 mode_yolo: Color::Rgb(0xc7, 0x45, 0x45), // red
1151 mode_plan: Color::Rgb(0xe8, 0xa6, 0x5a), // amber
1152 mode_operate: Color::Rgb(0x8a, 0x63, 0xd2), // violet
1153 permission_ask: Color::Rgb(0xd4, 0xa0, 0x17),
1154 permission_auto_review: Color::Rgb(0xe8, 0xa5, 0x5a),
1155 permission_full_access: Color::Rgb(0xe0, 0x60, 0x60),
1156 // Footer statusline
1157 status_ready: Color::Rgb(0xa0, 0x9d, 0x96),
1158 status_working: Color::Rgb(0x5d, 0xb8, 0xa6),
1159 status_warning: Color::Rgb(0xd4, 0xa0, 0x17),
1160 // Diff
1161 diff_added_fg: Color::Rgb(0x5d, 0xb8, 0x72),
1162 diff_deleted_fg: Color::Rgb(0xc6, 0x45, 0x45),
1163 diff_added_bg: Color::Rgb(0x1a, 0x24, 0x1d),
1164 diff_deleted_bg: Color::Rgb(0x24, 0x1a, 0x1a),
1165 // Tool cells
1166 tool_running: Color::Rgb(0x5d, 0xb8, 0xa6),
1167 tool_success: Color::Rgb(0xa0, 0x9d, 0x96),
1168 tool_failed: Color::Rgb(0xc6, 0x45, 0x45),
1169 };
1170
1171 pub const MATRIX_UI_THEME: UiTheme = UiTheme {
1172 name: "matrix",
1173 mode: PaletteMode::Dark,
1174 surface_bg: Color::Rgb(
1175 MATRIX_SURFACE_RGB.0,
1176 MATRIX_SURFACE_RGB.1,
1177 MATRIX_SURFACE_RGB.2,
1178 ),
1179 panel_bg: Color::Rgb(
1180 MATRIX_SURFACE_RGB.0,
1181 MATRIX_SURFACE_RGB.1,
1182 MATRIX_SURFACE_RGB.2,
1183 ),
1184 elevated_bg: Color::Rgb(
1185 MATRIX_ELEVATED_RGB.0,
1186 MATRIX_ELEVATED_RGB.1,
1187 MATRIX_ELEVATED_RGB.2,
1188 ),
1189 composer_bg: Color::Rgb(
1190 MATRIX_SURFACE_RGB.0,
1191 MATRIX_SURFACE_RGB.1,
1192 MATRIX_SURFACE_RGB.2,
1193 ),
1194 selection_bg: Color::Rgb(
1195 MATRIX_SELECTION_RGB.0,
1196 MATRIX_SELECTION_RGB.1,
1197 MATRIX_SELECTION_RGB.2,
1198 ),
1199 header_bg: Color::Rgb(
1200 MATRIX_SURFACE_RGB.0,
1201 MATRIX_SURFACE_RGB.1,
1202 MATRIX_SURFACE_RGB.2,
1203 ),
1204 footer_bg: Color::Rgb(
1205 MATRIX_SURFACE_RGB.0,
1206 MATRIX_SURFACE_RGB.1,
1207 MATRIX_SURFACE_RGB.2,
1208 ),
1209 text_dim: Color::Rgb(
1210 MATRIX_TEXT_DIM_RGB.0,
1211 MATRIX_TEXT_DIM_RGB.1,
1212 MATRIX_TEXT_DIM_RGB.2,
1213 ),
1214 text_hint: Color::Rgb(
1215 MATRIX_TEXT_HINT_RGB.0,
1216 MATRIX_TEXT_HINT_RGB.1,
1217 MATRIX_TEXT_HINT_RGB.2,
1218 ),
1219 text_muted: Color::Rgb(
1220 MATRIX_TEXT_MUTED_RGB.0,
1221 MATRIX_TEXT_MUTED_RGB.1,
1222 MATRIX_TEXT_MUTED_RGB.2,
1223 ),
1224 text_body: Color::Rgb(
1225 MATRIX_TEXT_BODY_RGB.0,
1226 MATRIX_TEXT_BODY_RGB.1,
1227 MATRIX_TEXT_BODY_RGB.2,
1228 ),
1229 text_soft: Color::Rgb(
1230 MATRIX_TEXT_SOFT_RGB.0,
1231 MATRIX_TEXT_SOFT_RGB.1,
1232 MATRIX_TEXT_SOFT_RGB.2,
1233 ),
1234 border: Color::Rgb(
1235 MATRIX_BORDER_RGB.0,
1236 MATRIX_BORDER_RGB.1,
1237 MATRIX_BORDER_RGB.2,
1238 ),
1239 accent_primary: Color::Rgb(
1240 MATRIX_BORDER_RGB.0,
1241 MATRIX_BORDER_RGB.1,
1242 MATRIX_BORDER_RGB.2,
1243 ),
1244 accent_secondary: Color::Rgb(0, 153, 0),
1245 accent_action: Color::Rgb(0x88, 0xff, 0x88),
1246 error_fg: Color::Rgb(0xb4, 0, 0),
1247 error_hover: Color::Rgb(0xe0, 0, 0),
1248 error_surface: Color::Rgb(0x1a, 0x0d, 0x0d),
1249 error_border: Color::Rgb(0xb4, 0, 0),
1250 error_text: Color::Rgb(0xff, 0x44, 0x44),
1251 warning: Color::Rgb(204, 204, 0),
1252 success: Color::Rgb(0x88, 0xff, 0x88),
1253 info: Color::Rgb(0, 204, 0),
1254 mode_agent: Color::Rgb(0, 154, 0),
1255 mode_yolo: Color::Rgb(255, 100, 100),
1256 mode_plan: Color::Rgb(255, 170, 60),
1257 mode_operate: Color::Rgb(100, 255, 220),
1258 permission_ask: Color::Rgb(204, 204, 0),
1259 permission_auto_review: Color::Rgb(255, 170, 60),
1260 permission_full_access: Color::Rgb(255, 100, 100),
1261 status_ready: Color::Rgb(0, 108, 0),
1262 status_working: Color::Rgb(
1263 MATRIX_TEXT_BODY_RGB.0,
1264 MATRIX_TEXT_BODY_RGB.1,
1265 MATRIX_TEXT_BODY_RGB.2,
1266 ),
1267 status_warning: Color::Rgb(204, 204, 0),
1268 diff_added_fg: Color::Rgb(0x88, 0xff, 0x88),
1269 diff_deleted_fg: Color::Rgb(0xbc, 0x1d, 0x1d), // lifted to 3:1 on diff_deleted_bg
1270 diff_added_bg: Color::Rgb(0x0d, 0x1a, 0x0d),
1271 diff_deleted_bg: Color::Rgb(0x1a, 0x0d, 0x0d),
1272 tool_running: Color::Rgb(0x88, 0xff, 0x88),
1273 tool_success: Color::Rgb(0, 102, 0),
1274 tool_failed: Color::Rgb(0xb4, 0, 0),
1275 };
1276
1277 pub const UWU_UI_THEME: UiTheme = UiTheme {
1278 name: "uwu",
1279 mode: PaletteMode::Dark,
1280 surface_bg: Color::Rgb(0x16, 0x12, 0x1c),
1281 panel_bg: Color::Rgb(0x1f, 0x18, 0x28),
1282 elevated_bg: Color::Rgb(0x2c, 0x22, 0x38),
1283 composer_bg: Color::Rgb(0x1f, 0x18, 0x28),
1284 selection_bg: Color::Rgb(0x3d, 0x2e, 0x4e),
1285 header_bg: Color::Rgb(0x12, 0x0e, 0x18),
1286 footer_bg: Color::Rgb(0x12, 0x0e, 0x18),
1287 text_dim: Color::Rgb(0x6e, 0x60, 0x7a),
1288 text_hint: Color::Rgb(0x8a, 0x7c, 0x98),
1289 text_muted: Color::Rgb(0xb8, 0xaa, 0xc8),
1290 text_body: Color::Rgb(0xf7, 0xf0, 0xf8),
1291 text_soft: Color::Rgb(0xe8, 0xdc, 0xee),
1292 border: Color::Rgb(0x4a, 0x3a, 0x5c),
1293 accent_primary: Color::Rgb(0xff, 0x9e, 0xcd),
1294 accent_secondary: Color::Rgb(0x9a, 0xec, 0xe0),
1295 accent_action: Color::Rgb(0xff, 0xd6, 0x9a),
1296 error_fg: Color::Rgb(0xff, 0x6b, 0x8a),
1297 error_hover: Color::Rgb(0xff, 0x8f, 0xa8),
1298 error_surface: Color::Rgb(0x3a, 0x18, 0x28),
1299 error_border: Color::Rgb(0xff, 0x6b, 0x8a),
1300 error_text: Color::Rgb(0xff, 0xc8, 0xd6),
1301 warning: Color::Rgb(0xff, 0xe0, 0x8a),
1302 success: Color::Rgb(0x9a, 0xef, 0xc0),
1303 info: Color::Rgb(0xb8, 0xc8, 0xff),
1304 mode_agent: Color::Rgb(0xc4, 0xa8, 0xff),
1305 mode_yolo: Color::Rgb(0xff, 0x55, 0x77),
1306 mode_plan: Color::Rgb(0xff, 0xb4, 0xe8),
1307 mode_operate: Color::Rgb(0x8a, 0xd4, 0xff),
1308 permission_ask: Color::Rgb(0xff, 0xe0, 0x8a),
1309 permission_auto_review: Color::Rgb(0xff, 0xd6, 0x9a),
1310 permission_full_access: Color::Rgb(0xff, 0x6b, 0x8a),
1311 status_ready: Color::Rgb(0x8a, 0x7c, 0x98),
1312 status_working: Color::Rgb(0x9a, 0xec, 0xe0),
1313 status_warning: Color::Rgb(0xff, 0xe0, 0x8a),
1314 diff_added_fg: Color::Rgb(0x9a, 0xef, 0xc0),
1315 diff_deleted_fg: Color::Rgb(0xff, 0x6b, 0x8a),
1316 diff_added_bg: Color::Rgb(0x1a, 0x2a, 0x24),
1317 diff_deleted_bg: Color::Rgb(0x3a, 0x18, 0x28),
1318 tool_running: Color::Rgb(0x9a, 0xec, 0xe0),
1319 tool_success: Color::Rgb(0x8a, 0x7c, 0x98),
1320 tool_failed: Color::Rgb(0xff, 0x6b, 0x8a),
1321 };
1322
1323 impl ThemeId {
1324 /// Resolve to a concrete `UiTheme`. For `System` this consults
1325 /// `PaletteMode::detect()` exactly once and returns the corresponding
1326 /// dark/light theme — callers that want to live-track terminal background
1327 /// changes need to re-invoke this.
1328 #[must_use]
1329 pub fn ui_theme(self) -> UiTheme {
1330 match self {
1331 Self::System => UiTheme::detect(),
1332 Self::Terminal => TERMINAL_UI_THEME,
1333 Self::Shoreline => SHORELINE_UI_THEME,
1334 Self::ShorelineLight => SHORELINE_LIGHT_UI_THEME,
1335 Self::Underwater => UNDERWATER_UI_THEME,
1336 Self::UnderwaterRetro => UNDERWATER_RETRO_UI_THEME,
1337 Self::Whale => UI_THEME,
1338 Self::WhaleLight => LIGHT_UI_THEME,
1339 Self::Grayscale => GRAYSCALE_UI_THEME,
1340 Self::CatppuccinMocha => CATPPUCCIN_MOCHA_UI_THEME,
1341 Self::TokyoNight => TOKYO_NIGHT_UI_THEME,
1342 Self::Dracula => DRACULA_UI_THEME,
1343 Self::GruvboxDark => GRUVBOX_DARK_UI_THEME,
1344 Self::Claude => CLAUDE_UI_THEME,
1345 Self::Matrix => MATRIX_UI_THEME,
1346 Self::SolarizedLight => SOLARIZED_LIGHT_UI_THEME,
1347 Self::Uwu => UWU_UI_THEME,
1348 }
1349 }
1350 }
1351
1352 impl UiTheme {
1353 /// Let the terminal own the ordinary shell while retaining explicit
1354 /// semantic surfaces (selection, elevation, errors, diffs, and status).
1355 ///
1356 /// Deepsea paints these reset cells later through the shared ocean
1357 /// column; Flat leaves them untouched so the host terminal shows through.
1358 #[must_use]
1359 pub const fn with_terminal_native_shell(mut self) -> Self {
1360 self.surface_bg = Color::Reset;
1361 self.panel_bg = Color::Reset;
1362 self.composer_bg = Color::Reset;
1363 self.header_bg = Color::Reset;
1364 self.footer_bg = Color::Reset;
1365 self
1366 }
1367
1368 #[must_use]
1369 pub fn for_mode(mode: PaletteMode) -> Self {
1370 match mode {
1371 PaletteMode::Dark => UI_THEME,
1372 PaletteMode::Light => LIGHT_UI_THEME,
1373 PaletteMode::Grayscale => GRAYSCALE_UI_THEME,
1374 PaletteMode::SolarizedLight => SOLARIZED_LIGHT_UI_THEME,
1375 }
1376 }
1377
1378 #[must_use]
1379 pub fn detect() -> Self {
1380 Self::for_mode(PaletteMode::detect())
1381 }
1382
1383 #[must_use]
1384 pub fn with_background_color(mut self, color: Color) -> Self {
1385 self.surface_bg = color;
1386 self.header_bg = color;
1387 self.footer_bg = color;
1388 self
1389 }
1390 }
1391
1392 #[must_use]
1393 pub fn theme_label_for_mode(mode: PaletteMode) -> &'static str {
1394 match mode {
1395 PaletteMode::Dark => "dark",
1396 PaletteMode::Light => "light",
1397 PaletteMode::Grayscale => "grayscale",
1398 PaletteMode::SolarizedLight => "solarized-light",
1399 }
1400 }
1401
1402 #[must_use]
1403 #[cfg(test)]
1404 pub fn ui_theme_from_settings(theme: &str, background_color: Option<&str>) -> UiTheme {
1405 super::resolve_theme_setting(theme, background_color)
1406 .map(|(_, _, theme)| theme)
1407 .unwrap_or_else(|_| UiTheme::detect())
1408 }
1409
1410 #[must_use]
1411 pub fn parse_hex_rgb_color(value: &str) -> Option<Color> {
1412 let (r, g, b) = parse_hex_rgb(value)?;
1413 Some(Color::Rgb(r, g, b))
1414 }
1415
1416 #[must_use]
1417 pub fn hex_rgb_string(color: Color) -> Option<String> {
1418 let Color::Rgb(r, g, b) = color else {
1419 return None;
1420 };
1421 Some(format!("#{r:02x}{g:02x}{b:02x}"))
1422 }
1423
1424 #[cfg(test)]
1425 mod tests {
1426 use super::*;
1427 use crate::ids::{SELECTABLE_THEMES, normalize_theme_name};
1428
1429 #[test]
1430 fn whale_refresh_keeps_theme_ids_and_default_compatibility() {
1431 let names = SELECTABLE_THEMES
1432 .iter()
1433 .map(|theme| theme.name())
1434 .collect::<Vec<_>>();
1435 assert_eq!(
1436 names,
1437 [
1438 "system",
1439 "terminal",
1440 "shoreline",
1441 "shoreline-light",
1442 "underwater",
1443 "underwater-retro",
1444 "dark",
1445 "light",
1446 "grayscale",
1447 "catppuccin-mocha",
1448 "tokyo-night",
1449 "dracula",
1450 "gruvbox-dark",
1451 "claude",
1452 "matrix",
1453 "solarized-light",
1454 "uwu",
1455 ]
1456 );
1457 assert_eq!(normalize_theme_name("default"), Some("system"));
1458 assert_eq!(normalize_theme_name("whale"), Some("dark"));
1459 // The retired treatment spellings are theme names now.
1460 assert_eq!(normalize_theme_name("underwater"), Some("underwater"));
1461 assert_eq!(normalize_theme_name("deepsea"), Some("underwater"));
1462 assert_eq!(normalize_theme_name("ombre"), Some("underwater"));
1463 assert_eq!(normalize_theme_name("owo"), Some("uwu"));
1464 assert_eq!(normalize_theme_name("kawaii"), Some("uwu"));
1465 assert_eq!(normalize_theme_name("retro"), Some("underwater-retro"));
1466 assert_eq!(
1467 ThemeId::from_name("underwater-retro"),
1468 Some(ThemeId::UnderwaterRetro)
1469 );
1470 assert_eq!(ThemeId::UnderwaterRetro.name(), "underwater-retro");
1471 }
1472
1473 #[test]
1474 fn whale_pair_uses_codewhale_semantic_grammar() {
1475 assert_eq!(UI_THEME.accent_primary, WHALE_ACTION);
1476 assert_eq!(UI_THEME.status_working, WHALE_LIVE);
1477 assert_eq!(UI_THEME.accent_action, WHALE_HUMAN);
1478 assert_eq!(UI_THEME.warning, STATUS_WARNING);
1479 assert_eq!(UI_THEME.error_fg, WHALE_ERROR);
1480 assert_eq!(UI_THEME.mode_operate, MODE_OPERATE);
1481 assert_eq!(LIGHT_UI_THEME.accent_primary, LIGHT_ACTION);
1482 assert_eq!(LIGHT_UI_THEME.status_working, LIGHT_LIVE);
1483 assert_eq!(LIGHT_UI_THEME.accent_action, LIGHT_HUMAN);
1484 assert_eq!(LIGHT_UI_THEME.warning, LIGHT_WARNING);
1485 assert_eq!(LIGHT_UI_THEME.error_fg, LIGHT_DANGER);
1486 assert_eq!(LIGHT_UI_THEME.mode_operate, LIGHT_OPERATE);
1487 }
1488
1489 /// Dogfood A7 (#4092): every mode must be tellable apart from the footer
1490 /// badge alone — Operate must never wear the full-access red again. Mode
1491 /// slots also stay distinct from general semantic lanes so limited-color
1492 /// adaptation can identify direct `UiTheme` call sites without guessing.
1493 #[test]
1494 fn every_selectable_theme_keeps_mode_badges_distinct() {
1495 for theme_id in SELECTABLE_THEMES {
1496 let ui = theme_id.ui_theme();
1497 let badges = [
1498 ("work", ui.mode_agent),
1499 ("plan", ui.mode_plan),
1500 ("operate", ui.mode_operate),
1501 ("full access", ui.mode_yolo),
1502 ];
1503 for (i, (name_a, color_a)) in badges.iter().enumerate() {
1504 for (name_b, color_b) in badges.iter().skip(i + 1) {
1505 assert_ne!(
1506 color_a,
1507 color_b,
1508 "theme '{}' renders modes '{name_a}' and '{name_b}' with the same badge color",
1509 theme_id.name(),
1510 );
1511 }
1512 }
1513 let semantic_lanes = [
1514 ("action", ui.accent_primary),
1515 ("live", ui.status_working),
1516 ("human", ui.accent_action),
1517 ("warning", ui.warning),
1518 ("danger", ui.error_fg),
1519 ("success", ui.success),
1520 ];
1521 for (mode_name, mode_color) in badges {
1522 for (lane_name, lane_color) in semantic_lanes {
1523 assert_ne!(
1524 mode_color,
1525 lane_color,
1526 "theme '{}' reuses the {lane_name} color for mode '{mode_name}', erasing render-stage identity",
1527 theme_id.name(),
1528 );
1529 }
1530 }
1531 }
1532 }
1533
1534 #[test]
1535 fn every_selectable_theme_keeps_permission_lanes_distinct() {
1536 for theme_id in SELECTABLE_THEMES {
1537 let ui = theme_id.ui_theme();
1538 let permissions = [
1539 ("ask", ui.permission_ask),
1540 ("auto-review", ui.permission_auto_review),
1541 ("full-access", ui.permission_full_access),
1542 ];
1543 for (index, (name_a, color_a)) in permissions.iter().enumerate() {
1544 for (name_b, color_b) in permissions.iter().skip(index + 1) {
1545 assert_ne!(
1546 color_a,
1547 color_b,
1548 "theme '{}' renders permission lanes '{name_a}' and '{name_b}' identically",
1549 theme_id.name(),
1550 );
1551 }
1552 }
1553 }
1554 }
1555
1556 #[test]
1557 fn every_selectable_theme_separates_live_workers_from_completed_work() {
1558 for theme_id in SELECTABLE_THEMES {
1559 let ui = theme_id.ui_theme();
1560 assert_ne!(
1561 ui.info,
1562 ui.success,
1563 "theme '{}' makes live workers indistinguishable from completed work",
1564 theme_id.name(),
1565 );
1566 }
1567 }
1568 }
1569
1569 lines RUST