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,227 +1,284 @@
1
1
  import React from "react";
2
2
  import type { CliTool } from "../../data/cli-tools.js";
3
- import { theme } from "../theme.js";
3
+ import { theme, type UiColor } from "../theme.js";
4
4
 
5
5
  // ─── Status type ───────────────────────────────────────────────────────────────
6
6
 
7
- export type InstallMethod = "npm" | "bun" | "pnpm" | "yarn" | "brew" | "pip" | "unknown";
7
+ export type InstallMethod =
8
+ | "npm"
9
+ | "bun"
10
+ | "pnpm"
11
+ | "yarn"
12
+ | "brew"
13
+ | "pip"
14
+ | "unknown";
8
15
 
9
16
  export interface CliToolStatus {
10
- tool: CliTool;
11
- installed: boolean;
12
- installedVersion?: string;
13
- latestVersion?: string;
14
- hasUpdate?: boolean;
15
- checking: boolean;
16
- installMethod?: InstallMethod;
17
- allMethods?: InstallMethod[];
18
- updateCommand?: string;
19
- brewFormula?: string;
17
+ tool: CliTool;
18
+ installed: boolean;
19
+ installedVersion?: string;
20
+ latestVersion?: string;
21
+ hasUpdate?: boolean;
22
+ checking: boolean;
23
+ installMethod?: InstallMethod;
24
+ allMethods?: InstallMethod[];
25
+ updateCommand?: string;
26
+ brewFormula?: string;
20
27
  }
21
28
 
22
29
  // ─── Helpers ────────────────────────────────────────────────────────────────
23
30
 
