@brimveyn/aimux 1.9.3 → 1.9.6

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 (46) hide show
  1. package/package.json +2 -2
  2. package/src/app-runtime/side-effects.ts +29 -0
  3. package/src/app.tsx +4 -5
  4. package/src/input/modes/types.ts +2 -0
  5. package/src/pty/terminal-snapshot.ts +7 -7
  6. package/src/state/reducers/git-mode-state.ts +33 -1
  7. package/src/state/types.ts +1 -0
  8. package/src/ui/components/git/diff-renderer/build-rows.ts +21 -0
  9. package/src/ui/components/git/diff-renderer/fold-strip.tsx +7 -7
  10. package/src/ui/components/git/diff-renderer/pierre-diff.tsx +3 -3
  11. package/src/ui/components/git/diff-renderer/split-view.tsx +18 -17
  12. package/src/ui/components/git/diff-renderer/stacked-view.tsx +13 -13
  13. package/src/ui/components/git/git-panel.tsx +42 -39
  14. package/src/ui/components/git/git-view.tsx +20 -20
  15. package/src/ui/components/layout/session-bar.tsx +13 -13
  16. package/src/ui/components/layout/sidebar/sidebar.tsx +26 -28
  17. package/src/ui/components/layout/sidebar/tab-item.tsx +21 -21
  18. package/src/ui/components/layout/split-layout.tsx +2 -2
  19. package/src/ui/components/layout/status-bar.tsx +12 -12
  20. package/src/ui/components/layout/terminal-pane.tsx +19 -31
  21. package/src/ui/components/modals/app/ai-usage-modal.tsx +21 -21
  22. package/src/ui/components/modals/app/help-modal.tsx +5 -6
  23. package/src/ui/components/modals/app/update-available-modal.tsx +3 -3
  24. package/src/ui/components/modals/git/git-commit-modal.tsx +17 -17
  25. package/src/ui/components/modals/sessions/create-session-modal.tsx +8 -8
  26. package/src/ui/components/modals/sessions/session-picker-modal.tsx +6 -6
  27. package/src/ui/components/modals/shared/form.tsx +4 -4
  28. package/src/ui/components/modals/shared/modal-keybinds-overlay.tsx +4 -4
  29. package/src/ui/components/modals/shared/modal-shell.tsx +6 -6
  30. package/src/ui/components/modals/shared/picker.tsx +6 -6
  31. package/src/ui/components/modals/snippets/snippet-picker-modal.tsx +5 -5
  32. package/src/ui/components/modals/tabs/new-tab-modal.tsx +6 -6
  33. package/src/ui/components/modals/themes/theme-picker-modal.tsx +17 -20
  34. package/src/ui/components/overlays/ai-usage/ai-usage-indicator.tsx +12 -12
  35. package/src/ui/components/overlays/context-menu/context-menu-overlay.tsx +6 -6
  36. package/src/ui/components/overlays/pending-chord-overlay.tsx +5 -5
  37. package/src/ui/components/primitives/bare-input.tsx +5 -5
  38. package/src/ui/components/primitives/input-field.tsx +5 -5
  39. package/src/ui/components/primitives/list-item.tsx +27 -22
  40. package/src/ui/components/primitives/surface.tsx +12 -9
  41. package/src/ui/filter-themes.ts +9 -8
  42. package/src/ui/root.tsx +5 -4
  43. package/src/ui/shiki.ts +8 -11
  44. package/src/ui/theme-store.ts +48 -118
  45. package/src/ui/theme.ts +8 -8
  46. package/src/ui/themes.ts +9 -20
@@ -1,21 +1,24 @@
1
1
  import type { ReactNode } from 'react'
2
2
 
3
- import { type SurfaceToken, useBg } from '../../theme'
3
+ import type { TuiColorToken } from '../../themes'
4
+
5
+ import { useTheme } from '../../theme'
4
6
 
5
7
  type SurfaceTone = 'muted' | 'elevated' | 'selected' | 'input' | 'inputActive'
6
8
 
