@brimveyn/aimux 1.9.0 → 1.9.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.
Files changed (67) hide show
  1. package/package.json +2 -2
  2. package/src/app-runtime/side-effects.ts +6 -1
  3. package/src/app.tsx +20 -16
  4. package/src/input/keymap/describe-bindings.ts +63 -2
  5. package/src/pty/terminal-snapshot.ts +7 -5
  6. package/src/state/reducers/git-mode-state.ts +58 -0
  7. package/src/state/types.ts +11 -6
  8. package/src/ui/breaking-update-screen.tsx +2 -2
  9. package/src/ui/components/git/diff-renderer/build-rows.ts +467 -0
  10. package/src/ui/components/{diff-renderer → git/diff-renderer}/fold-strip.tsx +1 -1
  11. package/src/ui/components/git/diff-renderer/highlight.ts +102 -0
  12. package/src/ui/components/{diff-renderer → git/diff-renderer}/pierre-diff.tsx +23 -15
  13. package/src/ui/components/git/diff-renderer/prepare-diff.ts +62 -0
  14. package/src/ui/components/{diff-renderer → git/diff-renderer}/split-view.tsx +122 -47
  15. package/src/ui/components/{diff-renderer → git/diff-renderer}/stacked-view.tsx +106 -35
  16. package/src/ui/components/{diff-renderer → git/diff-renderer}/use-diff-prefetch.ts +8 -17
  17. package/src/ui/components/git/diff-renderer/use-diff-preparation.ts +240 -0
  18. package/src/ui/components/git/diff-renderer/use-segment-virtualization.ts +95 -0
  19. package/src/ui/components/{git-panel.tsx → git/git-panel.tsx} +9 -5
  20. package/src/ui/components/{git-view.tsx → git/git-view.tsx} +70 -37
  21. package/src/ui/components/{git-pane-context-menu.ts → git/pane/git-pane-context-menu.ts} +2 -2
  22. package/src/ui/components/{git-pane-widget.tsx → git/pane/git-pane-widget.tsx} +4 -4
  23. package/src/ui/components/{session-bar.tsx → layout/session-bar.tsx} +36 -13
  24. package/src/ui/components/{sidebar-group-metadata.ts → layout/sidebar/sidebar-group-metadata.ts} +2 -2
  25. package/src/ui/components/{sidebar.tsx → layout/sidebar/sidebar.tsx} +6 -6
  26. package/src/ui/components/{tab-item.tsx → layout/sidebar/tab-item.tsx} +5 -5
  27. package/src/ui/components/{use-sidebar-branch.ts → layout/sidebar/use-sidebar-branch.ts} +1 -1
  28. package/src/ui/components/{split-layout.tsx → layout/split-layout.tsx} +5 -5
  29. package/src/ui/components/{status-bar.tsx → layout/status-bar.tsx} +7 -7
  30. package/src/ui/components/{terminal-pane.tsx → layout/terminal-pane.tsx} +8 -8
  31. package/src/ui/components/{ai-usage-modal.tsx → modals/app/ai-usage-modal.tsx} +10 -6
  32. package/src/ui/components/{help-modal.tsx → modals/app/help-modal.tsx} +6 -6
  33. package/src/ui/components/{update-available-modal.tsx → modals/app/update-available-modal.tsx} +4 -4
  34. package/src/ui/components/{git-commit-modal.tsx → modals/git/git-commit-modal.tsx} +8 -8
  35. package/src/ui/components/modals/sessions/create-session-modal.tsx +79 -0
  36. package/src/ui/components/modals/sessions/session-name-modal.tsx +10 -0
  37. package/src/ui/components/{session-picker-modal.tsx → modals/sessions/session-picker-modal.tsx} +8 -8
  38. package/src/ui/components/modals/shared/form.tsx +164 -0
  39. package/src/ui/components/{modal-keybinds-overlay.tsx → modals/shared/modal-keybinds-overlay.tsx} +7 -7
  40. package/src/ui/components/{modal-shell.tsx → modals/shared/modal-shell.tsx} +1 -1
  41. package/src/ui/components/{picker.tsx → modals/shared/picker.tsx} +3 -3
  42. package/src/ui/components/modals/snippets/snippet-editor-modal.tsx +30 -0
  43. package/src/ui/components/{snippet-picker-modal.tsx → modals/snippets/snippet-picker-modal.tsx} +6 -6
  44. package/src/ui/components/{new-tab-modal.tsx → modals/tabs/new-tab-modal.tsx} +17 -20
  45. package/src/ui/components/{theme-picker-modal.tsx → modals/themes/theme-picker-modal.tsx} +6 -6
  46. package/src/ui/components/{ai-usage-indicator.tsx → overlays/ai-usage/ai-usage-indicator.tsx} +3 -3
  47. package/src/ui/components/{context-menu-box.tsx → overlays/context-menu/context-menu-box.tsx} +1 -1
  48. package/src/ui/components/{context-menu-overlay.tsx → overlays/context-menu/context-menu-overlay.tsx} +3 -3
  49. package/src/ui/components/{pending-chord-overlay.tsx → overlays/pending-chord-overlay.tsx} +6 -6
  50. package/src/ui/components/{bare-input.tsx → primitives/bare-input.tsx} +2 -2
  51. package/src/ui/components/{input-field.tsx → primitives/input-field.tsx} +2 -2
  52. package/src/ui/components/{list-item.tsx → primitives/list-item.tsx} +1 -1
  53. package/src/ui/components/{surface.tsx → primitives/surface.tsx} +1 -1
  54. package/src/ui/root.tsx +24 -22
  55. package/src/ui/theme-store.ts +30 -26
  56. package/src/ui/themes.ts +1 -0
  57. package/src/ui/components/create-session-modal.tsx +0 -96
  58. package/src/ui/components/diff-renderer/build-rows.ts +0 -349
  59. package/src/ui/components/diff-renderer/highlight.ts +0 -44
  60. package/src/ui/components/diff-renderer/prepare-diff.ts +0 -65
  61. package/src/ui/components/diff-renderer/use-diff-preparation.ts +0 -106
  62. package/src/ui/components/session-name-modal.tsx +0 -11
  63. package/src/ui/components/snippet-editor-modal.tsx +0 -40
  64. /package/src/ui/components/{diff-renderer → git/diff-renderer}/filetype.ts +0 -0
  65. /package/src/ui/components/{diff-renderer → git/diff-renderer}/index.ts +0 -0
  66. /package/src/ui/components/{sidebar-scroll.ts → layout/sidebar/sidebar-scroll.ts} +0 -0
  67. /package/src/ui/components/{use-sidebar-auto-scroll.ts → layout/sidebar/use-sidebar-auto-scroll.ts} +0 -0