24
- function getUninstallHint(tool: CliTool, method: InstallMethod, brewFormula?: string): string {
25
- switch (method) {
26
- case "bun": return `bun remove -g ${tool.packageName}`;
27
- case "npm": return `npm uninstall -g ${tool.packageName}`;
28
- case "pnpm": return `pnpm remove -g ${tool.packageName}`;
29
- case "yarn": return `yarn global remove ${tool.packageName}`;
30
- case "brew": return `brew uninstall ${brewFormula || tool.name}`;
31
- case "pip": return `pip uninstall ${tool.packageName}`;
32
- default: return "";
33
- }
31
+ function getUninstallHint(
32
+ tool: CliTool,
33
+ method: InstallMethod,
34
+ brewFormula?: string,
35
+ ): string {
36
+ switch (method) {
37
+ case "bun":
38
+ return `bun remove -g ${tool.packageName}`;
39
+ case "npm":
40
+ return `npm uninstall -g ${tool.packageName}`;
41
+ case "pnpm":
42
+ return `pnpm remove -g ${tool.packageName}`;
43
+ case "yarn":
44
+ return `yarn global remove ${tool.packageName}`;
45
+ case "brew":
46
+ return `brew uninstall ${brewFormula || tool.name}`;
47
+ case "pip":
48
+ return `pip uninstall ${tool.packageName}`;
49
+ default:
50
+ return "";
51
+ }
34
52
  }
35
53
 
36
54
  // ─── Row renderer ──────────────────────────────────────────────────────────────
37
55
 
38
56
  export function renderCliToolRow(
39
- status: CliToolStatus,
40
- _index: number,
41
- isSelected: boolean,
57
+ status: CliToolStatus,
58
+ _index: number,
59
+ isSelected: boolean,
42
60
  ): React.ReactNode {
43
- const { tool, installed, installedVersion, hasUpdate, checking, allMethods } = status;
44
- const hasConflict = allMethods && allMethods.length > 1;
45
-
46
- let icon: string;
47
- let iconColor: string;
48
-
49
- if (!installed) {
50
- icon = "○";
51
- iconColor = theme.colors.muted;
52
- } else if (hasConflict) {
53
- icon = "!";
54
- iconColor = theme.colors.danger;
55
- } else if (hasUpdate) {
56
- icon = "*";
57
- iconColor = theme.colors.warning;
58
- } else {
59
- icon = "●";
60
- iconColor = theme.colors.success;
61
- }
62
-
63
- const versionText = installedVersion ? ` v${installedVersion}` : "";
64
- const methodTag = installed && allMethods?.length
65
- ? ` ${allMethods.join("+")}`
66
- : "";
67
-
68
- if (isSelected) {
69
- return (
70
- <text bg={theme.selection.bg} fg={theme.selection.fg}>
71
- {" "}{icon} {tool.displayName}{versionText}{methodTag}
72
- {checking ? " ..." : ""}{" "}
73
- </text>
74
- );
75
- }
76
-
77
- return (
78
- <text>
79
- <span fg={iconColor}> {icon}</span>
80
- <span fg={theme.colors.text}> {tool.displayName}</span>
81
- {versionText ? <span fg={theme.colors.success}>{versionText}</span> : null}
82
- {methodTag ? <span fg={hasConflict ? theme.colors.danger : theme.colors.dim}>{methodTag}</span> : null}
83
- {checking ? <span fg={theme.colors.muted}>{" ..."}</span> : null}
84
- </text>
85
- );
61
+ const { tool, installed, installedVersion, hasUpdate, checking, allMethods } =
62
+ status;
63
+ const hasConflict = allMethods && allMethods.length > 1;
64
+
65
+ let icon: string;
66
+ let iconColor: UiColor;
67
+
68
+ if (!installed) {
69
+ icon = "○";
70
+ iconColor = theme.colors.muted;
71
+ } else if (hasConflict) {
72
+ icon = "!";
73
+ iconColor = theme.colors.danger;
74
+ } else if (hasUpdate) {
75
+ icon = "*";
76
+ iconColor = theme.colors.warning;
77
+ } else {
78
+ icon = "●";
79
+ iconColor = theme.colors.success;
80
+ }
81
+
82
+ const versionText = installedVersion ? ` v${installedVersion}` : "";
83
+ const methodTag =
84
+ installed && allMethods?.length ? ` ${allMethods.join("+")}` : "";
85
+
86
+ if (isSelected) {
87
+ return (
88
+ <text bg={theme.selection.bg} fg={theme.selection.fg}>
89
+ {" "}
90
+ {icon} {tool.displayName}
91
+ {versionText}
92
+ {methodTag}
93
+ {checking ? " ..." : ""}{" "}
94
+ </text>
95
+ );
96
+ }
97
+
98
+ return (
99
+ <text fg={theme.colors.text}>
100
+ <span fg={iconColor}> {icon}</span>
101
+ <span fg={theme.colors.text}> {tool.displayName}</span>
102
+ {versionText ? (
103
+ <span fg={theme.colors.success}>{versionText}</span>
104
+ ) : null}
105
+ {methodTag ? (
106
+ <span fg={hasConflict ? theme.colors.danger : theme.colors.dim}>
107
+ {methodTag}
108
+ </span>
109
+ ) : null}
110
+ {checking ? <span fg={theme.colors.muted}>{" ..."}</span> : null}
111
+ </text>
112
+ );
86
113
  }
87
114
 
88
115
  // ─── Detail renderer ───────────────────────────────────────────────────────────
89
116
 
90
117
  export function renderCliToolDetail(
91
- status: CliToolStatus | undefined,
118
+ status: CliToolStatus | undefined,
92
119
  ): React.ReactNode {
93
- if (!status) {
94
- return (
95
- <box
96
- flexDirection="column"
97
- alignItems="center"
98
- justifyContent="center"
99
- flexGrow={1}
100
- >
101
- <text fg={theme.colors.muted}>Select a tool to see details</text>
102
- </box>
103
- );
104
- }
105
-
106
- const { tool, installed, installedVersion, latestVersion, hasUpdate, checking, installMethod, allMethods, updateCommand, brewFormula } =
107
- status;
108
-
109
- const hasConflict = allMethods && allMethods.length > 1;
110
-
111
- return (
112
- <box flexDirection="column">
113
- <box marginBottom={1}>
114
- <text fg={theme.colors.info}>
115
- <strong>{"⚙ "}{tool.displayName}</strong>
116
- </text>
117
- {hasUpdate ? <text fg={theme.colors.warning}> ⬆</text> : null}
118
- {hasConflict ? <text fg={theme.colors.danger}> !</text> : null}
119
- </box>
120
-
121
- <text fg={theme.colors.muted}>{tool.description}</text>
122
-
123
- <box marginTop={1} flexDirection="column">
124
- <box>
125
- <text fg={theme.colors.muted}>{"Status "}</text>
126
- {!installed ? (
127
- <text fg={theme.colors.muted}>{"○ Not installed"}</text>
128
- ) : checking ? (
129
- <text fg={theme.colors.success}>{"● Checking..."}</text>
130
- ) : hasUpdate ? (
131
- <text fg={theme.colors.warning}>{"● Update available"}</text>
132
- ) : (
133
- <text fg={theme.colors.success}>{"● Up to date"}</text>
134
- )}
135
- </box>
136
- {installedVersion ? (
137
- <box>
138
- <text fg={theme.colors.muted}>{"Version "}</text>
139
- <text>
140
- <span fg={theme.colors.success}>v{installedVersion}</span>
141
- {latestVersion && hasUpdate ? (
142
- <span fg={theme.colors.warning}> → v{latestVersion}</span>
143
- ) : null}
144
- </text>
145
- </box>
146
- ) : latestVersion ? (
147
- <box>
148
- <text fg={theme.colors.muted}>{"Latest "}</text>
149
- <text fg={theme.colors.text}>v{latestVersion}</text>
150
- </box>
151
- ) : null}
152
- {installed && updateCommand ? (
153
- <box>
154
- <text fg={theme.colors.muted}>{"Update "}</text>
155
- <text fg={theme.colors.accent}>{updateCommand}</text>
156
- </box>
157
- ) : !installed ? (
158
- <box>
159
- <text fg={theme.colors.muted}>{"Install "}</text>
160
- <text fg={theme.colors.accent}>{tool.installCommand}</text>
161
- </box>
162
- ) : null}
163
- <box>
164
- <text fg={theme.colors.muted}>{"Website "}</text>
165
- <text fg={theme.colors.link}>{tool.website}</text>
166
- </box>
167
- </box>
168
-
169
- {/* Conflict warning */}
170
- {hasConflict ? (
171
- <box marginTop={1} flexDirection="column">
172
- <box>
173
- <text bg={theme.colors.danger} fg="white">
174
- <strong>{" "}Conflict: installed via {allMethods.join(" + ")}{" "}</strong>
175
- </text>
176
- </box>
177
- <box marginTop={1}>
178
- <text fg={theme.colors.muted}>
179
- Multiple installs can cause version mismatches.
180
- {"\n"}Keep one, remove the rest:
181
- </text>
182
- </box>
183
- {allMethods.map((method, i) => (
184
- <box key={method}>
185
- <text>
186
- <span fg={i === 0 ? theme.colors.success : theme.colors.danger}>
187
- {i === 0 ? " ● keep " : " ○ remove "}
188
- </span>
189
- <span fg={theme.colors.warning}>{method}</span>
190
- {i > 0 ? (
191
- <span fg={theme.colors.dim}>{` ${getUninstallHint(tool, method, brewFormula)}`}</span>
192
- ) : (
193
- <span fg={theme.colors.dim}> (active in PATH)</span>
194
- )}
195
- </text>
196
- </box>
197
- ))}
198
- <box marginTop={1}>
199
- <text bg={theme.colors.danger} fg="white">{" "}c{" "}</text>
200
- <text fg={theme.colors.muted}> Resolve — pick which to keep</text>
201
- </box>
202
- </box>
203
- ) : null}
204
-
205
- {/* Actions */}
206
- <box marginTop={2} flexDirection="column">
207
- {!installed ? (
208
- <box>
209
- <text bg={theme.colors.success} fg="black">{" "}Enter{" "}</text>
210
- <text fg={theme.colors.muted}> Install</text>
211
- <text fg={theme.colors.dim}> {tool.installCommand}</text>
212
- </box>
213
- ) : hasUpdate ? (
214
- <box>
215
- <text bg={theme.colors.warning} fg="black">{" "}Enter{" "}</text>
216
- <text fg={theme.colors.muted}> Update to v{latestVersion}</text>
217
- </box>
218
- ) : (
219
- <box>
220
- <text bg={theme.colors.muted} fg="white">{" "}Enter{" "}</text>
221
- <text fg={theme.colors.muted}> Reinstall</text>
222
- </box>
223
- )}
224
- </box>
225
- </box>
226
- );
120
+ if (!status) {
121
+ return (
122
+ <box
123
+ flexDirection="column"
124
+ alignItems="center"
125
+ justifyContent="center"
126
+ flexGrow={1}
127
+ >
128
+ <text fg={theme.colors.muted}>Select a tool to see details</text>
129
+ </box>
130
+ );
131
+ }
132
+
133
+ const {
134
+ tool,
135
+ installed,
136
+ installedVersion,
137
+ latestVersion,
138
+ hasUpdate,
139
+ checking,
140
+ installMethod,
141
+ allMethods,
142
+ updateCommand,
143
+ brewFormula,
144
+ } = status;
145
+
146
+ const hasConflict = allMethods && allMethods.length > 1;
147
+
148
+ return (
149
+ <box flexDirection="column">
150
+ <box marginBottom={1}>
151
+ <text fg={theme.colors.info}>
152
+ <strong>
153
+ {"⚙ "}
154
+ {tool.displayName}
155
+ </strong>
156
+ </text>
157
+ {hasUpdate ? <text fg={theme.colors.warning}> ⬆</text> : null}
158
+ {hasConflict ? <text fg={theme.colors.danger}> !</text> : null}
159
+ </box>
160
+
161
+ <text fg={theme.colors.muted}>{tool.description}</text>
162
+
163
+ <box marginTop={1} flexDirection="column">
164
+ <box>
165
+ <text fg={theme.colors.muted}>{"Status "}</text>
166
+ {!installed ? (
167
+ <text fg={theme.colors.muted}>{"○ Not installed"}</text>
168
+ ) : checking ? (
169
+ <text fg={theme.colors.success}>{"● Checking..."}</text>
170
+ ) : hasUpdate ? (
171
+ <text fg={theme.colors.warning}>{"● Update available"}</text>
172
+ ) : (
173
+ <text fg={theme.colors.success}>{"● Up to date"}</text>
174
+ )}
175
+ </box>
176
+ {installedVersion ? (
177
+ <box>
178
+ <text fg={theme.colors.muted}>{"Version "}</text>
179
+ <text fg={theme.colors.text}>
180
+ <span fg={theme.colors.success}>v{installedVersion}</span>
181
+ {latestVersion && hasUpdate ? (
182
+ <span fg={theme.colors.warning}> → v{latestVersion}</span>
183
+ ) : null}
184
+ </text>
185
+ </box>
186
+ ) : latestVersion ? (
187
+ <box>
188
+ <text fg={theme.colors.muted}>{"Latest "}</text>
189
+ <text fg={theme.colors.text}>v{latestVersion}</text>
190
+ </box>
191
+ ) : null}
192
+ {installed && updateCommand ? (
193
+ <box>
194
+ <text fg={theme.colors.muted}>{"Update "}</text>
195
+ <text fg={theme.colors.accent}>{updateCommand}</text>
196
+ </box>
197
+ ) : !installed ? (
198
+ <box>
199
+ <text fg={theme.colors.muted}>{"Install "}</text>
200
+ <text fg={theme.colors.accent}>{tool.installCommand}</text>
201
+ </box>
202
+ ) : null}
203
+ <box>
204
+ <text fg={theme.colors.muted}>{"Website "}</text>
205
+ <text fg={theme.colors.link}>{tool.website}</text>
206
+ </box>
207
+ </box>
208
+
209
+ {/* Conflict warning */}
210
+ {hasConflict ? (
211
+ <box marginTop={1} flexDirection="column">
212
+ <box>
213
+ <text bg={theme.colors.danger} fg={theme.hints.fg}>
214
+ <strong>
215
+ {" "}
216
+ Conflict: installed via {allMethods.join(" + ")}{" "}
217
+ </strong>
218
+ </text>
219
+ </box>
220
+ <box marginTop={1}>
221
+ <text fg={theme.colors.muted}>
222
+ Multiple installs can cause version mismatches.
223
+ {"\n"}Keep one, remove the rest:
224
+ </text>
225
+ </box>
226
+ {allMethods.map((method, i) => (
227
+ <box key={method}>
228
+ <text fg={theme.colors.text}>
229
+ <span fg={i === 0 ? theme.colors.success : theme.colors.danger}>
230
+ {i === 0 ? " ● keep " : " ○ remove "}
231
+ </span>
232
+ <span fg={theme.colors.warning}>{method}</span>
233
+ {i > 0 ? (
234
+ <span
235
+ fg={theme.colors.dim}
236
+ >{` ${getUninstallHint(tool, method, brewFormula)}`}</span>
237
+ ) : (
238
+ <span fg={theme.colors.dim}> (active in PATH)</span>
239
+ )}
240
+ </text>
241
+ </box>
242
+ ))}
243
+ <box marginTop={1}>
244
+ <text bg={theme.colors.danger} fg={theme.hints.fg}>
245
+ {" "}
246
+ c{" "}
247
+ </text>
248
+ <text fg={theme.colors.muted}> Resolve — pick which to keep</text>
249
+ </box>
250
+ </box>
251
+ ) : null}
252
+
253
+ {/* Actions */}
254
+ <box marginTop={2} flexDirection="column">
255
+ {!installed ? (
256
+ <box>
257
+ <text bg={theme.colors.success} fg={theme.hints.fg}>
258
+ {" "}
259
+ Enter{" "}
260
+ </text>
261
+ <text fg={theme.colors.muted}> Install</text>
262
+ <text fg={theme.colors.dim}> {tool.installCommand}</text>
263
+ </box>
264
+ ) : hasUpdate ? (
265
+ <box>
266
+ <text bg={theme.colors.warning} fg={theme.hints.fg}>
267
+ {" "}
268
+ Enter{" "}
269
+ </text>
270
+ <text fg={theme.colors.muted}> Update to v{latestVersion}</text>
271
+ </box>
272
+ ) : (
273
+ <box>
274
+ <text bg={theme.colors.muted} fg={theme.hints.fg}>
275
+ {" "}
276
+ Enter{" "}
277
+ </text>
278
+ <text fg={theme.colors.muted}> Reinstall</text>
279
+ </box>
280
+ )}
281
+ </box>
282
+ </box>
283
+ );
227
284
  }
@@ -1,13 +1,14 @@
1
1
  import React from "react";
2
2
  import type { ResolvedRule, Violation } from "../../types/gitignore.js";
3
+ import { theme } from "../theme.js";
3
4
 
4
5
  /**
5
6
  * A unified row for the Gitignore screen — represents one resolved rule,
6
7
  * annotated with every violation it has against the working tree.
7
8
  */
8
9
  export interface RuleRow {
9
- rule: ResolvedRule;
10
- violations: Violation[];
10
+ rule: ResolvedRule;
11
+ violations: Violation[];
11
12
  }
12
13
 
13
14
  // Two color channels that never collide:
@@ -15,17 +16,17 @@ export interface RuleRow {
15
16
  // reddish = ignored/dropped from git. Same on clean AND violation rows.
16
17
  // • Filename color = severity: dim gray when fine, red when it's a violation.
17
18
  // No row background tints — red is reserved entirely for "needs attention".
18
- const TRACK_FG = "#5fd787"; // green — item ends up tracked by git
19
- const IGNORE_FG = "#d78787"; // reddish — item ends up ignored / dropped
20
- const VIOLATION_PATH_FG = "#ff5f5f"; // bright red filename = look here
21
- const CLEAN_PATH_FG = "gray";
22
- const SELECTED_BG = "#2a2a3a"; // faint slate highlight for the cursor row
19
+ const TRACK_FG = theme.colors.success; // green — item ends up tracked by git
20
+ const IGNORE_FG = theme.colors.danger; // reddish — item ends up ignored / dropped
21
+ const VIOLATION_PATH_FG = theme.colors.danger; // bright red filename = look here
22
+ const CLEAN_PATH_FG = theme.colors.muted;
23
+ const SELECTED_BG = theme.surface.bg; // faint slate highlight for the cursor row
23
24
 
24
25
  // Violation badges are solid chips so the required direction reads at a glance:
25
26
  // green chip = will be tracked, red chip = will be ignored/dropped.
26
- const TRACK_CHIP_BG = "#1f5132";
27
- const IGNORE_CHIP_BG = "#5a2323";
28
- const CHIP_FG = "white";
27
+ const TRACK_CHIP_BG = theme.colors.success;
28
+ const IGNORE_CHIP_BG = theme.colors.danger;
29
+ const CHIP_FG = theme.colors.text;
29
30
 
30
31
  // The only marker is the cursor: ▶ on the selected row, blank otherwise.
31
32
  // Action (track/ignore) is read from the badge word and, for clean items,
@@ -38,42 +39,42 @@ const MARKER_NONE = " ";
38
39
  const MAX_PATH_LEN = 22;
39
40
 
40
41
  function truncatePath(p: string): string {
41
- return p.length > MAX_PATH_LEN ? p.slice(0, MAX_PATH_LEN - 1) + "…" : p;
42
+ return p.length > MAX_PATH_LEN ? p.slice(0, MAX_PATH_LEN - 1) + "…" : p;
42
43
  }
43
44
 
44
45
  export function renderRuleRow(
45
- row: RuleRow,
46
- isSelected: boolean,
46
+ row: RuleRow,
47
+ isSelected: boolean,
47
48
  ): React.ReactNode {
48
- const { rule, violations } = row;
49
- const path = truncatePath(rule.pattern);
50
- const key = `${rule.action}:${rule.pattern}`;
51
- // Badge text padded to a fixed width so paths line up in a column.
52
- const badge = rule.action === "ignore" ? "ignore" : "track ";
53
- const badgeFg = rule.action === "ignore" ? IGNORE_FG : TRACK_FG;
54
- const suffix = violations.length > 1 ? ` +${violations.length - 1}` : "";
55
- const rowBg = isSelected ? SELECTED_BG : undefined;
56
- const marker = isSelected ? MARKER_SELECTED : MARKER_NONE;
57
- const isViolation = violations.length > 0;
49
+ const { rule, violations } = row;
50
+ const path = truncatePath(rule.pattern);
51
+ const key = `${rule.action}:${rule.pattern}`;
52
+ // Badge text padded to a fixed width so paths line up in a column.
53
+ const badge = rule.action === "ignore" ? "ignore" : "track ";
54
+ const badgeFg = rule.action === "ignore" ? IGNORE_FG : TRACK_FG;
55
+ const suffix = violations.length > 1 ? ` +${violations.length - 1}` : "";
56
+ const rowBg = isSelected ? SELECTED_BG : undefined;
57
+ const marker = isSelected ? MARKER_SELECTED : MARKER_NONE;
58
+ const isViolation = violations.length > 0;
58
59
 
59
- if (isViolation) {
60
- // Solid badge chip + red filename. The chip color is the fix direction.
61
- const chipBg = rule.action === "ignore" ? IGNORE_CHIP_BG : TRACK_CHIP_BG;
62
- return (
63
- <text key={key} bg={rowBg}>
64
- <span fg={VIOLATION_PATH_FG}>{` ${marker} `}</span>
65
- <span bg={chipBg} fg={CHIP_FG}>{` ${badge} `}</span>
66
- <span fg={VIOLATION_PATH_FG}>{` ${path}${suffix}`}</span>
67
- </text>
68
- );
69
- }
60
+ if (isViolation) {
61
+ // Solid badge chip + red filename. The chip color is the fix direction.
62
+ const chipBg = rule.action === "ignore" ? IGNORE_CHIP_BG : TRACK_CHIP_BG;
63
+ return (
64
+ <text key={key} bg={rowBg}>
65
+ <span fg={VIOLATION_PATH_FG}>{` ${marker} `}</span>
66
+ <span bg={chipBg} fg={CHIP_FG}>{` ${badge} `}</span>
67
+ <span fg={VIOLATION_PATH_FG}>{` ${path}${suffix}`}</span>
68
+ </text>
69
+ );
70
+ }
70
71
 
71
- // Clean row: flat colored badge word, dim path, no chip.
72
- return (
73
- <text key={key} bg={rowBg}>
74
- <span fg={badgeFg}>{` ${marker} `}</span>
75
- <span fg={badgeFg}>{badge}</span>
76
- <span fg={CLEAN_PATH_FG}>{` ${path}`}</span>
77
- </text>
78
- );
72
+ // Clean row: flat colored badge word, dim path, no chip.
73
+ return (
74
+ <text key={key} bg={rowBg}>
75
+ <span fg={badgeFg}>{` ${marker} `}</span>
76
+ <span fg={badgeFg}>{badge}</span>
77
+ <span fg={CLEAN_PATH_FG}>{` ${path}`}</span>
78
+ </text>
79
+ );
79
80
  }