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
|
@@ -3,47 +3,50 @@ import type { SkillInfo, SkillSetInfo } from "../../types/index.js";
|
|
|
3
3
|
// ─── Item types ───────────────────────────────────────────────────────────────
|
|
4
4
|
|
|
5
5
|
export interface SkillCategoryItem {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
id: string;
|
|
7
|
+
kind: "category";
|
|
8
|
+
label: string;
|
|
9
|
+
title: string;
|
|
10
|
+
categoryKey: string;
|
|
11
|
+
count?: number;
|
|
12
|
+
tone: "purple" | "green" | "teal" | "yellow" | "gray" | "red";
|
|
13
|
+
badge?: string;
|
|
14
|
+
star?: string;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export interface SkillSkillItem {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
id: string;
|
|
19
|
+
kind: "skill";
|
|
20
|
+
label: string;
|
|
21
|
+
skill: SkillInfo;
|
|
22
|
+
/** Extra indent level for child skills inside an expanded skill set */
|
|
23
|
+
indent?: number;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export interface SkillSetItem {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
id: string;
|
|
28
|
+
kind: "skillset";
|
|
29
|
+
label: string;
|
|
30
|
+
skillSet: SkillSetInfo;
|
|
31
|
+
expanded: boolean;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
export type SkillBrowserItem =
|
|
34
|
+
export type SkillBrowserItem =
|
|
35
|
+
| SkillCategoryItem
|
|
36
|
+
| SkillSkillItem
|
|
37
|
+
| SkillSetItem;
|
|
35
38
|
|
|
36
39
|
// ─── Adapter ─────────────────────────────────────────────────────────────────
|
|
37
40
|
|
|
38
41
|
export interface BuildSkillBrowserItemsArgs {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
recommended: SkillInfo[];
|
|
43
|
+
popular: SkillInfo[];
|
|
44
|
+
installed: SkillInfo[];
|
|
45
|
+
searchResults: SkillInfo[];
|
|
46
|
+
query: string;
|
|
47
|
+
isSearchLoading: boolean;
|
|
48
|
+
skillSets?: SkillSetInfo[];
|
|
49
|
+
expandedSets?: Set<string>;
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
/**
|
|
@@ -51,172 +54,176 @@ export interface BuildSkillBrowserItemsArgs {
|
|
|
51
54
|
* Extracted from SkillsScreen so it can be tested independently.
|
|
52
55
|
*/
|
|
53
56
|
export function buildSkillBrowserItems({
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
recommended,
|
|
58
|
+
popular,
|
|
59
|
+
installed,
|
|
60
|
+
searchResults,
|
|
61
|
+
query,
|
|
62
|
+
isSearchLoading,
|
|
63
|
+
skillSets = [],
|
|
64
|
+
expandedSets = new Set(),
|
|
62
65
|
}: BuildSkillBrowserItemsArgs): SkillBrowserItem[] {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
66
|
+
const lowerQuery = query.toLowerCase();
|
|
67
|
+
const items: SkillBrowserItem[] = [];
|
|
68
|
+
|
|
69
|
+
// ── INSTALLED: always shown at top (if any) ──
|
|
70
|
+
const installedFiltered = lowerQuery
|
|
71
|
+
? installed.filter((s) => s.name.toLowerCase().includes(lowerQuery))
|
|
72
|
+
: installed;
|
|
73
|
+
|
|
74
|
+
if (installedFiltered.length > 0) {
|
|
75
|
+
items.push({
|
|
76
|
+
id: "cat:installed",
|
|
77
|
+
kind: "category",
|
|
78
|
+
label: `Installed (${installedFiltered.length})`,
|
|
79
|
+
title: "Installed",
|
|
80
|
+
categoryKey: "installed",
|
|
81
|
+
count: installedFiltered.length,
|
|
82
|
+
tone: "purple",
|
|
83
|
+
star: "\u25CF ",
|
|
84
|
+
});
|
|
85
|
+
for (const skill of installedFiltered) {
|
|
86
|
+
items.push({
|
|
87
|
+
id: `skill:${skill.id}`,
|
|
88
|
+
kind: "skill",
|
|
89
|
+
label: skill.name,
|
|
90
|
+
skill,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ── RECOMMENDED: skill sets + individual skills, all as first-class items ──
|
|
96
|
+
const filteredSets = lowerQuery
|
|
97
|
+
? skillSets.filter((s) => {
|
|
98
|
+
if (s.name.toLowerCase().includes(lowerQuery)) return true;
|
|
99
|
+
if (s.description.toLowerCase().includes(lowerQuery)) return true;
|
|
100
|
+
if (
|
|
101
|
+
s.loaded &&
|
|
102
|
+
s.skills.some((sk) => sk.name.toLowerCase().includes(lowerQuery))
|
|
103
|
+
)
|
|
104
|
+
return true;
|
|
105
|
+
return false;
|
|
106
|
+
})
|
|
107
|
+
: skillSets;
|
|
108
|
+
|
|
109
|
+
const filteredRec = lowerQuery
|
|
110
|
+
? recommended.filter(
|
|
111
|
+
(s) =>
|
|
112
|
+
s.name.toLowerCase().includes(lowerQuery) ||
|
|
113
|
+
(s.description || "").toLowerCase().includes(lowerQuery),
|
|
114
|
+
)
|
|
115
|
+
: recommended;
|
|
116
|
+
|
|
117
|
+
const recommendedCount = filteredSets.length + filteredRec.length;
|
|
118
|
+
|
|
119
|
+
items.push({
|
|
120
|
+
id: "cat:recommended",
|
|
121
|
+
kind: "category",
|
|
122
|
+
label: "Recommended",
|
|
123
|
+
title: "Recommended",
|
|
124
|
+
categoryKey: "recommended",
|
|
125
|
+
count: recommendedCount,
|
|
126
|
+
tone: "green",
|
|
127
|
+
star: "\u2605 ",
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Skill sets first within recommended
|
|
131
|
+
for (const set of filteredSets) {
|
|
132
|
+
const isExpanded = expandedSets.has(set.id);
|
|
133
|
+
items.push({
|
|
134
|
+
id: `skillset:${set.id}`,
|
|
135
|
+
kind: "skillset",
|
|
136
|
+
label: set.name,
|
|
137
|
+
skillSet: set,
|
|
138
|
+
expanded: isExpanded,
|
|
139
|
+
});
|
|
140
|
+
// When expanded, show child skills as indented skill items
|
|
141
|
+
if (isExpanded && set.loaded) {
|
|
142
|
+
const childSkills = lowerQuery
|
|
143
|
+
? set.skills.filter((sk) => sk.name.toLowerCase().includes(lowerQuery))
|
|
144
|
+
: set.skills;
|
|
145
|
+
for (const skill of childSkills) {
|
|
146
|
+
items.push({
|
|
147
|
+
id: `skill:${skill.id}`,
|
|
148
|
+
kind: "skill",
|
|
149
|
+
label: skill.name,
|
|
150
|
+
skill,
|
|
151
|
+
indent: 2,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Then individual recommended skills
|
|
158
|
+
for (const skill of filteredRec) {
|
|
159
|
+
items.push({
|
|
160
|
+
id: `skill:${skill.id}`,
|
|
161
|
+
kind: "skill",
|
|
162
|
+
label: skill.name,
|
|
163
|
+
skill,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ── SEARCH MODE ──
|
|
168
|
+
if (query.length >= 2) {
|
|
169
|
+
if (!isSearchLoading && searchResults.length > 0) {
|
|
170
|
+
const recNames = new Set(recommended.map((s) => s.name));
|
|
171
|
+
const deduped = searchResults
|
|
172
|
+
.filter((s) => !recNames.has(s.name))
|
|
173
|
+
.sort((a, b) => (b.stars ?? 0) - (a.stars ?? 0));
|
|
174
|
+
|
|
175
|
+
if (deduped.length > 0) {
|
|
176
|
+
items.push({
|
|
177
|
+
id: "cat:search",
|
|
178
|
+
kind: "category",
|
|
179
|
+
label: `Search (${deduped.length})`,
|
|
180
|
+
title: "Search",
|
|
181
|
+
categoryKey: "popular",
|
|
182
|
+
count: deduped.length,
|
|
183
|
+
tone: "teal",
|
|
184
|
+
});
|
|
185
|
+
for (const skill of deduped) {
|
|
186
|
+
items.push({
|
|
187
|
+
id: `skill:${skill.id}`,
|
|
188
|
+
kind: "skill",
|
|
189
|
+
label: skill.name,
|
|
190
|
+
skill,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return items;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ── POPULAR (default, no search query) — only skills with meaningful stars ──
|
|
199
|
+
// Dedup by name — API can return same skill name from different repos
|
|
200
|
+
const seenPopular = new Set<string>();
|
|
201
|
+
const popularDeduped = popular
|
|
202
|
+
.filter((s) => (s.stars ?? 0) >= 5)
|
|
203
|
+
.filter((s) => {
|
|
204
|
+
if (seenPopular.has(s.name)) return false;
|
|
205
|
+
seenPopular.add(s.name);
|
|
206
|
+
return true;
|
|
207
|
+
});
|
|
208
|
+
if (popularDeduped.length > 0) {
|
|
209
|
+
items.push({
|
|
210
|
+
id: "cat:popular",
|
|
211
|
+
kind: "category",
|
|
212
|
+
label: "Popular",
|
|
213
|
+
title: "Popular",
|
|
214
|
+
categoryKey: "popular",
|
|
215
|
+
count: popularDeduped.length,
|
|
216
|
+
tone: "teal",
|
|
217
|
+
});
|
|
218
|
+
for (const skill of popularDeduped) {
|
|
219
|
+
items.push({
|
|
220
|
+
id: `skill:${skill.id}`,
|
|
221
|
+
kind: "skill",
|
|
222
|
+
label: skill.name,
|
|
223
|
+
skill,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return items;
|
|
222
229
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
import { theme, type UiColor } from "../theme.js";
|
|
2
3
|
|
|
3
4
|
interface CategoryHeaderProps {
|
|
4
5
|
/** Category title */
|
|
@@ -8,7 +9,7 @@ interface CategoryHeaderProps {
|
|
|
8
9
|
/** Status badge (e.g., "✓ Configured", "3 plugins") */
|
|
9
10
|
status?: string;
|
|
10
11
|
/** Status badge color */
|
|
11
|
-
statusColor?:
|
|
12
|
+
statusColor?: UiColor;
|
|
12
13
|
/** Whether category is expanded */
|
|
13
14
|
expanded?: boolean;
|
|
14
15
|
/** Number of items in category */
|
|
@@ -19,7 +20,7 @@ export function CategoryHeader({
|
|
|
19
20
|
title,
|
|
20
21
|
version,
|
|
21
22
|
status,
|
|
22
|
-
statusColor =
|
|
23
|
+
statusColor = theme.colors.success,
|
|
23
24
|
expanded = true,
|
|
24
25
|
count,
|
|
25
26
|
}: CategoryHeaderProps) {
|
|
@@ -30,14 +31,14 @@ export function CategoryHeader({
|
|
|
30
31
|
|
|
31
32
|
// Simple format without dynamic line calculation
|
|
32
33
|
return (
|
|
33
|
-
<text>
|
|
34
|
-
<span fg=
|
|
35
|
-
<span fg=
|
|
34
|
+
<text fg={theme.colors.text}>
|
|
35
|
+
<span fg={theme.colors.muted}>{expandIcon}</span>
|
|
36
|
+
<span fg={theme.colors.text}>
|
|
36
37
|
<strong> {title}</strong>
|
|
37
38
|
</span>
|
|
38
|
-
<span fg=
|
|
39
|
-
<span fg=
|
|
40
|
-
<span fg=
|
|
39
|
+
<span fg={theme.colors.muted}>{versionBadge}</span>
|
|
40
|
+
<span fg={theme.colors.muted}>{countBadge}</span>
|
|
41
|
+
<span fg={theme.colors.border}> ────</span>
|
|
41
42
|
<span fg={statusColor}>{statusText}</span>
|
|
42
43
|
</text>
|
|
43
44
|
);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
import { theme } from "../theme.js";
|
|
2
3
|
|
|
3
4
|
interface EmptyFilterStateProps {
|
|
4
5
|
query: string;
|
|
@@ -11,16 +12,20 @@ export function EmptyFilterState({
|
|
|
11
12
|
}: EmptyFilterStateProps) {
|
|
12
13
|
return (
|
|
13
14
|
<box flexDirection="column" marginTop={2} paddingLeft={2} paddingRight={2}>
|
|
14
|
-
<text fg=
|
|
15
|
+
<text fg={theme.colors.warning}>
|
|
16
|
+
No {entityName} found for "{query}"
|
|
17
|
+
</text>
|
|
15
18
|
<box marginTop={1}>
|
|
16
|
-
<text fg=
|
|
19
|
+
<text fg={theme.colors.muted}>
|
|
20
|
+
Try a different search term, or if you think
|
|
21
|
+
</text>
|
|
17
22
|
</box>
|
|
18
|
-
<text fg=
|
|
23
|
+
<text fg={theme.colors.muted}>this is a bug, report it at:</text>
|
|
19
24
|
<box marginTop={1}>
|
|
20
|
-
<text fg=
|
|
25
|
+
<text fg={theme.colors.link}>github.com/MadAppGang/magus/issues</text>
|
|
21
26
|
</box>
|
|
22
27
|
<box marginTop={1}>
|
|
23
|
-
<text fg=
|
|
28
|
+
<text fg={theme.colors.muted}>Press Esc to clear the filter.</text>
|
|
24
29
|
</box>
|
|
25
30
|
</box>
|
|
26
31
|
);
|
|
Binary file
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
import { theme, type UiColor } from "../theme.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Scope indicator for list items.
|
|
@@ -15,16 +16,19 @@ export function scopeIndicatorText(
|
|
|
15
16
|
user?: boolean,
|
|
16
17
|
project?: boolean,
|
|
17
18
|
local?: boolean,
|
|
18
|
-
): {
|
|
19
|
-
|
|
19
|
+
): {
|
|
20
|
+
text: string;
|
|
21
|
+
segments: Array<{ char: string; bg: UiColor; fg: UiColor }>;
|
|
22
|
+
} {
|
|
23
|
+
const segments: Array<{ char: string; bg: UiColor; fg: UiColor }> = [
|
|
20
24
|
user
|
|
21
|
-
? { char: "u", bg:
|
|
25
|
+
? { char: "u", bg: theme.colors.info, fg: theme.hints.fg }
|
|
22
26
|
: { char: " ", bg: "", fg: "" },
|
|
23
27
|
project
|
|
24
|
-
? { char: "p", bg:
|
|
28
|
+
? { char: "p", bg: theme.colors.success, fg: theme.hints.fg }
|
|
25
29
|
: { char: " ", bg: "", fg: "" },
|
|
26
30
|
local
|
|
27
|
-
? { char: "l", bg:
|
|
31
|
+
? { char: "l", bg: theme.colors.warning, fg: theme.hints.fg }
|
|
28
32
|
: { char: " ", bg: "", fg: "" },
|
|
29
33
|
];
|
|
30
34
|
|
|
@@ -42,7 +46,7 @@ export function ScopeIndicator({ user, project, local }: ScopeIndicatorProps) {
|
|
|
42
46
|
const { segments } = scopeIndicatorText(user, project, local);
|
|
43
47
|
|
|
44
48
|
return (
|
|
45
|
-
<text>
|
|
49
|
+
<text fg={theme.colors.text}>
|
|
46
50
|
{segments.map((s, i) =>
|
|
47
51
|
s.bg ? (
|
|
48
52
|
<span key={i} bg={s.bg} fg={s.fg}>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useState, useEffect, useMemo } from "react";
|
|
2
|
+
import { theme } from "../theme.js";
|
|
2
3
|
|
|
3
4
|
interface ScrollableListProps<T> {
|
|
4
5
|
/** Array of items to display */
|
|
@@ -62,7 +63,7 @@ export function ScrollableList<T>({
|
|
|
62
63
|
return (
|
|
63
64
|
<box flexDirection="column">
|
|
64
65
|
{showScrollIndicators && hasItemsAbove && (
|
|
65
|
-
<text fg=
|
|
66
|
+
<text fg={theme.colors.info}>↑ {scrollOffset} more</text>
|
|
66
67
|
)}
|
|
67
68
|
|
|
68
69
|
{visibleItems.map(({ item, originalIndex }) => (
|
|
@@ -76,7 +77,7 @@ export function ScrollableList<T>({
|
|
|
76
77
|
))}
|
|
77
78
|
|
|
78
79
|
{showScrollIndicators && hasItemsBelow && (
|
|
79
|
-
<text fg=
|
|
80
|
+
<text fg={theme.colors.info}>↓ {itemsBelow} more</text>
|
|
80
81
|
)}
|
|
81
82
|
</box>
|
|
82
83
|
);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { useKeyboardHandler } from "../hooks/useKeyboardHandler.js";
|
|
3
|
+
import { theme } from "../theme.js";
|
|
3
4
|
|
|
4
5
|
interface SearchInputProps {
|
|
5
6
|
/** Current search value */
|
|
@@ -41,7 +42,7 @@ export function SearchInput({
|
|
|
41
42
|
|
|
42
43
|
return (
|
|
43
44
|
<box flexDirection="row">
|
|
44
|
-
<text fg=
|
|
45
|
+
<text fg={theme.colors.info}>❯ </text>
|
|
45
46
|
<input
|
|
46
47
|
value={value}
|
|
47
48
|
onChange={onChange}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
import { theme } from "../theme.js";
|
|
2
3
|
|
|
3
4
|
interface StyledTextProps {
|
|
4
5
|
/** Text content */
|
|
@@ -25,8 +26,8 @@ interface StyledTextProps {
|
|
|
25
26
|
* Converts props to nested tag pattern required by OpenTUI.
|
|
26
27
|
*
|
|
27
28
|
* Example:
|
|
28
|
-
* <StyledText bold color=
|
|
29
|
-
* becomes: <text fg=
|
|
29
|
+
* <StyledText bold color={theme.colors.success}>Success</StyledText>
|
|
30
|
+
* becomes: <text fg={theme.colors.success}><strong>Success</strong></text>
|
|
30
31
|
*/
|
|
31
32
|
export function StyledText({
|
|
32
33
|
children,
|
|
@@ -52,12 +53,12 @@ export function StyledText({
|
|
|
52
53
|
}
|
|
53
54
|
// Note: dim and inverse use span with colors since OpenTUI doesn't have these as React elements
|
|
54
55
|
if (dim) {
|
|
55
|
-
content = <span fg=
|
|
56
|
+
content = <span fg={theme.colors.muted}>{content}</span>;
|
|
56
57
|
}
|
|
57
58
|
if (inverse) {
|
|
58
59
|
// Inverse effect approximated with contrasting colors
|
|
59
60
|
content = (
|
|
60
|
-
<span fg=
|
|
61
|
+
<span fg={theme.hints.fg} bg={theme.colors.text}>
|
|
61
62
|
{content}
|
|
62
63
|
</span>
|
|
63
64
|
);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import type { Screen } from "../state/types.js";
|
|
3
|
+
import { theme } from "../theme.js";
|
|
3
4
|
|
|
4
5
|
interface Tab {
|
|
5
6
|
key: string;
|
|
@@ -34,7 +35,7 @@ export function TabBar({ currentScreen }: TabBarProps) {
|
|
|
34
35
|
{/* Tab content */}
|
|
35
36
|
{isSelected ? (
|
|
36
37
|
<box>
|
|
37
|
-
<text bg=
|
|
38
|
+
<text bg={theme.colors.accent} fg={theme.hints.fg}>
|
|
38
39
|
<strong>
|
|
39
40
|
{" "}
|
|
40
41
|
{tab.key}:{tab.label}{" "}
|
|
@@ -43,14 +44,14 @@ export function TabBar({ currentScreen }: TabBarProps) {
|
|
|
43
44
|
</box>
|
|
44
45
|
) : (
|
|
45
46
|
<box>
|
|
46
|
-
<text fg=
|
|
47
|
+
<text fg={theme.colors.muted}>
|
|
47
48
|
{" "}
|
|
48
49
|
{tab.key}:{tab.label}{" "}
|
|
49
50
|
</text>
|
|
50
51
|
</box>
|
|
51
52
|
)}
|
|
52
53
|
{/* Separator */}
|
|
53
|
-
{!isLast && <text fg=
|
|
54
|
+
{!isLast && <text fg={theme.colors.muted}>│</text>}
|
|
54
55
|
</box>
|
|
55
56
|
);
|
|
56
57
|
})}
|