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
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { ThemeMode } from "@opentui/core";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The one place claudeup needs to know whether the terminal is light or dark.
|
|
5
|
+
*
|
|
6
|
+
* Everything else in the palette avoids this question on purpose: accents are
|
|
7
|
+
* chosen to clear 3:1 against both backgrounds, and body text uses the terminal's
|
|
8
|
+
* own foreground. A *disabled row tint* is the exception, because "subtle" is
|
|
9
|
+
* defined relative to the page — a wash that sits gently on cream is a bright
|
|
10
|
+
* band on near-black, and there is no third colour that is quiet on both.
|
|
11
|
+
*
|
|
12
|
+
* ANSI cannot help here either: there is no alpha channel, so the tint cannot be
|
|
13
|
+
* expressed as "the page, 8% toward the foreground". OpenTUI's alpha blending
|
|
14
|
+
* lives only in its 3d module, not the text renderer.
|
|
15
|
+
*
|
|
16
|
+
* So we ask. OpenTUI queries the terminal over OSC and reports `themeMode`; we
|
|
17
|
+
* read it once at startup. **Unknown means no tint** — a terminal that does not
|
|
18
|
+
* answer the query gets today's behaviour rather than a guess that could paint a
|
|
19
|
+
* bright band across half the list.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
let detected: ThemeMode | null = null;
|
|
23
|
+
|
|
24
|
+
/** Set once at startup from `renderer.themeMode`. */
|
|
25
|
+
export function setThemeMode(mode: ThemeMode | null): void {
|
|
26
|
+
detected = mode;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getThemeMode(): ThemeMode | null {
|
|
30
|
+
return detected;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Test seam — reset detection between cases. */
|
|
34
|
+
export function resetThemeMode(): void {
|
|
35
|
+
detected = null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Fill for an UNLIT segment of the three-square scope bar.
|
|
40
|
+
*
|
|
41
|
+
* The bar reads as three segments, each lit (in its scope colour) or unlit. Unlit
|
|
42
|
+
* used to be the same mid-grey as body de-emphasis, `#6B7280`, which is dark —
|
|
43
|
+
* so an unlit segment carried the same visual weight as a lit one and the bar
|
|
44
|
+
* read as three similar blobs. Reported directly: "non installed state is dark as
|
|
45
|
+
* well - confusing".
|
|
46
|
+
*
|
|
47
|
+
* An unlit segment should recede toward the page, and "toward the page" is the
|
|
48
|
+
* one thing a fixed colour cannot express: a pale fill that sits quietly on cream
|
|
49
|
+
* is a bright block on near-black. Hence the two values, chosen per detected
|
|
50
|
+
* terminal — this is the only place claudeup asks.
|
|
51
|
+
*/
|
|
52
|
+
export const SCOPE_OFF_FILL = {
|
|
53
|
+
/** Just off a light page — a filled but quiet segment. */
|
|
54
|
+
light: "#D9D9CC",
|
|
55
|
+
/** Just off a dark page. */
|
|
56
|
+
dark: "#3A3A40",
|
|
57
|
+
} as const;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Neutral fallback when the terminal never answered the OSC query. A mid grey is
|
|
61
|
+
* visible on both pages — not as quiet as the tuned values, but never invisible
|
|
62
|
+
* and never a bright block. Failing to a wrong extreme would be worse than
|
|
63
|
+
* failing to something merely unremarkable.
|
|
64
|
+
*/
|
|
65
|
+
export const SCOPE_OFF_FALLBACK = "#9CA3AF";
|
|
66
|
+
|
|
67
|
+
/** Fill colour for an unlit scope segment. Always a colour — never undefined,
|
|
68
|
+
* because the segment is a filled block whatever the terminal turns out to be. */
|
|
69
|
+
export function scopeOffFill(): string {
|
|
70
|
+
if (detected === "light") return SCOPE_OFF_FILL.light;
|
|
71
|
+
if (detected === "dark") return SCOPE_OFF_FILL.dark;
|
|
72
|
+
return SCOPE_OFF_FALLBACK;
|
|
73
|
+
}
|
package/src/ui/theme.ts
CHANGED
|
@@ -1,55 +1,149 @@
|
|
|
1
|
+
import { RGBA } from "@opentui/core";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* claudeup's palette: our own vivid accents, on the terminal's own canvas.
|
|
5
|
+
*
|
|
6
|
+
* Three rules, and the reason for each:
|
|
7
|
+
*
|
|
8
|
+
* 1. **Backgrounds are never ours.** Surfaces and modals use the terminal's
|
|
9
|
+
* background. Painting an absolute fill boxes the UI into whichever theme we
|
|
10
|
+
* guessed, and it was the original sin here — modals hardcoded #1C1C1E.
|
|
11
|
+
* 2. **Body text is never ours.** Normal text uses the terminal's foreground,
|
|
12
|
+
* which is the only colour guaranteed to clear a text-grade contrast ratio
|
|
13
|
+
* against that terminal's own background. `text` used to be the string
|
|
14
|
+
* `"white"` — OpenTUI resolves that to rgb(255,255,255), not to "the
|
|
15
|
+
* terminal's white" — so on a light theme every installed plugin was drawn
|
|
16
|
+
* white-on-cream and vanished while the *un*installed ones, in grey, stayed
|
|
17
|
+
* readable. The list read exactly backwards.
|
|
18
|
+
* 3. **Accents ARE ours, and vivid.** Deferring accents to ANSI slots 0-15 made
|
|
19
|
+
* everything legible but flattened it: a theme that renders slots 2 and 3 as
|
|
20
|
+
* neighbouring olives leaves user/project/local scope squares indistinguishable,
|
|
21
|
+
* and slot 8 chips melt into the page. Distinction is a product decision, so
|
|
22
|
+
* these are chosen, not inherited.
|
|
23
|
+
*
|
|
24
|
+
* ## How the accents were chosen
|
|
25
|
+
*
|
|
26
|
+
* By measurement, not taste. A single colour CANNOT reach 4.5:1 against both a
|
|
27
|
+
* light and a dark terminal — clearing 4.5:1 on cream needs relative luminance
|
|
28
|
+
* <= ~0.17, clearing it on near-black needs >= ~0.22, and those do not overlap.
|
|
29
|
+
* So the bar is 3:1, WCAG's threshold for UI components and large text, which
|
|
30
|
+
* admits a band of mid-dark saturated colours. Every accent below clears 3:1
|
|
31
|
+
* against BOTH reference backgrounds, verified in theme-adaptive-colors.test.ts —
|
|
32
|
+
* so a colour that only works on dark cannot be added without failing the build.
|
|
33
|
+
*
|
|
34
|
+
* Where we own a badge's background we also own its ink, so `ink` is plain white
|
|
35
|
+
* and its contrast is deterministic rather than a function of the user's theme.
|
|
36
|
+
* That is why badge ink is not slot 0 or the terminal background: on a light
|
|
37
|
+
* theme, terminal-background ink on a yellow chip is unreadable.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/** Reference backgrounds every accent is measured against. */
|
|
41
|
+
export const CONTRAST_REFERENCE = {
|
|
42
|
+
light: "#FAFAD2",
|
|
43
|
+
dark: "#1C1C1E",
|
|
44
|
+
} as const;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The vivid set. Ratios are (on light / on dark), measured — see the test.
|
|
48
|
+
* Hues for the three scopes are deliberately far apart (blue / green / amber)
|
|
49
|
+
* because they appear as single-character squares, where a subtle hue shift is
|
|
50
|
+
* not readable at all.
|
|
51
|
+
*/
|
|
52
|
+
export const brand = {
|
|
53
|
+
accent: "#9333EA", // 5.04 / 3.16 — brand purple
|
|
54
|
+
link: "#2563EB", // 4.84 / 3.29
|
|
55
|
+
info: "#0E7490", // 5.02 / 3.18 — teal
|
|
56
|
+
success: "#15803D", // 4.70 / 3.39
|
|
57
|
+
warning: "#B45309", // 4.70 / 3.39 — amber
|
|
58
|
+
danger: "#DC2626", // 4.52 / 3.52
|
|
59
|
+
muted: "#6B7280", // 4.53 / 3.52 — de-emphasis, still legible on both
|
|
60
|
+
/** Ink on OUR badges. We own both sides, so contrast is deterministic. */
|
|
61
|
+
ink: "#FFFFFF",
|
|
62
|
+
} as const;
|
|
63
|
+
|
|
64
|
+
/** Any colour a component may hand to a JSX colour prop. */
|
|
65
|
+
export type UiColor = string | RGBA;
|
|
66
|
+
|
|
67
|
+
/** The terminal's own foreground — the only honest "normal text" colour. */
|
|
68
|
+
const fg = () => RGBA.defaultForeground();
|
|
69
|
+
|
|
70
|
+
/** The terminal's own background — for surfaces, never for ink. */
|
|
71
|
+
const bg = () => RGBA.defaultBackground();
|
|
72
|
+
|
|
1
73
|
export const theme = {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
74
|
+
colors: {
|
|
75
|
+
/** Adaptive by rule 2 — never make this one of ours. */
|
|
76
|
+
text: fg(),
|
|
77
|
+
muted: brand.muted,
|
|
78
|
+
/** Same grey as `muted`; a third level should come from layout, not colour. */
|
|
79
|
+
dim: brand.muted,
|
|
80
|
+
border: brand.muted,
|
|
81
|
+
link: brand.link,
|
|
82
|
+
accent: brand.accent,
|
|
83
|
+
/** Low-key brand tint for bylines — readable, deliberately not attention-grabbing. */
|
|
84
|
+
byline: brand.accent,
|
|
85
|
+
success: brand.success,
|
|
86
|
+
warning: brand.warning,
|
|
87
|
+
danger: brand.danger,
|
|
88
|
+
info: brand.info,
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
selection: {
|
|
92
|
+
bg: brand.accent,
|
|
93
|
+
fg: brand.ink,
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
/** Blue / green / amber: the widest hue separation available, because these
|
|
97
|
+
* render as one-character squares where a subtle shift reads as noise. */
|
|
98
|
+
scopes: {
|
|
99
|
+
user: brand.link,
|
|
100
|
+
project: brand.success,
|
|
101
|
+
local: brand.warning,
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
/** Badge colours. `fg` is the ink ON the block; `badgeFg` is the same hue used
|
|
105
|
+
* as ink on the normal background instead. */
|
|
106
|
+
category: {
|
|
107
|
+
purple: { bg: brand.accent, fg: brand.ink, badgeFg: brand.accent },
|
|
108
|
+
green: { bg: brand.success, fg: brand.ink, badgeFg: brand.success },
|
|
109
|
+
teal: { bg: brand.info, fg: brand.ink, badgeFg: brand.info },
|
|
110
|
+
yellow: { bg: brand.warning, fg: brand.ink, badgeFg: brand.warning },
|
|
111
|
+
gray: { bg: brand.muted, fg: brand.ink, badgeFg: brand.muted },
|
|
112
|
+
red: { bg: brand.danger, fg: brand.ink, badgeFg: brand.danger },
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
meta: {
|
|
116
|
+
muted: brand.muted,
|
|
117
|
+
warning: brand.warning,
|
|
118
|
+
success: brand.success,
|
|
119
|
+
danger: brand.danger,
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Footer key chips. A vivid block with white ink, because the point of a chip
|
|
124
|
+
* is to read as a pressable key at a glance — the previous neutral-grey chip
|
|
125
|
+
* melted into a light page. Dark slates were measured and rejected: they clear
|
|
126
|
+
* 3:1 on cream but drop to ~2.2 on a dark terminal, i.e. an invisible block.
|
|
127
|
+
*/
|
|
128
|
+
hints: {
|
|
129
|
+
defaultBg: brand.accent,
|
|
130
|
+
primaryBg: brand.info,
|
|
131
|
+
dangerBg: brand.danger,
|
|
132
|
+
fg: brand.ink,
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
/** Panels and modals sit on the terminal's own background and are separated by
|
|
136
|
+
* their border, never by an absolute fill that fights the user's theme. */
|
|
137
|
+
surface: {
|
|
138
|
+
bg: bg(),
|
|
139
|
+
border: brand.muted,
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
spacing: {
|
|
143
|
+
rowIndent: 1,
|
|
144
|
+
sectionGap: 1,
|
|
145
|
+
panelPadding: 1,
|
|
146
|
+
},
|
|
55
147
|
} as const;
|
|
148
|
+
|
|
149
|
+
export { fg as defaultFg, bg as defaultBg };
|