claudeup 4.35.0 → 4.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/package.json +4 -4
  2. package/src/__tests__/git-worktree.test.ts +108 -0
  3. package/src/__tests__/scope-squares.test.tsx +165 -0
  4. package/src/__tests__/theme-adaptive-colors.test.ts +307 -0
  5. package/src/__tests__/uppercase-keybindings.test.ts +101 -0
  6. package/src/__tests__/worktree-registry-resolution.test.ts +107 -0
  7. package/src/main.tsx +21 -5
  8. package/src/opentui.d.ts +21 -12
  9. package/src/services/claude-settings.ts +37 -7
  10. package/src/services/git-worktree.ts +129 -0
  11. package/src/services/plugin-manager.ts +10 -1
  12. package/src/ui/App.tsx +19 -12
  13. package/src/ui/adapters/pluginsAdapter.ts +174 -168
  14. package/src/ui/adapters/settingsAdapter.ts +119 -116
  15. package/src/ui/adapters/skillsAdapter.ts +203 -196
  16. package/src/ui/components/CategoryHeader.tsx +9 -8
  17. package/src/ui/components/EmptyFilterState.tsx +10 -5
  18. package/src/ui/components/FlagDetailEditor.tsx +0 -0
  19. package/src/ui/components/ScopeIndicator.tsx +10 -6
  20. package/src/ui/components/ScrollableList.tsx +3 -2
  21. package/src/ui/components/SearchInput.tsx +2 -1
  22. package/src/ui/components/StyledText.tsx +5 -4
  23. package/src/ui/components/TabBar.tsx +4 -3
  24. package/src/ui/components/layout/FooterHints.tsx +37 -30
  25. package/src/ui/components/layout/Panel.tsx +6 -5
  26. package/src/ui/components/layout/ProgressBar.tsx +7 -6
  27. package/src/ui/components/layout/ScopeTabs.tsx +6 -5
  28. package/src/ui/components/layout/ScreenLayout.tsx +27 -24
  29. package/src/ui/components/layout/index.ts +3 -3
  30. package/src/ui/components/modals/ConfirmModal.tsx +12 -11
  31. package/src/ui/components/modals/InputModal.tsx +14 -6
  32. package/src/ui/components/modals/LoadingModal.tsx +6 -5
  33. package/src/ui/components/modals/MessageModal.tsx +9 -8
  34. package/src/ui/components/modals/SelectModal.tsx +11 -7
  35. package/src/ui/components/modals/VersionMismatchModal.tsx +14 -16
  36. package/src/ui/components/primitives/ActionHints.tsx +26 -26
  37. package/src/ui/components/primitives/DetailSection.tsx +13 -12
  38. package/src/ui/components/primitives/KeyValueLine.tsx +9 -8
  39. package/src/ui/components/primitives/ListCategoryRow.tsx +25 -27
  40. package/src/ui/components/primitives/MetaText.tsx +3 -3
  41. package/src/ui/components/primitives/ScopeDetail.tsx +48 -48
  42. package/src/ui/components/primitives/ScopeSquares.tsx +47 -22
  43. package/src/ui/components/primitives/SelectableRow.tsx +22 -16
  44. package/src/ui/hooks/useGitignoreModal.ts +78 -74
  45. package/src/ui/registry.ts +11 -11
  46. package/src/ui/renderers/cliToolRenderers.tsx +260 -203
  47. package/src/ui/renderers/gitignoreRenderers.tsx +43 -42
  48. package/src/ui/renderers/mcpRenderers.tsx +121 -117
  49. package/src/ui/renderers/pluginRenderers.tsx +528 -471
  50. package/src/ui/renderers/profileRenderers.tsx +346 -300
  51. package/src/ui/renderers/settingsRenderers.tsx +183 -176
  52. package/src/ui/renderers/skillRenderers.tsx +410 -326
  53. package/src/ui/screens/AliasScreen.tsx +1336 -1309
  54. package/src/ui/screens/CliToolsScreen.tsx +92 -40
  55. package/src/ui/screens/EnvVarsScreen.tsx +19 -13
  56. package/src/ui/screens/GitignoreScreen.tsx +510 -493
  57. package/src/ui/screens/McpRegistryScreen.tsx +28 -21
  58. package/src/ui/screens/McpScreen.tsx +12 -3
  59. package/src/ui/screens/PluginsScreen.tsx +17 -7
  60. package/src/ui/screens/ProfilesScreen.tsx +39 -23
  61. package/src/ui/screens/SkillsScreen.tsx +832 -688
  62. package/src/ui/theme-mode.ts +73 -0
  63. package/src/ui/theme.ts +147 -53