@@ -1,13 +1,13 @@
1
1
  import { useKeyboard } from '@opentui/react'
2
2
  import { useEffect, useState } from 'react'
3
3
 
4
- import { useAppStore } from '../../state/app-store'
4
+ import { useAppStore } from '../../../../state/app-store'
5
5
  import {
6
6
  closeContextMenu,
7
7
  type ContextMenuState,
8
8
  subscribeContextMenu,
9
- } from '../context-menu/controller'
10
- import { useTokens } from '../theme'
9
+ } from '../../../context-menu/controller'
10
+ import { useTokens } from '../../../theme'
11
11
 
12
12
  export function ContextMenuOverlay() {
13
13
  const [menu, setMenu] = useState<ContextMenuState | null>(null)
@@ -1,9 +1,9 @@
1
- import { parseKeyNotation } from '../../input/keymap/key-chord'
2
- import { formatChord } from '../../input/keymap/key-format'
3
- import { useAppStore } from '../../state/app-store'
4
- import { useKeymap } from '../keymap-context'
5
- import { useTokens } from '../theme'
6
- import { Surface } from './surface'
1
+ import { parseKeyNotation } from '../../../input/keymap/key-chord'
2
+ import { formatChord } from '../../../input/keymap/key-format'
3
+ import { useAppStore } from '../../../state/app-store'
4
+ import { useKeymap } from '../../keymap-context'
5
+ import { useTokens } from '../../theme'
6
+ import { Surface } from '../primitives/surface'
7
7
 
8
8
  export function PendingChordOverlay() {
9
9
  const t = useTokens()
@@ -1,4 +1,4 @@
1
- import { useTokens } from '../theme'
1
+ import { useTokens } from '../../theme'
2
2
 
3
3
  interface BareInputProps {
4
4
  value: string
@@ -9,7 +9,7 @@ interface BareInputProps {
9
9
  export function BareInput({ cursorPos, placeholder = '', value }: BareInputProps) {
10
10
  const t = useTokens()
11
11
  const fg = t.palette.ink
12
- const bg = t.palette.neutral
12
+ const bg = t.bg
13
13
  const placeholderFg = t.faint
14
14
 
15
15
  if (!value) {
@@ -1,4 +1,4 @@
1
- import { useTokens } from '../theme'
1
+ import { useTokens } from '../../theme'
2
2
  import { Surface } from './surface'
3
3
 
4
4
  interface InputFieldProps {
@@ -32,7 +32,7 @@ export function InputField({ active, cursorPos, placeholder, value }: InputField
32
32
  <Surface tone="inputActive" padding={1}>
33
33
  <text fg={fg}>
34
34
  {before}
35
- <span bg={t.palette.ink} fg={t.palette.neutral}>
35
+ <span bg={t.palette.ink} fg={t.bg}>
36
36
  {cursorDisplay}
37
37
  </span>
38
38
  {trailing}
@@ -1,6 +1,6 @@
1
1
  import type { ReactNode } from 'react'
2
2
 
3
- import { useTokens } from '../theme'
3
+ import { useTokens } from '../../theme'
4
4
  import { Surface } from './surface'
5
5
 
6
6
  export type ListItemDirection = 'row' | 'column'
@@ -1,6 +1,6 @@
1
1
  import type { ReactNode } from 'react'
2
2
 
3
- import { type SurfaceToken, useBg } from '../theme'
3
+ import { type SurfaceToken, useBg } from '../../theme'
4
4
 
5
5
  type SurfaceTone = 'muted' | 'elevated' | 'selected' | 'input' | 'inputActive'
6
6
 
package/src/ui/root.tsx CHANGED
@@ -8,28 +8,28 @@ import { useAppStore } from '../state/app-store'
8
8
  import { dispatchGlobal } from '../state/dispatch-ref'
9
9
  import { getGitPaneWidthFromRatio } from '../state/git-pane-sizing'
10
10
  import { getTreeForTab, PANE_BORDER, type SplitDirection } from '../state/layout-tree'
11
- import { AIUsageModal } from './components/ai-usage-modal'
12
- import { ContextMenuBox } from './components/context-menu-box'
13
- import { ContextMenuOverlay } from './components/context-menu-overlay'
14
- import { CreateSessionModal } from './components/create-session-modal'
15
- import { GitCommitModal } from './components/git-commit-modal'
16
- import { buildGitPaneContextMenu } from './components/git-pane-context-menu'
17
- import { GitPaneWidget } from './components/git-pane-widget'
18
- import { GitView } from './components/git-view'
19
- import { HelpModal } from './components/help-modal'
20
- import { NewTabModal } from './components/new-tab-modal'
21
- import { PendingChordOverlay } from './components/pending-chord-overlay'
22
- import { SessionBar } from './components/session-bar'
23
- import { SessionNameModal } from './components/session-name-modal'
24
- import { SessionPickerModal } from './components/session-picker-modal'
25
- import { Sidebar } from './components/sidebar'
26
- import { SnippetEditorModal } from './components/snippet-editor-modal'
27
- import { SnippetPickerModal } from './components/snippet-picker-modal'
28
- import { SplitLayout } from './components/split-layout'
29
- import { StatusBar } from './components/status-bar'
30
- import { TerminalPane } from './components/terminal-pane'
31
- import { ThemePickerModal } from './components/theme-picker-modal'
32
- import { UpdateAvailableModal } from './components/update-available-modal'
11
+ import { GitView } from './components/git/git-view'
12
+ import { buildGitPaneContextMenu } from './components/git/pane/git-pane-context-menu'
13
+ import { GitPaneWidget } from './components/git/pane/git-pane-widget'
14
+ import { SessionBar } from './components/layout/session-bar'
15
+ import { Sidebar } from './components/layout/sidebar/sidebar'
16
+ import { SplitLayout } from './components/layout/split-layout'
17
+ import { StatusBar } from './components/layout/status-bar'
18
+ import { TerminalPane } from './components/layout/terminal-pane'
19
+ import { AIUsageModal } from './components/modals/app/ai-usage-modal'
20
+ import { HelpModal } from './components/modals/app/help-modal'
21
+ import { UpdateAvailableModal } from './components/modals/app/update-available-modal'
22
+ import { GitCommitModal } from './components/modals/git/git-commit-modal'
23
+ import { CreateSessionModal } from './components/modals/sessions/create-session-modal'
24
+ import { SessionNameModal } from './components/modals/sessions/session-name-modal'
25
+ import { SessionPickerModal } from './components/modals/sessions/session-picker-modal'
26
+ import { SnippetEditorModal } from './components/modals/snippets/snippet-editor-modal'
27
+ import { SnippetPickerModal } from './components/modals/snippets/snippet-picker-modal'
28
+ import { NewTabModal } from './components/modals/tabs/new-tab-modal'
29
+ import { ThemePickerModal } from './components/modals/themes/theme-picker-modal'
30
+ import { ContextMenuBox } from './components/overlays/context-menu/context-menu-box'
31
+ import { ContextMenuOverlay } from './components/overlays/context-menu/context-menu-overlay'
32
+ import { PendingChordOverlay } from './components/overlays/pending-chord-overlay'
33
33
  import { useBg, useTokens } from './theme'
34
34
 
35
35
  function getCreateSessionFields(modal: ModalState) {
@@ -277,7 +277,9 @@ export function RootView({
277
277
  if (inGitMode) {
278
278
  return (
279
279
  <box flexDirection="column" width="100%" height="100%" backgroundColor={editorBg}>
280
+ {sessionBarPosition === 'top' && <SessionBar forceVisible />}
280
281
  <GitView themeId={themeId} />
282
+ {sessionBarPosition === 'bottom' && <SessionBar forceVisible />}
281
283
  <StatusBar />
282
284
  <PendingChordOverlay />
283
285
  <ContextMenuOverlay />
@@ -5,22 +5,20 @@ import {
5
5
  accent,
6
6
  type AimuxPalette,
7
7
  type AimuxTheme,
8
- border,
8
+ computeSurfaces,
9
9
  diffAddBg,
10
10
  diffDeleteBg,
11
- elevated,
12
11
  extendPalette,
13
- faint,
14
- hover,
15
- muted,
16
- selected,
17
12
  type ThemeId,
13
+ type ThemeMode,
18
14
  THEMES,
19
15
  } from './themes'
20
16
 
21
17
  export interface ThemeTokens {
22
18
  /** `accent` with `primary` fallback. */
23
19
  accent: string
20
+ /** App-level background surface (derived from palette.neutral seed via OKLCH). */
21
+ bg: string
24
22
  /** Subtle border between panels. */
25
23
  border: string
26
24
  /** Background tint for inserted diff lines. */
@@ -40,29 +38,33 @@ export interface ThemeTokens {
40
38
  selected: string
41
39
  }
42
40
 
43
- function computeTokens(palette: AimuxPalette): ThemeTokens {
41
+ function computeTokens(palette: AimuxPalette, mode: ThemeMode): ThemeTokens {
42
+ const surfaces = computeSurfaces(palette, mode)
44
43
  return {
45
44
  accent: accent(palette),
46
- border: border(palette),
45
+ bg: surfaces.bg,
46
+ border: surfaces.border,
47
47
  diffAddBg: diffAddBg(palette),
48
48
  diffDeleteBg: diffDeleteBg(palette),
49
- elevated: elevated(palette),
50
- faint: faint(palette),
51
- hover: hover(palette),
52
- muted: muted(palette),
49
+ elevated: surfaces.elevated,
50
+ faint: surfaces.faint,
51
+ hover: surfaces.hover,
52
+ muted: surfaces.muted,
53
53
  palette,
54
- selected: selected(palette),
54
+ selected: surfaces.selected,
55
55
  }
56
56
  }
57
57
 
58
- // Memoize by palette reference so `useStore` selectors return stable objects.
58
+ // Memoize by (palette, mode) so `useStore` selectors return stable objects.
59
59
  let cachedPalette: AimuxPalette | null = null
60
+ let cachedMode: ThemeMode | null = null
60
61
  let cachedTokens: ThemeTokens | null = null
61
62
 
62
- function deriveTokens(palette: AimuxPalette): ThemeTokens {
63
- if (cachedPalette === palette && cachedTokens) return cachedTokens
63
+ function deriveTokens(palette: AimuxPalette, mode: ThemeMode): ThemeTokens {
64
+ if (cachedPalette === palette && cachedMode === mode && cachedTokens) return cachedTokens
64
65
  cachedPalette = palette
65
- cachedTokens = computeTokens(palette)
66
+ cachedMode = mode
67
+ cachedTokens = computeTokens(palette, mode)
66
68
  return cachedTokens
67
69
  }
68
70
 
@@ -78,12 +80,13 @@ const themeStore = createStore<ThemeStore>(() => ({ theme: DEFAULT, transparent:
78
80
 
79
81
  /** Subscribe to the active palette plus precomputed derived shades. */
80
82
  export function useTokens(): ThemeTokens {
81
- return useStore(themeStore, (s) => deriveTokens(s.theme.palette))
83
+ return useStore(themeStore, (s) => deriveTokens(s.theme.palette, s.theme.mode))
82
84
  }
83
85
 
84
86
  /** Synchronous tokens for non-React callers. */
85
87
  export function getCurrentTokens(): ThemeTokens {
86
- return deriveTokens(themeStore.getState().theme.palette)
88
+ const s = themeStore.getState()
89
+ return deriveTokens(s.theme.palette, s.theme.mode)
87
90
  }
88
91
 
89
92
  /** Synchronous snapshot for non-React callers (reducers, side effects). */
@@ -121,18 +124,19 @@ export function setTransparent(value: boolean): void {
121
124
 
122
125
  export type SurfaceToken = 'base' | 'elevated' | 'hover' | 'selected' | 'border'
123
126
 
124
- function resolveSurface(palette: AimuxPalette, token: SurfaceToken): string {
127
+ function resolveSurface(palette: AimuxPalette, mode: ThemeMode, token: SurfaceToken): string {
128
+ const surfaces = computeSurfaces(palette, mode)
125
129
  switch (token) {
126
130
  case 'base':
127
- return palette.neutral
131
+ return surfaces.bg
128
132
  case 'elevated':
129
- return elevated(palette)
133
+ return surfaces.elevated
130
134
  case 'hover':
131
- return hover(palette)
135
+ return surfaces.hover
132
136
  case 'selected':
133
- return selected(palette)
137
+ return surfaces.selected
134
138
  case 'border':
135
- return border(palette)
139
+ return surfaces.border
136
140
  }
137
141
  }
138
142
 
@@ -144,6 +148,6 @@ function resolveSurface(palette: AimuxPalette, token: SurfaceToken): string {
144
148
  export function useBg(token: SurfaceToken): string | undefined {
145
149
  return useStore(themeStore, (s) => {
146
150
  if (s.transparent && token === 'base') return undefined
147
- return resolveSurface(s.theme.palette, token)
151
+ return resolveSurface(s.theme.palette, s.theme.mode, token)
148
152
  })
149
153
  }
package/src/ui/themes.ts CHANGED
@@ -13,6 +13,7 @@ export type {
13
13
  export {
14
14
  accent,
15
15
  border,
16
+ computeSurfaces,
16
17
  diffAddBg,
17
18
  diffDeleteBg,
18
19
  elevated,
@@ -1,96 +0,0 @@
1
- import type { DirectoryResult } from '../../state/types'
2
-
3
- import { abbreviatePath } from '../path-format'
4
- import { getCurrentTokens, useTokens } from '../theme'
5
- import { uiTokens } from '../ui-tokens'
6
- import { InputField } from './input-field'
7
- import { ListItem } from './list-item'
8
- import { ModalShell } from './modal-shell'
9
-
10
- const VISIBLE_ROWS = 8
11
-
12
- function getDirectoryResultIcon(result: DirectoryResult): string {
13
- if (result.type === 'worktree') return '\u{e728}'
14
- if (result.type === 'workspace') return '\u{f07c}'
15
- return '\u{e702}'
16
- }
17
-
18
- function getDirectoryResultColor(result: DirectoryResult): string {
19
- const t = getCurrentTokens()
20
- if (result.type === 'worktree') return t.palette.warning
21
- if (result.type === 'workspace') return t.accent
22
- return t.palette.primary
23
- }
24
-
25
- interface CreateSessionModalProps {
26
- activeField: 'directory' | 'name'
27
- directoryQuery: string
28
- sessionName: string
29
- results: DirectoryResult[]
30
- selectedIndex: number
31
- pendingProjectPath: string | null
32
- }
33
-
34
- export function CreateSessionModal({
35
- activeField,
36
- directoryQuery,
37
- pendingProjectPath,
38
- results,
39
- selectedIndex,
40
- sessionName,
41
- }: CreateSessionModalProps) {
42
- const t = useTokens()
43
- const dirActive = activeField === 'directory'
44
- const nameActive = activeField === 'name'
45
-
46
- const scrollOffset = Math.max(0, selectedIndex - VISIBLE_ROWS + 1)
47
- const visibleResults = results.slice(scrollOffset, scrollOffset + VISIBLE_ROWS)
48
-
49
- return (
50
- <ModalShell
51
- title="Create workspace"
52
- keybindsModeId="modal.create-session"
53
- width={uiTokens.modalWidth.xl}
54
- >
55
- <box flexDirection="column">
56
- <text fg={dirActive ? t.palette.ink : t.muted}>Search projects</text>
57
- <InputField
58
- active={dirActive}
59
- placeholder="Type a project name..."
60
- value={
61
- pendingProjectPath && !dirActive ? abbreviatePath(pendingProjectPath) : directoryQuery
62
- }
63
- />
64
- </box>
65
-
66
- <box flexDirection="column" height={VISIBLE_ROWS}>
67
- {results.length === 0 ? (
68
- <text fg={t.muted}>
69
- {directoryQuery.length > 0 ? 'No matches' : 'Type a project name to search...'}
70
- </text>
71
- ) : (
72
- visibleResults.map((result, index) => {
73
- const active = dirActive && scrollOffset + index === selectedIndex
74
- return (
75
- <ListItem
76
- key={result.path}
77
- active={active}
78
- leading={
79
- <text fg={getDirectoryResultColor(result)}>{getDirectoryResultIcon(result)}</text>
80
- }
81
- title={
82
- <text fg={active ? t.palette.ink : t.muted}>{abbreviatePath(result.path)}</text>
83
- }
84
- />
85
- )
86
- })
87
- )}
88
- </box>
89
-
90
- <box flexDirection="column">
91
- <text fg={nameActive ? t.palette.ink : t.muted}>Workspace name</text>
92
- <InputField active={nameActive} value={sessionName} />
93
- </box>
94
- </ModalShell>
95
- )
96
- }