7
- function toneToToken(tone: SurfaceTone): SurfaceToken {
9
+ function toneToToken(tone: SurfaceTone): TuiColorToken {
8
10
  switch (tone) {
9
11
  case 'elevated':
10
- return 'elevated'
12
+ return 'backgroundElement'
11
13
  case 'selected':
12
- case 'inputActive':
13
- return 'selected'
14
+ return 'backgroundElement'
14
15
  case 'input':
15
- return 'base'
16
+ return 'backgroundElement'
17
+ case 'inputActive':
18
+ return 'backgroundElement'
16
19
  case 'muted':
17
20
  default:
18
- return 'elevated'
21
+ return 'backgroundPanel'
19
22
  }
20
23
  }
21
24
 
@@ -44,10 +47,10 @@ export function Surface({
44
47
  tone = 'muted',
45
48
  width,
46
49
  }: SurfaceProps) {
47
- const bg = useBg(toneToToken(tone))
50
+ const t = useTheme()
48
51
  return (
49
52
  <box
50
- backgroundColor={bg}
53
+ backgroundColor={t[toneToToken(tone)]}
51
54
  flexDirection={flexDirection}
52
55
  gap={gap}
53
56
  padding={padding}
@@ -1,15 +1,16 @@
1
- import { THEME_IDS, type ThemeId, THEMES } from './themes'
1
+ import { THEME_IDS, type ThemeId } from './themes'
2
+
3
+ export function themeDisplayName(id: ThemeId): string {
4
+ return id
5
+ .split(/[-_]/)
6
+ .map((p) => (p.length > 0 ? (p[0] ?? '').toUpperCase() + p.slice(1) : p))
7
+ .join(' ')
8
+ }
2
9
 
3
10
  export function filterThemeIds(filter: string | null): ThemeId[] {
4
11
  if (!filter) return THEME_IDS
5
12
  const needle = filter.toLowerCase()
6
13
  return THEME_IDS.filter((id) => {
7
- const entry = THEMES[id]
8
- if (!entry) return false
9
- return (
10
- id.toLowerCase().includes(needle) ||
11
- entry.displayName.toLowerCase().includes(needle) ||
12
- entry.name.toLowerCase().includes(needle)
13
- )
14
+ return id.toLowerCase().includes(needle) || themeDisplayName(id).toLowerCase().includes(needle)
14
15
  })
15
16
  }
package/src/ui/root.tsx CHANGED
@@ -30,7 +30,7 @@ import { ThemePickerModal } from './components/modals/themes/theme-picker-modal'
30
30
  import { ContextMenuBox } from './components/overlays/context-menu/context-menu-box'
31
31
  import { ContextMenuOverlay } from './components/overlays/context-menu/context-menu-overlay'
32
32
  import { PendingChordOverlay } from './components/overlays/pending-chord-overlay'
33
- import { useBg, useTokens } from './theme'
33
+ import { useTheme } from './theme'
34
34
 
35
35
  function getCreateSessionFields(modal: ModalState) {
36
36
  if (modal.type !== 'create-session') {
@@ -250,7 +250,8 @@ export function RootView({
250
250
  terminalRows,
251
251
  themeId,
252
252
  }: RootViewProps) {
253
- const editorBg = useBg('base')
253
+ const t = useTheme()
254
+ const editorBg = t.background
254
255
  const tabs = useAppStore((s) => s.tabs)
255
256
  const activeTabId = useAppStore((s) => s.activeTabId)
256
257
  const layoutTrees = useAppStore((s) => s.layoutTrees)
@@ -415,8 +416,8 @@ function GitPaneInPaneMode({
415
416
  side: 'left' | 'right'
416
417
  }) => void
417
418
  }) {
418
- const bg = useBg('elevated')
419
- const tokens = useTokens()
419
+ const tokens = useTheme()
420
+ const bg = tokens.backgroundPanel
420
421
  const gitPane = useAppStore((s) => s.gitPane)
421
422
  const width = getGitPaneWidthFromRatio(ratio)
422
423
  const contentWidth = Math.max(1, width - 1)
package/src/ui/shiki.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { tuiThemeToShiki } from '@brimveyn/aimux-config'
1
2
  import {
2
3
  type BundledLanguage,
3
4
  createHighlighter,
@@ -5,13 +6,14 @@ import {
5
6
  type ThemeRegistrationRaw,
6
7
  } from 'shiki'
7
8
 
8
- import { getCurrentTheme } from './theme-store'
9
- import { paletteToShikiTheme } from './themes'
9
+ import { getCurrentMode, getCurrentTheme, getCurrentThemeId } from './theme-store'
10
10
 
11
11
  function buildActiveTheme(): { id: string; raw: ThemeRegistrationRaw } {
12
- const theme = getCurrentTheme()
13
- const id = `${theme.name}-${theme.mode}`
14
- return { id, raw: paletteToShikiTheme({ mode: theme.mode, name: id, palette: theme.palette }) }
12
+ const id = `${getCurrentThemeId()}-${getCurrentMode()}`
13
+ return {
14
+ id,
15
+ raw: tuiThemeToShiki({ mode: getCurrentMode(), name: id, theme: getCurrentTheme() }),
16
+ }
15
17
  }
16
18
 
17
19
  let highlighterPromise: Promise<Highlighter> | null = null
@@ -39,11 +41,6 @@ export async function ensureShikiLang(h: Highlighter, lang: string): Promise<boo
39
41
  }
40
42
  }
41
43
 
42
- /**
43
- * Make sure the active aimux theme (light or dark, with any palette overrides)
44
- * is loaded into the highlighter. Returns the registered theme name so the
45
- * caller can pass it to `codeToTokens`.
46
- */
47
44
  export async function ensureActiveShikiTheme(h: Highlighter): Promise<string> {
48
45
  const { id, raw } = buildActiveTheme()
49
46
  if (id === activeThemeId) return id
@@ -51,7 +48,7 @@ export async function ensureActiveShikiTheme(h: Highlighter): Promise<string> {
51
48
  await h.loadTheme(raw)
52
49
  activeThemeId = id
53
50
  } catch {
54
- // Reloading the theme is best-effort; on failure we keep using the prior id.
51
+ // best-effort
55
52
  }
56
53
  return activeThemeId ?? id
57
54
  }
@@ -1,114 +1,74 @@
1
- import { useStore } from 'zustand'
2
- import { createStore } from 'zustand/vanilla'
3
-
4
1
  import {
5
- accent,
6
- type AimuxPalette,
7
- type AimuxTheme,
8
- computeSurfaces,
9
- diffAddBg,
10
- diffDeleteBg,
11
- extendPalette,
2
+ migrateThemeId,
3
+ type ResolvedTuiTheme,
4
+ resolveTuiTheme,
12
5
  type ThemeId,
13
6
  type ThemeMode,
14
- THEMES,
15
- } from './themes'
7
+ TUI_THEMES,
8
+ } from '@brimveyn/aimux-config'
9
+ import { useStore } from 'zustand'
10
+ import { createStore } from 'zustand/vanilla'
16
11
 
17
- export interface ThemeTokens {
18
- /** `accent` with `primary` fallback. */
19
- accent: string
20
- /** App-level background surface (derived from palette.neutral seed via OKLCH). */
21
- bg: string
22
- /** Subtle border between panels. */
23
- border: string
24
- /** Background tint for inserted diff lines. */
25
- diffAddBg: string
26
- /** Background tint for removed diff lines. */
27
- diffDeleteBg: string
28
- /** Surface a notch above the base background — sidebars, headers. */
29
- elevated: string
30
- /** Fainter still — line numbers, placeholders, disabled. */
31
- faint: string
32
- /** Hover/highlight surface (line highlight, list hover). */
33
- hover: string
34
- /** Half-way between background and ink — labels, captions, paths. */
35
- muted: string
36
- palette: AimuxPalette
37
- /** Selected/active row surface. */
38
- selected: string
39
- }
12
+ const DEFAULT_ID: ThemeId = 'aimux'
40
13
 
41
- function computeTokens(palette: AimuxPalette, mode: ThemeMode): ThemeTokens {
42
- const surfaces = computeSurfaces(palette, mode)
43
- return {
44
- accent: accent(palette),
45
- bg: surfaces.bg,
46
- border: surfaces.border,
47
- diffAddBg: diffAddBg(palette),
48
- diffDeleteBg: diffDeleteBg(palette),
49
- elevated: surfaces.elevated,
50
- faint: surfaces.faint,
51
- hover: surfaces.hover,
52
- muted: surfaces.muted,
53
- palette,
54
- selected: surfaces.selected,
55
- }
14
+ interface ThemeStore {
15
+ id: ThemeId
16
+ mode: ThemeMode
17
+ transparent: boolean
56
18
  }
57
19
 
58
- // Memoize by (palette, mode) so `useStore` selectors return stable objects.
59
- let cachedPalette: AimuxPalette | null = null
20
+ const themeStore = createStore<ThemeStore>(() => ({
21
+ id: DEFAULT_ID,
22
+ mode: 'dark',
23
+ transparent: false,
24
+ }))
25
+
26
+ let cachedId: ThemeId | null = null
60
27
  let cachedMode: ThemeMode | null = null
61
- let cachedTokens: ThemeTokens | null = null
28
+ let cachedResolved: ResolvedTuiTheme | null = null
62
29
 
63
- function deriveTokens(palette: AimuxPalette, mode: ThemeMode): ThemeTokens {
64
- if (cachedPalette === palette && cachedMode === mode && cachedTokens) return cachedTokens
65
- cachedPalette = palette
30
+ function derive(id: ThemeId, mode: ThemeMode): ResolvedTuiTheme {
31
+ if (cachedResolved && cachedId === id && cachedMode === mode) return cachedResolved
32
+ cachedId = id
66
33
  cachedMode = mode
67
- cachedTokens = computeTokens(palette, mode)
68
- return cachedTokens
34
+ const json = TUI_THEMES[id] ?? TUI_THEMES.aimux
35
+ if (!json) throw new Error(`No theme JSON for ${id}`)
36
+ cachedResolved = resolveTuiTheme(json, mode)
37
+ return cachedResolved
69
38
  }
70
39
 
71
- interface ThemeStore {
72
- theme: AimuxTheme
73
- transparent: boolean
40
+ /** Subscribe to the resolved TUI theme for the active id+mode. */
41
+ export function useTheme(): ResolvedTuiTheme {
42
+ return useStore(themeStore, (s) => derive(s.id, s.mode))
74
43
  }
75
44
 
76
- const DEFAULT = THEMES['aimux-dark']
77
- if (!DEFAULT) throw new Error('default theme missing')
78
-
79
- const themeStore = createStore<ThemeStore>(() => ({ theme: DEFAULT, transparent: false }))
45
+ /** Synchronous snapshot of the resolved theme for non-React callers. */
46
+ export function getCurrentTheme(): ResolvedTuiTheme {
47
+ const s = themeStore.getState()
48
+ return derive(s.id, s.mode)
49
+ }
80
50
 
81
- /** Subscribe to the active palette plus precomputed derived shades. */
82
- export function useTokens(): ThemeTokens {
83
- return useStore(themeStore, (s) => deriveTokens(s.theme.palette, s.theme.mode))
51
+ export function getCurrentThemeId(): ThemeId {
52
+ return themeStore.getState().id
84
53
  }
85
54
 
86
- /** Synchronous tokens for non-React callers. */
87
- export function getCurrentTokens(): ThemeTokens {
88
- const s = themeStore.getState()
89
- return deriveTokens(s.theme.palette, s.theme.mode)
55
+ export function getCurrentMode(): ThemeMode {
56
+ return themeStore.getState().mode
90
57
  }
91
58
 
92
- /** Synchronous snapshot for non-React callers (reducers, side effects). */
93
- export function getCurrentTheme(): AimuxTheme {
94
- return themeStore.getState().theme
59
+ /** Swap the active theme by id. Falls back via migrateThemeId for legacy ids. */
60
+ export function applyTheme(id: string): void {
61
+ const next = migrateThemeId(id)
62
+ if (themeStore.getState().id === next) return
63
+ themeStore.setState({ id: next })
95
64
  }
96
65
 
97
- /** Swap the active theme by id, optionally merging palette overrides. */
98
- export function applyTheme(id: ThemeId, paletteOverrides?: Partial<AimuxPalette>): void {
99
- const entry = THEMES[id]
100
- if (!entry) return
101
- const palette = extendPalette(entry.palette, paletteOverrides)
102
- const merged: AimuxTheme = {
103
- ...entry,
104
- bg: palette.neutral,
105
- fg: palette.ink,
106
- palette,
107
- }
108
- themeStore.setState({ theme: merged })
66
+ /** Toggle between dark and light. Plumbed but not yet wired to UI controls. */
67
+ export function setMode(mode: ThemeMode): void {
68
+ if (themeStore.getState().mode === mode) return
69
+ themeStore.setState({ mode })
109
70
  }
110
71
 
111
- /** Subscribe to the transparent-mode flag. */
112
72
  export function useTransparent(): boolean {
113
73
  return useStore(themeStore, (s) => s.transparent)
114
74
  }
@@ -121,33 +81,3 @@ export function setTransparent(value: boolean): void {
121
81
  if (themeStore.getState().transparent === value) return
122
82
  themeStore.setState({ transparent: value })
123
83
  }
124
-
125
- export type SurfaceToken = 'base' | 'elevated' | 'hover' | 'selected' | 'border'
126
-
127
- function resolveSurface(palette: AimuxPalette, mode: ThemeMode, token: SurfaceToken): string {
128
- const surfaces = computeSurfaces(palette, mode)
129
- switch (token) {
130
- case 'base':
131
- return surfaces.bg
132
- case 'elevated':
133
- return surfaces.elevated
134
- case 'hover':
135
- return surfaces.hover
136
- case 'selected':
137
- return surfaces.selected
138
- case 'border':
139
- return surfaces.border
140
- }
141
- }
142
-
143
- /**
144
- * Resolve a surface color through the transparent-mode flag. Returns
145
- * `undefined` for the base surface when transparent mode is on, letting the
146
- * terminal emulator's own background show through.
147
- */
148
- export function useBg(token: SurfaceToken): string | undefined {
149
- return useStore(themeStore, (s) => {
150
- if (s.transparent && token === 'base') return undefined
151
- return resolveSurface(s.theme.palette, s.theme.mode, token)
152
- })
153
- }
package/src/ui/theme.ts CHANGED
@@ -1,16 +1,16 @@
1
- // Back-compat shim: the theme singleton has been replaced with a Zustand store.
2
- // React components use `useTokens` for derived shades; non-React callers use
3
- // `getCurrentTheme()` / `getCurrentTokens()`.
1
+ // Back-compat shim: the theme singleton lives in `theme-store.ts`. React
2
+ // components use `useTheme` for the resolved TUI token map; non-React callers
3
+ // use `getCurrentTheme()`.
4
4
 
5
+ export type { ResolvedTuiTheme, TuiColorToken } from './themes'
5
6
  export {
6
7
  applyTheme,
8
+ getCurrentMode,
7
9
  getCurrentTheme,
8
- getCurrentTokens,
10
+ getCurrentThemeId,
9
11
  getTransparent,
12
+ setMode,
10
13
  setTransparent,
11
- type SurfaceToken,
12
- type ThemeTokens,
13
- useBg,
14
- useTokens,
14
+ useTheme,
15
15
  useTransparent,
16
16
  } from './theme-store'
package/src/ui/themes.ts CHANGED
@@ -1,31 +1,20 @@
1
- // Thin runtime re-export. Theme data, types, palette utilities, and the
2
- // palette-to-Shiki generator all live in `@brimveyn/aimux-config`.
1
+ // Thin runtime re-export of the TUI theme namespace.
3
2
 
4
3
  export type {
5
- AimuxPalette,
6
- AimuxTheme,
7
- AimuxThemeConfig,
8
- Theme,
4
+ ResolvedTuiTheme,
5
+ RGBA,
9
6
  ThemeId,
10
7
  ThemeMode,
8
+ TuiColorToken,
9
+ TuiColorValue,
10
+ TuiThemeJson,
11
11
  } from '@brimveyn/aimux-config'
12
12
 
13
13
  export {
14
- accent,
15
- border,
16
- computeSurfaces,
17
- diffAddBg,
18
- diffDeleteBg,
19
- elevated,
20
- extendPalette,
21
- faint,
22
- hover,
23
14
  isKnownThemeId,
24
15
  migrateThemeId,
25
- mix,
26
- muted,
27
- paletteToShikiTheme,
28
- selected,
16
+ resolveTuiTheme,
29
17
  THEME_IDS,
30
- THEMES,
18
+ TUI_COLOR_TOKENS,
19
+ TUI_THEMES,
31
20
  } from '@brimveyn/aimux-config'