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.
- package/package.json +4 -4
- package/src/__tests__/git-worktree.test.ts +108 -0
- package/src/__tests__/scope-squares.test.tsx +165 -0
- package/src/__tests__/theme-adaptive-colors.test.ts +307 -0
- package/src/__tests__/uppercase-keybindings.test.ts +101 -0
- package/src/__tests__/worktree-registry-resolution.test.ts +107 -0
- package/src/main.tsx +21 -5
- package/src/opentui.d.ts +21 -12
- package/src/services/claude-settings.ts +37 -7
- package/src/services/git-worktree.ts +129 -0
- package/src/services/plugin-manager.ts +10 -1
- package/src/ui/App.tsx +19 -12
- package/src/ui/adapters/pluginsAdapter.ts +174 -168
- package/src/ui/adapters/settingsAdapter.ts +119 -116
- package/src/ui/adapters/skillsAdapter.ts +203 -196
- package/src/ui/components/CategoryHeader.tsx +9 -8
- package/src/ui/components/EmptyFilterState.tsx +10 -5
- package/src/ui/components/FlagDetailEditor.tsx +0 -0
- package/src/ui/components/ScopeIndicator.tsx +10 -6
- package/src/ui/components/ScrollableList.tsx +3 -2
- package/src/ui/components/SearchInput.tsx +2 -1
- package/src/ui/components/StyledText.tsx +5 -4
- package/src/ui/components/TabBar.tsx +4 -3
- package/src/ui/components/layout/FooterHints.tsx +37 -30
- package/src/ui/components/layout/Panel.tsx +6 -5
- package/src/ui/components/layout/ProgressBar.tsx +7 -6
- package/src/ui/components/layout/ScopeTabs.tsx +6 -5
- package/src/ui/components/layout/ScreenLayout.tsx +27 -24
- package/src/ui/components/layout/index.ts +3 -3
- package/src/ui/components/modals/ConfirmModal.tsx +12 -11
- package/src/ui/components/modals/InputModal.tsx +14 -6
- package/src/ui/components/modals/LoadingModal.tsx +6 -5
- package/src/ui/components/modals/MessageModal.tsx +9 -8
- package/src/ui/components/modals/SelectModal.tsx +11 -7
- package/src/ui/components/modals/VersionMismatchModal.tsx +14 -16
- package/src/ui/components/primitives/ActionHints.tsx +26 -26
- package/src/ui/components/primitives/DetailSection.tsx +13 -12
- package/src/ui/components/primitives/KeyValueLine.tsx +9 -8
- package/src/ui/components/primitives/ListCategoryRow.tsx +25 -27
- package/src/ui/components/primitives/MetaText.tsx +3 -3
- package/src/ui/components/primitives/ScopeDetail.tsx +48 -48
- package/src/ui/components/primitives/ScopeSquares.tsx +47 -22
- package/src/ui/components/primitives/SelectableRow.tsx +22 -16
- package/src/ui/hooks/useGitignoreModal.ts +78 -74
- package/src/ui/registry.ts +11 -11
- package/src/ui/renderers/cliToolRenderers.tsx +260 -203
- package/src/ui/renderers/gitignoreRenderers.tsx +43 -42
- package/src/ui/renderers/mcpRenderers.tsx +121 -117
- package/src/ui/renderers/pluginRenderers.tsx +528 -471
- package/src/ui/renderers/profileRenderers.tsx +346 -300
- package/src/ui/renderers/settingsRenderers.tsx +183 -176
- package/src/ui/renderers/skillRenderers.tsx +410 -326
- package/src/ui/screens/AliasScreen.tsx +1336 -1309
- package/src/ui/screens/CliToolsScreen.tsx +92 -40
- package/src/ui/screens/EnvVarsScreen.tsx +19 -13
- package/src/ui/screens/GitignoreScreen.tsx +510 -493
- package/src/ui/screens/McpRegistryScreen.tsx +28 -21
- package/src/ui/screens/McpScreen.tsx +12 -3
- package/src/ui/screens/PluginsScreen.tsx +17 -7
- package/src/ui/screens/ProfilesScreen.tsx +39 -23
- package/src/ui/screens/SkillsScreen.tsx +832 -688
- package/src/ui/theme-mode.ts +73 -0
- 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 =
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
57
|
+
status: CliToolStatus,
|
|
58
|
+
_index: number,
|
|
59
|
+
isSelected: boolean,
|
|
42
60
|
): React.ReactNode {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
118
|
+
status: CliToolStatus | undefined,
|
|
92
119
|
): React.ReactNode {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
-
|
|
10
|
-
|
|
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 =
|
|
19
|
-
const IGNORE_FG =
|
|
20
|
-
const VIOLATION_PATH_FG =
|
|
21
|
-
const CLEAN_PATH_FG =
|
|
22
|
-
const SELECTED_BG =
|
|
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 =
|
|
27
|
-
const IGNORE_CHIP_BG =
|
|
28
|
-
const CHIP_FG =
|
|
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
|
-
|
|
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
|
-
|
|
46
|
-
|
|
46
|
+
row: RuleRow,
|
|
47
|
+
isSelected: boolean,
|
|
47
48
|
): React.ReactNode {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
}
|