@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.
- package/package.json +2 -2
- package/src/app-runtime/side-effects.ts +6 -1
- package/src/app.tsx +20 -16
- package/src/input/keymap/describe-bindings.ts +63 -2
- package/src/pty/terminal-snapshot.ts +7 -5
- package/src/state/reducers/git-mode-state.ts +58 -0
- package/src/state/types.ts +11 -6
- package/src/ui/breaking-update-screen.tsx +2 -2
- package/src/ui/components/git/diff-renderer/build-rows.ts +467 -0
- package/src/ui/components/{diff-renderer → git/diff-renderer}/fold-strip.tsx +1 -1
- package/src/ui/components/git/diff-renderer/highlight.ts +102 -0
- package/src/ui/components/{diff-renderer → git/diff-renderer}/pierre-diff.tsx +23 -15
- package/src/ui/components/git/diff-renderer/prepare-diff.ts +62 -0
- package/src/ui/components/{diff-renderer → git/diff-renderer}/split-view.tsx +122 -47
- package/src/ui/components/{diff-renderer → git/diff-renderer}/stacked-view.tsx +106 -35
- package/src/ui/components/{diff-renderer → git/diff-renderer}/use-diff-prefetch.ts +8 -17
- package/src/ui/components/git/diff-renderer/use-diff-preparation.ts +240 -0
- package/src/ui/components/git/diff-renderer/use-segment-virtualization.ts +95 -0
- package/src/ui/components/{git-panel.tsx → git/git-panel.tsx} +9 -5
- package/src/ui/components/{git-view.tsx → git/git-view.tsx} +70 -37
- package/src/ui/components/{git-pane-context-menu.ts → git/pane/git-pane-context-menu.ts} +2 -2
- package/src/ui/components/{git-pane-widget.tsx → git/pane/git-pane-widget.tsx} +4 -4
- package/src/ui/components/{session-bar.tsx → layout/session-bar.tsx} +36 -13
- package/src/ui/components/{sidebar-group-metadata.ts → layout/sidebar/sidebar-group-metadata.ts} +2 -2
- package/src/ui/components/{sidebar.tsx → layout/sidebar/sidebar.tsx} +6 -6
- package/src/ui/components/{tab-item.tsx → layout/sidebar/tab-item.tsx} +5 -5
- package/src/ui/components/{use-sidebar-branch.ts → layout/sidebar/use-sidebar-branch.ts} +1 -1
- package/src/ui/components/{split-layout.tsx → layout/split-layout.tsx} +5 -5
- package/src/ui/components/{status-bar.tsx → layout/status-bar.tsx} +7 -7
- package/src/ui/components/{terminal-pane.tsx → layout/terminal-pane.tsx} +8 -8
- package/src/ui/components/{ai-usage-modal.tsx → modals/app/ai-usage-modal.tsx} +10 -6
- package/src/ui/components/{help-modal.tsx → modals/app/help-modal.tsx} +6 -6
- package/src/ui/components/{update-available-modal.tsx → modals/app/update-available-modal.tsx} +4 -4
- package/src/ui/components/{git-commit-modal.tsx → modals/git/git-commit-modal.tsx} +8 -8
- package/src/ui/components/modals/sessions/create-session-modal.tsx +79 -0
- package/src/ui/components/modals/sessions/session-name-modal.tsx +10 -0
- package/src/ui/components/{session-picker-modal.tsx → modals/sessions/session-picker-modal.tsx} +8 -8
- package/src/ui/components/modals/shared/form.tsx +164 -0
- package/src/ui/components/{modal-keybinds-overlay.tsx → modals/shared/modal-keybinds-overlay.tsx} +7 -7
- package/src/ui/components/{modal-shell.tsx → modals/shared/modal-shell.tsx} +1 -1
- package/src/ui/components/{picker.tsx → modals/shared/picker.tsx} +3 -3
- package/src/ui/components/modals/snippets/snippet-editor-modal.tsx +30 -0
- package/src/ui/components/{snippet-picker-modal.tsx → modals/snippets/snippet-picker-modal.tsx} +6 -6
- package/src/ui/components/{new-tab-modal.tsx → modals/tabs/new-tab-modal.tsx} +17 -20
- package/src/ui/components/{theme-picker-modal.tsx → modals/themes/theme-picker-modal.tsx} +6 -6
- package/src/ui/components/{ai-usage-indicator.tsx → overlays/ai-usage/ai-usage-indicator.tsx} +3 -3
- package/src/ui/components/{context-menu-box.tsx → overlays/context-menu/context-menu-box.tsx} +1 -1
- package/src/ui/components/{context-menu-overlay.tsx → overlays/context-menu/context-menu-overlay.tsx} +3 -3
- package/src/ui/components/{pending-chord-overlay.tsx → overlays/pending-chord-overlay.tsx} +6 -6
- package/src/ui/components/{bare-input.tsx → primitives/bare-input.tsx} +2 -2
- package/src/ui/components/{input-field.tsx → primitives/input-field.tsx} +2 -2
- package/src/ui/components/{list-item.tsx → primitives/list-item.tsx} +1 -1
- package/src/ui/components/{surface.tsx → primitives/surface.tsx} +1 -1
- package/src/ui/root.tsx +24 -22
- package/src/ui/theme-store.ts +30 -26
- package/src/ui/themes.ts +1 -0
- package/src/ui/components/create-session-modal.tsx +0 -96
- package/src/ui/components/diff-renderer/build-rows.ts +0 -349
- package/src/ui/components/diff-renderer/highlight.ts +0 -44
- package/src/ui/components/diff-renderer/prepare-diff.ts +0 -65
- package/src/ui/components/diff-renderer/use-diff-preparation.ts +0 -106
- package/src/ui/components/session-name-modal.tsx +0 -11
- package/src/ui/components/snippet-editor-modal.tsx +0 -40
- /package/src/ui/components/{diff-renderer → git/diff-renderer}/filetype.ts +0 -0
- /package/src/ui/components/{diff-renderer → git/diff-renderer}/index.ts +0 -0
- /package/src/ui/components/{sidebar-scroll.ts → layout/sidebar/sidebar-scroll.ts} +0 -0
- /package/src/ui/components/{use-sidebar-auto-scroll.ts → layout/sidebar/use-sidebar-auto-scroll.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brimveyn/aimux",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.1",
|
|
4
4
|
"description": "A terminal multiplexer for AI CLIs. Run Claude, Codex, OpenCode side-by-side with tabbed navigation, split panes, and persistent sessions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"bump": "bun run scripts/bump.ts"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@brimveyn/aimux-config": "0.5.
|
|
63
|
+
"@brimveyn/aimux-config": "0.5.2",
|
|
64
64
|
"@opentui/core": "^0.1.90",
|
|
65
65
|
"@opentui/react": "^0.1.90",
|
|
66
66
|
"@xterm/headless": "^6.0.0",
|
|
@@ -605,7 +605,12 @@ function handleSwitchSessionByIndex(ctx: SideEffectContext, index: number): void
|
|
|
605
605
|
logInputDebug('app.sessionBar.switchOutOfRange', { index, total: ordered.length })
|
|
606
606
|
return
|
|
607
607
|
}
|
|
608
|
-
if (target.id === state.currentSessionId)
|
|
608
|
+
if (target.id === state.currentSessionId) {
|
|
609
|
+
if (state.focusMode === 'git') {
|
|
610
|
+
dispatch({ type: 'exit-git-mode' })
|
|
611
|
+
}
|
|
612
|
+
return
|
|
613
|
+
}
|
|
609
614
|
handleSwitchSessionEffect(state, backend, dispatch, target)
|
|
610
615
|
}
|
|
611
616
|
|
package/src/app.tsx
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
import { type ResolvedConfig, setAutoCommitEnabled } from '@brimveyn/aimux-config'
|
|
2
2
|
import { useKeyboard, useRenderer, useTerminalDimensions } from '@opentui/react'
|
|
3
|
-
import {
|
|
4
|
-
useCallback,
|
|
5
|
-
useEffect,
|
|
6
|
-
useLayoutEffect,
|
|
7
|
-
useMemo,
|
|
8
|
-
useReducer,
|
|
9
|
-
useRef,
|
|
10
|
-
useState,
|
|
11
|
-
} from 'react'
|
|
3
|
+
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
|
12
4
|
|
|
13
5
|
import type { KeyChord } from './input/keymap/key-chord'
|
|
14
6
|
import type { TrieBinding } from './input/keymap/trie'
|
|
@@ -32,11 +24,11 @@ import { type TerminalContentOrigin } from './input/raw-input-handler'
|
|
|
32
24
|
import { getProfileConfigDir, getProfileName } from './profile-paths'
|
|
33
25
|
import { startAIUsageService } from './services/ai-usage/provider'
|
|
34
26
|
import { aiUsageStore } from './state/ai-usage-store'
|
|
35
|
-
import { appStore } from './state/app-store'
|
|
27
|
+
import { appStore, useAppStore } from './state/app-store'
|
|
36
28
|
import { setActiveDispatch, setActiveSideEffectRunner } from './state/dispatch-ref'
|
|
37
29
|
import { loadSessionCatalog } from './state/session-catalog'
|
|
38
30
|
import { loadSnippetCatalog } from './state/snippet-catalog'
|
|
39
|
-
import {
|
|
31
|
+
import { createInitialState } from './state/store'
|
|
40
32
|
import { KeymapContext } from './ui/keymap-context'
|
|
41
33
|
import { RootView } from './ui/root'
|
|
42
34
|
import { applyTheme, setTransparent } from './ui/theme'
|
|
@@ -81,7 +73,10 @@ export function App({
|
|
|
81
73
|
setTransparent(config.themeTransparent ?? false)
|
|
82
74
|
return initial
|
|
83
75
|
})
|
|
84
|
-
|
|
76
|
+
// Initialize zustand store with computed initial state on first render. Using
|
|
77
|
+
// useState's lazy initializer guarantees this runs exactly once before any
|
|
78
|
+
// selector reads. We discard the value — the store is the source of truth.
|
|
79
|
+
useState(() => {
|
|
85
80
|
const json = loadConfig()
|
|
86
81
|
const sessionBarVisible =
|
|
87
82
|
resolvedConfig.sessionBar?.initialVisible ?? json.sessionBarVisible ?? true
|
|
@@ -125,7 +120,7 @@ export function App({
|
|
|
125
120
|
...(userGitPane?.diffCount !== undefined ? { diffCount: userGitPane.diffCount } : {}),
|
|
126
121
|
}
|
|
127
122
|
|
|
128
|
-
|
|
123
|
+
const initial = createInitialState(
|
|
129
124
|
json.customCommands,
|
|
130
125
|
loadSessionCatalog(),
|
|
131
126
|
loadSnippetCatalog(),
|
|
@@ -137,11 +132,18 @@ export function App({
|
|
|
137
132
|
sidebar: sidebarOverrides,
|
|
138
133
|
}
|
|
139
134
|
)
|
|
135
|
+
// Replace the module-level default with the fully-resolved initial state.
|
|
136
|
+
// Preserves the dispatch baked into the store by app-store.ts.
|
|
137
|
+
appStore.setState(initial)
|
|
138
|
+
return null
|
|
140
139
|
})
|
|
141
140
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
141
|
+
// Single source of truth: read state from zustand. The selector returns the
|
|
142
|
+
// entire store object, so this component re-renders on every dispatch (same
|
|
143
|
+
// cadence as the previous useReducer setup), but selectors elsewhere only
|
|
144
|
+
// re-render on the slices they actually subscribe to.
|
|
145
|
+
const state = useAppStore((s) => s)
|
|
146
|
+
const dispatch = state.dispatch
|
|
145
147
|
|
|
146
148
|
useLayoutEffect(() => {
|
|
147
149
|
setActiveDispatch(dispatch)
|
|
@@ -191,6 +193,8 @@ export function App({
|
|
|
191
193
|
return () => {
|
|
192
194
|
cancelled = true
|
|
193
195
|
}
|
|
196
|
+
// dispatch is stable (same fn reference from the store) — safe to omit
|
|
197
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
194
198
|
}, [])
|
|
195
199
|
|
|
196
200
|
const resizingRef = useRef(false)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ModeId, ResolvedKeymapConfig } from '@brimveyn/aimux-config'
|
|
1
|
+
import type { Action, ModeId, ResolvedKeymapConfig } from '@brimveyn/aimux-config'
|
|
2
2
|
|
|
3
3
|
import { parseKeyNotation } from './key-chord'
|
|
4
4
|
import { formatNotationForDisplay } from './key-format'
|
|
@@ -18,6 +18,24 @@ export interface DescribedBindingGroup {
|
|
|
18
18
|
interface DescribeOptions {
|
|
19
19
|
withDescriptionOnly?: boolean
|
|
20
20
|
dedupeByDescription?: boolean
|
|
21
|
+
mergeAlternativesByDescription?: boolean
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const actionObjectIds = new WeakMap<object, number>()
|
|
25
|
+
let nextActionObjectId = 0
|
|
26
|
+
|
|
27
|
+
function getActionSignature(action: Action): string {
|
|
28
|
+
if (typeof action === 'function') {
|
|
29
|
+
let id = actionObjectIds.get(action)
|
|
30
|
+
if (id === undefined) {
|
|
31
|
+
id = nextActionObjectId
|
|
32
|
+
nextActionObjectId += 1
|
|
33
|
+
actionObjectIds.set(action, id)
|
|
34
|
+
}
|
|
35
|
+
return `fn:${id}`
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return `json:${JSON.stringify(action)}`
|
|
21
39
|
}
|
|
22
40
|
|
|
23
41
|
export function describeBindings(
|
|
@@ -31,9 +49,52 @@ export function describeBindings(
|
|
|
31
49
|
const leaderChord = parseKeyNotation(config.leader)[0]
|
|
32
50
|
const seenDescriptions = new Set<string>()
|
|
33
51
|
const result: DescribedBinding[] = []
|
|
52
|
+
const resultIndexByDescription = new Map<string, number>()
|
|
53
|
+
const resultIndexByAction = new Map<string, number>()
|
|
54
|
+
const seenKeysDisplayByIndex = new Map<number, Set<string>>()
|
|
34
55
|
|
|
35
56
|
for (const binding of mode.bindings) {
|
|
57
|
+
const keysDisplay = formatNotationForDisplay(binding.keys, leaderChord)
|
|
58
|
+
|
|
59
|
+
if (options.mergeAlternativesByDescription) {
|
|
60
|
+
const actionSignature = getActionSignature(binding.result)
|
|
61
|
+
const existingIndex = binding.description
|
|
62
|
+
? resultIndexByDescription.get(binding.description)
|
|
63
|
+
: resultIndexByAction.get(actionSignature)
|
|
64
|
+
|
|
65
|
+
if (existingIndex === undefined) {
|
|
66
|
+
if (options.withDescriptionOnly && !binding.description) continue
|
|
67
|
+
const nextIndex = result.length
|
|
68
|
+
if (binding.description) resultIndexByDescription.set(binding.description, nextIndex)
|
|
69
|
+
resultIndexByAction.set(actionSignature, nextIndex)
|
|
70
|
+
seenKeysDisplayByIndex.set(nextIndex, new Set([keysDisplay]))
|
|
71
|
+
result.push({
|
|
72
|
+
description: binding.description,
|
|
73
|
+
group: binding.group,
|
|
74
|
+
keys: binding.keys,
|
|
75
|
+
keysDisplay,
|
|
76
|
+
})
|
|
77
|
+
continue
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const seenKeysDisplay = seenKeysDisplayByIndex.get(existingIndex)
|
|
81
|
+
if (!seenKeysDisplay || seenKeysDisplay.has(keysDisplay)) continue
|
|
82
|
+
|
|
83
|
+
seenKeysDisplay.add(keysDisplay)
|
|
84
|
+
const existing = result[existingIndex]
|
|
85
|
+
if (!existing) continue
|
|
86
|
+
if (!existing.description && binding.description) {
|
|
87
|
+
existing.description = binding.description
|
|
88
|
+
existing.group = binding.group
|
|
89
|
+
resultIndexByDescription.set(binding.description, existingIndex)
|
|
90
|
+
}
|
|
91
|
+
existing.keys = `${existing.keys} / ${binding.keys}`
|
|
92
|
+
existing.keysDisplay = `${existing.keysDisplay} / ${keysDisplay}`
|
|
93
|
+
continue
|
|
94
|
+
}
|
|
95
|
+
|
|
36
96
|
if (options.withDescriptionOnly && !binding.description) continue
|
|
97
|
+
|
|
37
98
|
if (options.dedupeByDescription && binding.description) {
|
|
38
99
|
if (seenDescriptions.has(binding.description)) continue
|
|
39
100
|
seenDescriptions.add(binding.description)
|
|
@@ -42,7 +103,7 @@ export function describeBindings(
|
|
|
42
103
|
description: binding.description,
|
|
43
104
|
group: binding.group,
|
|
44
105
|
keys: binding.keys,
|
|
45
|
-
keysDisplay
|
|
106
|
+
keysDisplay,
|
|
46
107
|
})
|
|
47
108
|
}
|
|
48
109
|
|
|
@@ -2,7 +2,7 @@ import type { Terminal } from '@xterm/headless'
|
|
|
2
2
|
|
|
3
3
|
import type { TerminalLine, TerminalSnapshot, TerminalSpan } from '../state/types'
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { getCurrentTokens } from '../ui/theme'
|
|
6
6
|
|
|
7
7
|
const SNAPSHOT_TAIL_LINE_COUNT = 10
|
|
8
8
|
|
|
@@ -115,15 +115,17 @@ function buildLine(
|
|
|
115
115
|
let bg = getColorHex(current.getBgColor(), bgMode)
|
|
116
116
|
|
|
117
117
|
if (current.isInverse()) {
|
|
118
|
-
const
|
|
119
|
-
const
|
|
118
|
+
const tokens = getCurrentTokens()
|
|
119
|
+
const resolvedFg = fg ?? tokens.palette.ink
|
|
120
|
+
const resolvedBg = bg ?? tokens.bg
|
|
120
121
|
;[fg, bg] = [resolvedBg, resolvedFg]
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
const isCursorCell = cursorVisible && cursorColumn === column
|
|
124
125
|
if (isCursorCell) {
|
|
125
|
-
const
|
|
126
|
-
const
|
|
126
|
+
const tokens = getCurrentTokens()
|
|
127
|
+
const resolvedFg = fg ?? tokens.palette.ink
|
|
128
|
+
const resolvedBg = bg ?? tokens.bg
|
|
127
129
|
;[fg, bg] = [resolvedBg, resolvedFg]
|
|
128
130
|
}
|
|
129
131
|
|
|
@@ -274,6 +274,8 @@ export function reduceGitModeState(state: AppState, action: AppAction): AppState
|
|
|
274
274
|
}
|
|
275
275
|
}
|
|
276
276
|
case 'git-mode-set-parsed': {
|
|
277
|
+
const prevParsed = state.gitMode.parsedFiles[action.key]
|
|
278
|
+
if (prevParsed && prevParsed.hash === action.hash) return state
|
|
277
279
|
return {
|
|
278
280
|
...state,
|
|
279
281
|
gitMode: {
|
|
@@ -303,6 +305,62 @@ export function reduceGitModeState(state: AppState, action: AppAction): AppState
|
|
|
303
305
|
},
|
|
304
306
|
}
|
|
305
307
|
}
|
|
308
|
+
case 'git-mode-merge-highlights': {
|
|
309
|
+
const k = `${action.key}|${action.themeId}`
|
|
310
|
+
const existing = state.gitMode.highlights[k]
|
|
311
|
+
const sameContent = existing && existing.hash === action.hash
|
|
312
|
+
if (sameContent) {
|
|
313
|
+
const existingAdd = existing.add as unknown[]
|
|
314
|
+
const existingDel = existing.del as unknown[]
|
|
315
|
+
let changesAnything = false
|
|
316
|
+
outer: for (const patch of action.add) {
|
|
317
|
+
const tokens = patch.tokens as unknown[]
|
|
318
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
319
|
+
if (existingAdd[patch.start + i] === undefined) {
|
|
320
|
+
changesAnything = true
|
|
321
|
+
break outer
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (!changesAnything) {
|
|
326
|
+
outer: for (const patch of action.del) {
|
|
327
|
+
const tokens = patch.tokens as unknown[]
|
|
328
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
329
|
+
if (existingDel[patch.start + i] === undefined) {
|
|
330
|
+
changesAnything = true
|
|
331
|
+
break outer
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (!changesAnything) return state
|
|
337
|
+
}
|
|
338
|
+
const nextAdd = sameContent ? ([...(existing.add as unknown[])] as unknown[]) : []
|
|
339
|
+
const nextDel = sameContent ? ([...(existing.del as unknown[])] as unknown[]) : []
|
|
340
|
+
for (const patch of action.add) {
|
|
341
|
+
const tokens = patch.tokens as unknown[]
|
|
342
|
+
for (let i = 0; i < tokens.length; i++) nextAdd[patch.start + i] = tokens[i]
|
|
343
|
+
}
|
|
344
|
+
for (const patch of action.del) {
|
|
345
|
+
const tokens = patch.tokens as unknown[]
|
|
346
|
+
for (let i = 0; i < tokens.length; i++) nextDel[patch.start + i] = tokens[i]
|
|
347
|
+
}
|
|
348
|
+
return {
|
|
349
|
+
...state,
|
|
350
|
+
gitMode: {
|
|
351
|
+
...state.gitMode,
|
|
352
|
+
highlights: {
|
|
353
|
+
...state.gitMode.highlights,
|
|
354
|
+
[k]: {
|
|
355
|
+
add: nextAdd,
|
|
356
|
+
del: nextDel,
|
|
357
|
+
hash: action.hash,
|
|
358
|
+
themeId: action.themeId,
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
}
|
|
363
|
+
}
|
|
306
364
|
case 'git-mode-invalidate-diffs': {
|
|
307
365
|
if (action.paths.length === 0) return state
|
|
308
366
|
return clearDiffCacheForPaths(state, new Set(action.paths))
|
package/src/state/types.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import type { ModeId } from '@brimveyn/aimux-config'
|
|
2
2
|
import type { ThemedToken } from 'shiki'
|
|
3
3
|
|
|
4
|
-
import type { FileDiffMetadata } from '../diff-parser'
|
|
5
|
-
|
|
6
4
|
export type BuiltinAssistantId = 'claude' | 'codex' | 'opencode' | 'terminal'
|
|
7
5
|
|
|
8
6
|
export type AssistantId = BuiltinAssistantId | (string & {})
|
|
@@ -224,8 +222,7 @@ export interface FoldState {
|
|
|
224
222
|
|
|
225
223
|
export interface ParsedDiffEntry {
|
|
226
224
|
hash: string
|
|
227
|
-
// Stored as unknown at the state boundary
|
|
228
|
-
// | null, narrowed at read sites via getParsedFile().
|
|
225
|
+
// Stored as unknown at the state boundary and narrowed at read sites.
|
|
229
226
|
file: unknown
|
|
230
227
|
}
|
|
231
228
|
|
|
@@ -237,9 +234,9 @@ export interface HighlightsEntry {
|
|
|
237
234
|
del: unknown
|
|
238
235
|
}
|
|
239
236
|
|
|
240
|
-
export function
|
|
237
|
+
export function getParsedPayload<T>(entry: ParsedDiffEntry | undefined): T | null {
|
|
241
238
|
if (!entry) return null
|
|
242
|
-
return entry.file as
|
|
239
|
+
return entry.file as T | null
|
|
243
240
|
}
|
|
244
241
|
|
|
245
242
|
export function getHighlightsTokens(entry: HighlightsEntry | undefined): {
|
|
@@ -608,6 +605,14 @@ export type GitModeAction =
|
|
|
608
605
|
add: unknown
|
|
609
606
|
del: unknown
|
|
610
607
|
}
|
|
608
|
+
| {
|
|
609
|
+
type: 'git-mode-merge-highlights'
|
|
610
|
+
key: string
|
|
611
|
+
hash: string
|
|
612
|
+
themeId: string
|
|
613
|
+
add: Array<{ start: number; tokens: unknown }>
|
|
614
|
+
del: Array<{ start: number; tokens: unknown }>
|
|
615
|
+
}
|
|
611
616
|
| { type: 'git-mode-invalidate-diffs'; paths: string[] }
|
|
612
617
|
| { type: 'git-mode-set-loading'; key: string; loading: boolean }
|
|
613
618
|
| { type: 'git-mode-set-pending-delete'; path: string | null }
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useKeyboard } from '@opentui/react'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { ModalShell } from './components/modals/shared/modal-shell'
|
|
4
|
+
import { ListItem } from './components/primitives/list-item'
|
|
5
5
|
import { uiTokens } from './ui-tokens'
|
|
6
6
|
|
|
7
7
|
interface BreakingUpdateScreenProps {
|