@@ -1,15 +1,20 @@
1
- import React from "react";
1
+ import type React from "react";
2
+ import { theme } from "../../theme.js";
2
3
 
3
4
  /** One footer hint: a key (or key group) and the action it performs. */
4
5
  export interface FooterHint {
5
- /** Key(s) for this action, e.g. ["↑", "↓"] or ["U"]. Joined for display. */
6
- keys: string[];
7
- label: string;
6
+ /** Key(s) for this action, e.g. ["↑", "↓"] or ["U"]. Joined for display. */
7
+ keys: string[];
8
+ label: string;
8
9
  }
9
10
 
10
- const KEY_BG = "#3f3f46";
11
- const KEY_FG = "#fafafa";
12
- const LABEL_FG = "gray";
11
+ // The shared chip palette, not a local one. This file used to define its own
12
+ // (`theme.colors.dim` block + terminal-foreground ink), which is why the footer
13
+ // stayed a low-contrast grey-on-grey strip after the palette was made vivid —
14
+ // the new colours simply never reached it. Anything chip-shaped uses theme.hints.
15
+ const KEY_BG = theme.hints.defaultBg;
16
+ const KEY_FG = theme.hints.fg;
17
+ const LABEL_FG = theme.colors.muted;
13
18
  const GAP = " ";
14
19
 
15
20
  /**
@@ -20,33 +25,35 @@ const GAP = " ";
20
25
  * Each key is shown in a chip so it reads as a pressable key, not prose.
21
26
  * Shared across screens so every footer looks the same.
22
27
  */
