@brimveyn/aimux 1.19.0 → 1.19.1
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
CHANGED
|
@@ -15,7 +15,7 @@ import { formatDivergence } from '../../../../state/session-worktrees'
|
|
|
15
15
|
import { IDLE_SESSION_STATUS } from '../../../../state/types'
|
|
16
16
|
import { useBusySpinner } from '../../../hooks/use-busy-spinner'
|
|
17
17
|
import { moveIdToIdPosition, orderSessionsForDisplay } from '../../../session-ordering'
|
|
18
|
-
import { useTheme } from '../../../theme'
|
|
18
|
+
import { useBaseTheme, useTheme } from '../../../theme'
|
|
19
19
|
import { FlashLabelBadge } from '../../flash/flash-label-badge'
|
|
20
20
|
import { ContextMenuBox } from '../../overlays/context-menu/context-menu-box'
|
|
21
21
|
import { useSidebarAutoScroll } from './use-sidebar-auto-scroll'
|
|
@@ -298,14 +298,17 @@ const WorkspaceRow = memo(function WorkspaceRow({
|
|
|
298
298
|
status,
|
|
299
299
|
}: WorkspaceRowProps) {
|
|
300
300
|
const t = useTheme()
|
|
301
|
+
// Selection highlight must stay opaque in transparent mode — otherwise the
|
|
302
|
+
// cursor row visually disappears against the see-through chrome.
|
|
303
|
+
const base = useBaseTheme()
|
|
301
304
|
const showSpinner = status.working
|
|
302
305
|
const showWaiting = status.waiting
|
|
303
306
|
const spinner = useBusySpinner(showSpinner)
|
|
304
307
|
let bgColor: string | undefined
|
|
305
308
|
if (dragging || isActiveItem) {
|
|
306
|
-
bgColor =
|
|
309
|
+
bgColor = base.backgroundElement
|
|
307
310
|
} else if (inCurrentGroup) {
|
|
308
|
-
bgColor =
|
|
311
|
+
bgColor = base.backgroundPanel
|
|
309
312
|
}
|
|
310
313
|
const workingColor = t.primary
|
|
311
314
|
const waitingColor = t.warning
|
|
@@ -7,7 +7,7 @@ import type { SessionRecord, WorktreeRecord } from '../../../../state/types'
|
|
|
7
7
|
import { useAppStore } from '../../../../state/app-store'
|
|
8
8
|
import { dispatchGlobal, runSideEffectGlobal } from '../../../../state/dispatch-ref'
|
|
9
9
|
import { formatDivergence } from '../../../../state/session-worktrees'
|
|
10
|
-
import { useTheme } from '../../../theme'
|
|
10
|
+
import { useBaseTheme, useTheme } from '../../../theme'
|
|
11
11
|
import { FlashLabelBadge } from '../../flash/flash-label-badge'
|
|
12
12
|
import { ContextMenuBox } from '../../overlays/context-menu/context-menu-box'
|
|
13
13
|
|
|
@@ -33,6 +33,8 @@ export const WorktreeRow = memo(function WorktreeRow({
|
|
|
33
33
|
worktree,
|
|
34
34
|
}: WorktreeRowProps) {
|
|
35
35
|
const t = useTheme()
|
|
36
|
+
// Selection highlight must stay opaque in transparent mode.
|
|
37
|
+
const base = useBaseTheme()
|
|
36
38
|
const currentSessionId = useAppStore((s) => s.currentSessionId)
|
|
37
39
|
const isCurrentSession = session.id === currentSessionId
|
|
38
40
|
const divergence = useAppStore((s) => s.worktreeDivergence[worktree.id])
|
|
@@ -102,9 +104,9 @@ export const WorktreeRow = memo(function WorktreeRow({
|
|
|
102
104
|
|
|
103
105
|
let bgColor: string | undefined
|
|
104
106
|
if (isActiveItem) {
|
|
105
|
-
bgColor =
|
|
107
|
+
bgColor = base.backgroundElement
|
|
106
108
|
} else if (inCurrentGroup) {
|
|
107
|
-
bgColor =
|
|
109
|
+
bgColor = base.backgroundPanel
|
|
108
110
|
}
|
|
109
111
|
|
|
110
112
|
const connector = isLast ? '└─' : '├─'
|
|
@@ -11,7 +11,7 @@ import { useAIUsageStore } from '../../../state/ai-usage-store'
|
|
|
11
11
|
import { useAppStore } from '../../../state/app-store'
|
|
12
12
|
import { useKeymap } from '../../keymap-context'
|
|
13
13
|
import { getStatusBarModel, type IdentitySegment } from '../../status-bar-model'
|
|
14
|
-
import { useTheme } from '../../theme'
|
|
14
|
+
import { useBaseTheme, useTheme } from '../../theme'
|
|
15
15
|
import { AIUsageIndicator } from '../overlays/ai-usage/ai-usage-indicator'
|
|
16
16
|
|
|
17
17
|
// Powerline-style separator glyph pairs.
|
|
@@ -110,6 +110,10 @@ function Separator({ bg, fg, glyph }: { bg: string; fg: string; glyph: string })
|
|
|
110
110
|
|
|
111
111
|
export function StatusBar() {
|
|
112
112
|
const t = useTheme()
|
|
113
|
+
// Badge foregrounds (mode label, version) sit on colored tiles and would
|
|
114
|
+
// render invisible with a transparent chrome color — always use the base
|
|
115
|
+
// (opaque) background token for contrast against those tiles.
|
|
116
|
+
const base = useBaseTheme()
|
|
113
117
|
const state = useAppStore((s) => s)
|
|
114
118
|
const config = useKeymap()
|
|
115
119
|
const model = getStatusBarModel(state, config)
|
|
@@ -139,7 +143,7 @@ export function StatusBar() {
|
|
|
139
143
|
<box height={1} flexShrink={0} flexDirection="row" overflow="hidden">
|
|
140
144
|
{/* A: mode */}
|
|
141
145
|
<box backgroundColor={modeColor} paddingLeft={1} paddingRight={1}>
|
|
142
|
-
<text fg={
|
|
146
|
+
<text fg={base.background} selectable={false}>
|
|
143
147
|
{getModeBadge(state.focusMode)}
|
|
144
148
|
</text>
|
|
145
149
|
</box>
|
|
@@ -186,7 +190,7 @@ export function StatusBar() {
|
|
|
186
190
|
|
|
187
191
|
{/* Y: version */}
|
|
188
192
|
<box backgroundColor={tileY} paddingLeft={1} paddingRight={1} flexShrink={0}>
|
|
189
|
-
<text fg={
|
|
193
|
+
<text fg={base.background} selectable={false}>
|
|
190
194
|
v{APP_VERSION}
|
|
191
195
|
</text>
|
|
192
196
|
</box>
|
package/src/ui/theme-store.ts
CHANGED
|
@@ -26,8 +26,7 @@ const themeStore = createStore<ThemeStore>(() => ({
|
|
|
26
26
|
let cachedId: ThemeId | null = null
|
|
27
27
|
let cachedMode: ThemeMode | null = null
|
|
28
28
|
let cachedBase: ResolvedTuiTheme | null = null
|
|
29
|
-
let
|
|
30
|
-
let cachedFinal: ResolvedTuiTheme | null = null
|
|
29
|
+
let cachedOverlay: ResolvedTuiTheme | null = null
|
|
31
30
|
|
|
32
31
|
// Chrome surface tokens: when transparent mode is on we replace these with
|
|
33
32
|
// 'transparent' so opentui's BoxRenderable skips its fill (alpha=0 early-returns
|
|
@@ -41,27 +40,27 @@ const CHROME_BG_TOKENS = [
|
|
|
41
40
|
'backgroundMenu',
|
|
42
41
|
] as const
|
|
43
42
|
|
|
43
|
+
// Both the opaque base and the transparent overlay are cached side-by-side
|
|
44
|
+
// (both keyed by id+mode). A single-slot cache would thrash when useTheme() and
|
|
45
|
+
// useBaseTheme() are both mounted with different transparent values — each call
|
|
46
|
+
// would invalidate the other's cache, hand React a fresh object reference every
|
|
47
|
+
// render, and drive an infinite update loop.
|
|
44
48
|
function derive(id: ThemeId, mode: ThemeMode, transparent: boolean): ResolvedTuiTheme {
|
|
45
|
-
if (cachedFinal && cachedId === id && cachedMode === mode && cachedTransparent === transparent)
|
|
46
|
-
return cachedFinal
|
|
47
|
-
|
|
48
49
|
if (!cachedBase || cachedId !== id || cachedMode !== mode) {
|
|
49
50
|
const json = TUI_THEMES[id] ?? TUI_THEMES.aimux
|
|
50
51
|
if (!json) throw new Error(`No theme JSON for ${id}`)
|
|
51
52
|
cachedBase = resolveTuiTheme(json, mode)
|
|
53
|
+
cachedOverlay = null
|
|
54
|
+
cachedId = id
|
|
55
|
+
cachedMode = mode
|
|
52
56
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
cachedTransparent = transparent
|
|
56
|
-
|
|
57
|
-
if (transparent) {
|
|
57
|
+
if (!transparent) return cachedBase
|
|
58
|
+
if (!cachedOverlay) {
|
|
58
59
|
const overlay: ResolvedTuiTheme = { ...cachedBase }
|
|
59
60
|
for (const key of CHROME_BG_TOKENS) overlay[key] = 'transparent'
|
|
60
|
-
|
|
61
|
-
} else {
|
|
62
|
-
cachedFinal = cachedBase
|
|
61
|
+
cachedOverlay = overlay
|
|
63
62
|
}
|
|
64
|
-
return
|
|
63
|
+
return cachedOverlay
|
|
65
64
|
}
|
|
66
65
|
|
|
67
66
|
/** Subscribe to the resolved TUI theme for the active id+mode (+transparent overlay). */
|
|
@@ -69,6 +68,16 @@ export function useTheme(): ResolvedTuiTheme {
|
|
|
69
68
|
return useStore(themeStore, (s) => derive(s.id, s.mode, s.transparent))
|
|
70
69
|
}
|
|
71
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Resolved TUI theme WITHOUT the transparent chrome overlay. Use for the rare
|
|
73
|
+
* chrome sites that must stay opaque even in transparent mode — e.g. the
|
|
74
|
+
* sidebar selection highlight (would otherwise vanish) and the status-bar
|
|
75
|
+
* badge foregrounds (would otherwise render as transparent-on-color).
|
|
76
|
+
*/
|
|
77
|
+
export function useBaseTheme(): ResolvedTuiTheme {
|
|
78
|
+
return useStore(themeStore, (s) => derive(s.id, s.mode, false))
|
|
79
|
+
}
|
|
80
|
+
|
|
72
81
|
/** Synchronous snapshot of the resolved theme for non-React callers. */
|
|
73
82
|
export function getCurrentTheme(): ResolvedTuiTheme {
|
|
74
83
|
const s = themeStore.getState()
|