23
- export function FooterHints({ hints }: { hints: FooterHint[] }): React.ReactNode {
24
- const nodes: React.ReactNode[] = [];
25
- hints.forEach((hint, i) => {
26
- if (i > 0) {
27
- nodes.push(
28
- <span key={`gap-${i}`} fg={LABEL_FG}>
29
- {GAP}
30
- </span>,
31
- );
32
- }
33
- nodes.push(
34
- <span key={`k-${i}`} bg={KEY_BG} fg={KEY_FG}>
35
- {` ${hint.keys.join("")} `}
36
- </span>,
37
- );
38
- nodes.push(
39
- <span key={`l-${i}`} fg={LABEL_FG}>
40
- {` ${hint.label}`}
41
- </span>,
42
- );
43
- });
44
- return <text>{nodes}</text>;
28
+ export function FooterHints({
29
+ hints,
30
+ }: { hints: FooterHint[] }): React.ReactNode {
31
+ const nodes: React.ReactNode[] = [];
32
+ hints.forEach((hint, i) => {
33
+ if (i > 0) {
34
+ nodes.push(
35
+ <span key={`gap-${i}`} fg={LABEL_FG}>
36
+ {GAP}
37
+ </span>,
38
+ );
39
+ }
40
+ nodes.push(
41
+ <span key={`k-${i}`} bg={KEY_BG} fg={KEY_FG}>
42
+ {` ${hint.keys.join("")} `}
43
+ </span>,
44
+ );
45
+ nodes.push(
46
+ <span key={`l-${i}`} fg={LABEL_FG}>
47
+ {` ${hint.label}`}
48
+ </span>,
49
+ );
50
+ });
51
+ return <text fg={theme.colors.text}>{nodes}</text>;
45
52
  }
46
53
 
47
54
  /** Convenience for callers that already have a node: same as <FooterHints>. */
48
55
  export function renderFooterHints(hints: FooterHint[]): React.ReactNode {
49
- return <FooterHints hints={hints} />;
56
+ return <FooterHints hints={hints} />;
50
57
  }
51
58
 
52
59
  export default FooterHints;
@@ -1,4 +1,5 @@
1
1
  import React from "react";
2
+ import { theme, type UiColor } from "../../theme.js";
2
3
 
3
4
  interface PanelProps {
4
5
  /** Panel title */
@@ -6,9 +7,9 @@ interface PanelProps {
6
7
  /** Panel content */
7
8
  children: React.ReactNode;
8
9
  /** Border color */
9
- borderColor?: string;
10
+ borderColor?: UiColor;
10
11
  /** Title color */
11
- titleColor?: string;
12
+ titleColor?: UiColor;
12
13
  /** Panel width - number or percentage like "50%" */
13
14
  width?: number | `${number}%` | "auto";
14
15
  /** Panel height - number or percentage like "50%" */
@@ -22,14 +23,14 @@ interface PanelProps {
22
23
  export function Panel({
23
24
  title,
24
25
  children,
25
- borderColor = "#7e57c2",
26
- titleColor = "#7e57c2",
26
+ borderColor = theme.colors.accent,
27
+ titleColor = theme.colors.accent,
27
28
  width,
28
29
  height,
29
30
  flexGrow = 1,
30
31
  focused = false,
31
32
  }: PanelProps) {
32
- const activeColor = focused ? "#7e57c2" : borderColor;
33
+ const activeColor = focused ? theme.colors.accent : borderColor;
33
34
 
34
35
  return (
35
36
  <box
@@ -1,4 +1,5 @@
1
1
  import React from "react";
2
+ import { theme } from "../../theme.js";
2
3
 
3
4
  interface ProgressBarProps {
4
5
  /** Progress message */
@@ -21,9 +22,9 @@ export function ProgressBar({ message, current, total }: ProgressBarProps) {
21
22
 
22
23
  return (
23
24
  <box>
24
- <text fg="cyan">⟳</text>
25
- <text> {message} </text>
26
- <text fg="cyan">
25
+ <text fg={theme.colors.info}>⟳</text>
26
+ <text fg={theme.colors.text}> {message} </text>
27
+ <text fg={theme.colors.info}>
27
28
  [{bar}] {current}/{total}
28
29
  </text>
29
30
  </box>
@@ -33,9 +34,9 @@ export function ProgressBar({ message, current, total }: ProgressBarProps) {
33
34
  // Indeterminate progress
34
35
  return (
35
36
  <box>
36
- <text fg="cyan">⟳</text>
37
- <text> {message}</text>
38
- <text fg="gray"> ...</text>
37
+ <text fg={theme.colors.info}>⟳</text>
38
+ <text fg={theme.colors.text}> {message}</text>
39
+ <text fg={theme.colors.muted}> ...</text>
39
40
  </box>
40
41
  );
41
42
  }
@@ -1,4 +1,5 @@
1
1
  import React from "react";
2
+ import { theme } from "../../theme.js";
2
3
 
3
4
  interface ScopeTabsProps {
4
5
  /** Current scope */
@@ -21,29 +22,29 @@ export function ScopeTabs({
21
22
  {/* Project tab */}
22
23
  <box>
23
24
  {isProject ? (
24
- <text bg="cyan" fg="black">
25
+ <text bg={theme.colors.info} fg={theme.hints.fg}>
25
26
  <strong> ◆ Project </strong>
26
27
  </text>
27
28
  ) : (
28
- <text fg="gray"> ○ Project </text>
29
+ <text fg={theme.colors.muted}> ○ Project </text>
29
30
  )}
30
31
  </box>
31
32
 
32
33
  {/* Global tab */}
33
34
  <box>
34
35
  {!isProject ? (
35
- <text bg="magenta" fg="white">
36
+ <text bg={theme.colors.accent} fg={theme.hints.fg}>
36
37
  <strong> ◆ Global </strong>
37
38
  </text>
38
39
  ) : (
39
- <text fg="gray"> ○ Global </text>
40
+ <text fg={theme.colors.muted}> ○ Global </text>
40
41
  )}
41
42
  </box>
42
43
 
43
44
  {/* Toggle hint */}
44
45
  {toggleHint && (
45
46
  <box marginLeft={2}>
46
- <text fg="#666666">({toggleHint})</text>
47
+ <text fg={theme.colors.muted}>({toggleHint})</text>
47
48
  </box>
48
49
  )}
49
50
  </box>
@@ -3,6 +3,7 @@ import { useDimensions } from "../../state/DimensionsContext.js";
3
3
  import { TabBar } from "../TabBar.js";
4
4
  import type { Screen } from "../../state/types.js";
5
5
  import { FooterHints, type FooterHint } from "./FooterHints.js";
6
+ import { theme } from "../../theme.js";
6
7
 
7
8
  interface ScreenLayoutProps {
8
9
  /** Screen title (e.g., "claudeup Plugins") */
@@ -32,7 +33,7 @@ interface ScreenLayoutProps {
32
33
  detailPanel: React.ReactNode;
33
34
  }
34
35
 
35
- const HEADER_COLOR = "#7e57c2";
36
+ const HEADER_COLOR = theme.colors.accent;
36
37
 
37
38
  export function ScreenLayout({
38
39
  title,
@@ -58,7 +59,7 @@ export function ScreenLayout({
58
59
  <box flexDirection="column" height={dimensions.contentHeight}>
59
60
  {/* Line above tabs */}
60
61
  <box height={1} paddingLeft={1} paddingRight={1}>
61
- <text fg="#333333">{"─".repeat(lineWidth)}</text>
62
+ <text fg={theme.colors.dim}>{"─".repeat(lineWidth)}</text>
62
63
  </box>
63
64
 
64
65
  {/* Tab bar */}
@@ -68,15 +69,21 @@ export function ScreenLayout({
68
69
 
69
70
  {/* Line below tabs */}
70
71
  <box height={1} paddingLeft={1} paddingRight={1}>
71
- <text fg="#333333">{"─".repeat(lineWidth)}</text>
72
+ <text fg={theme.colors.dim}>{"─".repeat(lineWidth)}</text>
72
73
  </box>
73
74
 
74
75
  {/* Header — title on left, subtitle/status on right */}
75
- <box height={1} paddingLeft={1} paddingRight={1} flexDirection="row" justifyContent="space-between">
76
+ <box
77
+ height={1}
78
+ paddingLeft={1}
79
+ paddingRight={1}
80
+ flexDirection="row"
81
+ justifyContent="space-between"
82
+ >
76
83
  <text fg={HEADER_COLOR}>
77
84
  <strong>{title}</strong>
78
85
  </text>
79
- {subtitle && <text fg="gray">{subtitle}</text>}
86
+ {subtitle && <text fg={theme.colors.muted}>{subtitle}</text>}
80
87
  {!subtitle && statusLine ? statusLine : null}
81
88
  </box>
82
89
 
@@ -84,16 +91,18 @@ export function ScreenLayout({
84
91
  {hasSearchBar && (
85
92
  <box height={1} paddingLeft={1} paddingRight={1}>
86
93
  {search.isActive ? (
87
- <text>
88
- <span fg="green">{"Filter: "}</span>
89
- <span fg="white">{search.query}</span>
90
- <span bg="white" fg="black"> </span>
94
+ <text fg={theme.colors.text}>
95
+ <span fg={theme.colors.success}>{"Filter: "}</span>
96
+ <span fg={theme.colors.text}>{search.query}</span>
97
+ <span bg={theme.colors.text} fg={theme.hints.fg}>
98
+ {" "}
99
+ </span>
91
100
  </text>
92
101
  ) : (
93
- <text>
94
- <span fg="green">{"Filter: "}</span>
95
- <span fg="yellow">{search.query}</span>
96
- <span fg="gray"> (Esc to clear)</span>
102
+ <text fg={theme.colors.text}>
103
+ <span fg={theme.colors.success}>{"Filter: "}</span>
104
+ <span fg={theme.colors.warning}>{search.query}</span>
105
+ <span fg={theme.colors.muted}> (Esc to clear)</span>
97
106
  </text>
98
107
  )}
99
108
  </box>
@@ -101,7 +110,7 @@ export function ScreenLayout({
101
110
 
102
111
  {/* Separator below header */}
103
112
  <box height={1} paddingLeft={1} paddingRight={1}>
104
- <text fg="#444444">{"─".repeat(lineWidth)}</text>
113
+ <text fg={theme.colors.border}>{"─".repeat(lineWidth)}</text>
105
114
  </box>
106
115
 
107
116
  {/* Main content area */}
@@ -118,19 +127,13 @@ export function ScreenLayout({
118
127
 
119
128
  {/* Vertical separator */}
120
129
  <box flexDirection="column" width={1} height={panelHeight}>
121
- <text fg="#444444">{"│".repeat(panelHeight)}</text>
130
+ <text fg={theme.colors.border}>{"│".repeat(panelHeight)}</text>
122
131
  </box>
123
132
 
124
133
  {/* Detail panel — scrollable */}
125
134
  <box width="50%" height={panelHeight} paddingLeft={1}>
126
- <scrollbox
127
- height={panelHeight}
128
- scrollY={true}
129
- scrollX={false}
130
- >
131
- <box flexDirection="column">
132
- {detailPanel}
133
- </box>
135
+ <scrollbox height={panelHeight} scrollY={true} scrollX={false}>
136
+ <box flexDirection="column">{detailPanel}</box>
134
137
  </scrollbox>
135
138
  </box>
136
139
  </box>
@@ -140,7 +143,7 @@ export function ScreenLayout({
140
143
  {Array.isArray(footerHints) ? (
141
144
  <FooterHints hints={footerHints as FooterHint[]} />
142
145
  ) : typeof footerHints === "string" ? (
143
- <text fg="gray">{footerHints}</text>
146
+ <text fg={theme.colors.muted}>{footerHints}</text>
144
147
  ) : (
145
148
  footerHints
146
149
  )}
@@ -3,7 +3,7 @@ export { ScopeTabs } from "./ScopeTabs.js";
3
3
  export { ProgressBar } from "./ProgressBar.js";
4
4
  export { ScreenLayout } from "./ScreenLayout.js";
5
5
  export {
6
- FooterHints,
7
- renderFooterHints,
8
- type FooterHint,
6
+ FooterHints,
7
+ renderFooterHints,
8
+ type FooterHint,
9
9
  } from "./FooterHints.js";
@@ -1,5 +1,6 @@
1
1
  import React from "react";
2
2
  import { useKeyboard } from "../../hooks/useKeyboard.js";
3
+ import { theme } from "../../theme.js";
3
4
 
4
5
  interface ConfirmModalProps {
5
6
  /** Modal title */
@@ -19,9 +20,9 @@ export function ConfirmModal({
19
20
  onCancel,
20
21
  }: ConfirmModalProps) {
21
22
  useKeyboard((key) => {
22
- if (key.name === "y" || key.name === "Y") {
23
+ if (key.name === "y") {
23
24
  onConfirm();
24
- } else if (key.name === "n" || key.name === "N" || key.name === "escape") {
25
+ } else if (key.name === "n" || key.name === "escape") {
25
26
  onCancel();
26
27
  }
27
28
  });
@@ -31,8 +32,8 @@ export function ConfirmModal({
31
32
  flexDirection="column"
32
33
  border
33
34
  borderStyle="rounded"
34
- borderColor="#525252"
35
- backgroundColor="#1C1C1E"
35
+ borderColor={theme.colors.dim}
36
+ backgroundColor={theme.surface.bg}
36
37
  paddingLeft={3}
37
38
  paddingRight={3}
38
39
  paddingTop={1}
@@ -40,19 +41,19 @@ export function ConfirmModal({
40
41
  width={60}
41
42
  >
42
43
  <box marginBottom={1}>
43
- <text fg="#EDEDED">
44
+ <text fg={theme.colors.text}>
44
45
  <strong>{title}</strong>
45
46
  </text>
46
47
  </box>
47
48
  <box marginBottom={1}>
48
- <text fg="#A1A1AA">{message}</text>
49
+ <text fg={theme.colors.muted}>{message}</text>
49
50
  </box>
50
51
  <box marginTop={1} flexDirection="row">
51
- <text fg="#71717A">Press </text>
52
- <text fg="#EDEDED">Y</text>
53
- <text fg="#71717A"> to confirm • </text>
54
- <text fg="#EDEDED">N</text>
55
- <text fg="#71717A"> to cancel</text>
52
+ <text fg={theme.colors.muted}>Press </text>
53
+ <text fg={theme.colors.text}>Y</text>
54
+ <text fg={theme.colors.muted}> to confirm • </text>
55
+ <text fg={theme.colors.text}>N</text>
56
+ <text fg={theme.colors.muted}> to cancel</text>
56
57
  </box>
57
58
  </box>
58
59
  );
@@ -1,4 +1,5 @@
1
1
  import React from "react";
2
+ import { theme } from "../../theme.js";
2
3
 
3
4
  interface InputModalProps {
4
5
  /** Modal title */
@@ -24,8 +25,8 @@ export function InputModal({
24
25
  flexDirection="column"
25
26
  border
26
27
  borderStyle="rounded"
27
- borderColor="#525252"
28
- backgroundColor="#1C1C1E"
28
+ borderColor={theme.colors.dim}
29
+ backgroundColor={theme.surface.bg}
29
30
  paddingLeft={3}
30
31
  paddingRight={3}
31
32
  paddingTop={1}
@@ -33,16 +34,23 @@ export function InputModal({
33
34
  width={60}
34
35
  >
35
36
  <box marginBottom={1}>
36
- <text fg="#EDEDED">
37
+ <text fg={theme.colors.text}>
37
38
  <strong>{title}</strong>
38
39
  </text>
39
40
  </box>
40
41
 
41
42
  <box marginBottom={1}>
42
- <text fg="#A1A1AA">{label}</text>
43
+ <text fg={theme.colors.muted}>{label}</text>
43
44
  </box>
44
45
 
45
- <box border borderStyle="rounded" borderColor="#3F3F46" paddingLeft={1} paddingRight={1} width={54}>
46
+ <box
47
+ border
48
+ borderStyle="rounded"
49
+ borderColor={theme.colors.dim}
50
+ paddingLeft={1}
51
+ paddingRight={1}
52
+ width={54}
53
+ >
46
54
  <input
47
55
  value={defaultValue}
48
56
  onSubmit={onSubmit as any}
@@ -52,7 +60,7 @@ export function InputModal({
52
60
  </box>
53
61
 
54
62
  <box marginTop={1}>
55
- <text fg="#71717A">↵ to confirm • Esc to cancel</text>
63
+ <text fg={theme.colors.muted}>↵ to confirm • Esc to cancel</text>
56
64
  </box>
57
65
  </box>
58
66
  );
@@ -1,4 +1,5 @@
1
1
  import React, { useState, useEffect } from "react";
2
+ import { theme } from "../../theme.js";
2
3
 
3
4
  interface LoadingModalProps {
4
5
  /** Loading message — supports multiline (first line = status, subsequent = detail) */
@@ -27,20 +28,20 @@ export function LoadingModal({ message }: LoadingModalProps) {
27
28
  flexDirection="column"
28
29
  border
29
30
  borderStyle="rounded"
30
- borderColor="#525252"
31
- backgroundColor="#1C1C1E"
31
+ borderColor={theme.colors.dim}
32
+ backgroundColor={theme.surface.bg}
32
33
  paddingLeft={3}
33
34
  paddingRight={3}
34
35
  paddingTop={1}
35
36
  paddingBottom={1}
36
37
  >
37
38
  <box flexDirection="row">
38
- <text fg="#A1A1AA">{SPINNER_FRAMES[frame]}</text>
39
- <text fg="#EDEDED"> {mainMessage}</text>
39
+ <text fg={theme.colors.muted}>{SPINNER_FRAMES[frame]}</text>
40
+ <text fg={theme.colors.text}> {mainMessage}</text>
40
41
  </box>
41
42
  {detail ? (
42
43
  <box marginTop={0} paddingLeft={2}>
43
- <text fg="#525252">{detail}</text>
44
+ <text fg={theme.colors.dim}>{detail}</text>
44
45
  </box>
45
46
  ) : null}
46
47
  </box>
@@ -1,5 +1,6 @@
1
1
  import React from "react";
2
2
  import { useKeyboard } from "../../hooks/useKeyboard.js";
3
+ import { theme } from "../../theme.js";
3
4
 
4
5
  interface MessageModalProps {
5
6
  /** Modal title */
@@ -13,9 +14,9 @@ interface MessageModalProps {
13
14
  }
14
15
 
15
16
  const variantConfig = {
16
- info: { icon: "ℹ", color: "#60A5FA" },
17
- success: { icon: "✓", color: "#4ADE80" },
18
- error: { icon: "✗", color: "#F87171" },
17
+ info: { icon: "ℹ", color: theme.colors.link },
18
+ success: { icon: "✓", color: theme.colors.success },
19
+ error: { icon: "✗", color: theme.colors.danger },
19
20
  } as const;
20
21
 
21
22
  export function MessageModal({
@@ -36,8 +37,8 @@ export function MessageModal({
36
37
  flexDirection="column"
37
38
  border
38
39
  borderStyle="rounded"
39
- borderColor="#525252"
40
- backgroundColor="#1C1C1E"
40
+ borderColor={theme.colors.dim}
41
+ backgroundColor={theme.surface.bg}
41
42
  paddingLeft={3}
42
43
  paddingRight={3}
43
44
  paddingTop={1}
@@ -46,17 +47,17 @@ export function MessageModal({
46
47
  >
47
48
  <box marginBottom={1}>
48
49
  <text fg={config.color}>{config.icon} </text>
49
- <text fg="#EDEDED">
50
+ <text fg={theme.colors.text}>
50
51
  <strong>{title}</strong>
51
52
  </text>
52
53
  </box>
53
54
 
54
55
  <box marginBottom={1}>
55
- <text fg="#A1A1AA">{message}</text>
56
+ <text fg={theme.colors.muted}>{message}</text>
56
57
  </box>
57
58
 
58
59
  <box marginTop={1}>
59
- <text fg="#71717A">Press any key to continue</text>
60
+ <text fg={theme.colors.muted}>Press any key to continue</text>
60
61
  </box>
61
62
  </box>
62
63
  );
@@ -1,6 +1,7 @@
1
1
  import React from "react";
2
2
  import type { SelectOption } from "../../state/types.js";
3
3
  import { useDimensions } from "../../state/DimensionsContext.js";
4
+ import { theme } from "../../theme.js";
4
5
 
5
6
  interface SelectModalProps {
6
7
  /** Modal title */
@@ -44,8 +45,8 @@ export function SelectModal({
44
45
  flexDirection="column"
45
46
  border
46
47
  borderStyle="rounded"
47
- borderColor="#525252"
48
- backgroundColor="#1C1C1E"
48
+ borderColor={theme.colors.dim}
49
+ backgroundColor={theme.surface.bg}
49
50
  paddingLeft={3}
50
51
  paddingRight={3}
51
52
  paddingTop={1}
@@ -53,21 +54,24 @@ export function SelectModal({
53
54
  width={width}
54
55
  >
55
56
  <box marginBottom={1}>
56
- <text fg="#EDEDED">
57
+ <text fg={theme.colors.text}>
57
58
  <strong>{title}</strong>
58
59
  </text>
59
60
  </box>
60
61
 
61
62
  <box marginBottom={1}>
62
- <text fg="#A1A1AA">{message}</text>
63
+ <text fg={theme.colors.muted}>{message}</text>
63
64
  </box>
64
65
 
65
66
  <box flexDirection="column" paddingLeft={1}>
66
67
  {options.map((option, idx) => {
67
68
  const isSelected = idx === selectedIndex;
68
69
  return (
69
- <text key={option.value} fg={isSelected ? "#F4F4F5" : "#A1A1AA"}>
70
- <span fg={isSelected ? "#F4F4F5" : "#71717A"}>
70
+ <text
71
+ key={option.value}
72
+ fg={isSelected ? theme.colors.text : theme.colors.muted}
73
+ >
74
+ <span fg={isSelected ? theme.colors.text : theme.colors.muted}>
71
75
  {isSelected ? "❯ " : " "}
72
76
  </span>
73
77
  {isSelected ? <strong>{option.label}</strong> : option.label}
@@ -77,7 +81,7 @@ export function SelectModal({
77
81
  </box>
78
82
 
79
83
  <box marginTop={1}>
80
- <text fg="#71717A">↑↓ Select • ↵ Confirm • Esc Cancel</text>
84
+ <text fg={theme.colors.muted}>↑↓ Select • ↵ Confirm • Esc Cancel</text>
81
85
  </box>
82
86
  </box>
83
87
  );
@@ -1,5 +1,6 @@
1
1
  import React from "react";
2
2
  import type { VersionMismatchInfo } from "../../../services/plugin-version-check.js";
3
+ import { theme } from "../../theme.js";
3
4
 
4
5
  interface VersionMismatchModalProps {
5
6
  /** List of version mismatches detected */
@@ -8,15 +9,15 @@ interface VersionMismatchModalProps {
8
9
  defaultIndex?: number;
9
10
  }
10
11
 
11
- const COLOR_BG = "#1C1C1E";
12
- const COLOR_BORDER = "#525252";
13
- const COLOR_TITLE = "#EDEDED";
14
- const COLOR_MUTED = "#A1A1AA";
15
- const COLOR_DIM = "#71717A";
16
- const COLOR_WARN = "#FBBF24";
17
- const COLOR_BAD = "#F87171";
18
- const COLOR_GOOD = "#4ADE80";
19
- const COLOR_ACCENT = "#F4F4F5";
12
+ const COLOR_BG = theme.surface.bg;
13
+ const COLOR_BORDER = theme.colors.dim;
14
+ const COLOR_TITLE = theme.colors.text;
15
+ const COLOR_MUTED = theme.colors.muted;
16
+ const COLOR_DIM = theme.colors.muted;
17
+ const COLOR_WARN = theme.colors.warning;
18
+ const COLOR_BAD = theme.colors.danger;
19
+ const COLOR_GOOD = theme.colors.success;
20
+ const COLOR_ACCENT = theme.colors.text;
20
21
 
21
22
  /** Truncate a plugin name to fit the column width */
22
23
  function shortName(pluginId: string, max: number): string {
@@ -80,7 +81,8 @@ export function VersionMismatchModal({
80
81
  </strong>
81
82
  </text>
82
83
  <text fg={COLOR_BORDER}>
83
- {"─".repeat(NAME_W - 1)} {"─".repeat(VER_W - 1)} {"".repeat(VER_W - 1)}
84
+ {"─".repeat(NAME_W - 1)} {"─".repeat(VER_W - 1)}{" "}
85
+ {"─".repeat(VER_W - 1)}
84
86
  </text>
85
87
 
86
88
  {/* Table rows */}
@@ -107,9 +109,7 @@ export function VersionMismatchModal({
107
109
 
108
110
  {/* Options */}
109
111
  <box flexDirection="column" paddingLeft={1}>
110
- <text
111
- fg={selectedIndex === 0 ? COLOR_ACCENT : COLOR_MUTED}
112
- >
112
+ <text fg={selectedIndex === 0 ? COLOR_ACCENT : COLOR_MUTED}>
113
113
  <span fg={selectedIndex === 0 ? COLOR_ACCENT : COLOR_DIM}>
114
114
  {selectedIndex === 0 ? "❯ " : " "}
115
115
  </span>
@@ -119,9 +119,7 @@ export function VersionMismatchModal({
119
119
  "Fix all projects"
120
120
  )}
121
121
  </text>
122
- <text
123
- fg={selectedIndex === 1 ? COLOR_ACCENT : COLOR_MUTED}
124
- >
122
+ <text fg={selectedIndex === 1 ? COLOR_ACCENT : COLOR_MUTED}>
125
123
  <span fg={selectedIndex === 1 ? COLOR_ACCENT : COLOR_DIM}>
126
124
  {selectedIndex === 1 ? "❯ " : " "}
127
125
  </span>