@brimveyn/aimux 1.21.0 → 1.22.2

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 (220) hide show
  1. package/README.md +27 -25
  2. package/package.json +2 -2
  3. package/src/app-runtime/auto-commit-driver.ts +22 -27
  4. package/src/app-runtime/backend-attach-runtime.ts +43 -36
  5. package/src/app-runtime/backend-runtime-events.ts +107 -88
  6. package/src/app-runtime/editor-actions.ts +201 -0
  7. package/src/app-runtime/git-actions.ts +197 -0
  8. package/src/app-runtime/measure-ref.ts +19 -0
  9. package/src/app-runtime/navigation-actions.ts +213 -0
  10. package/src/app-runtime/project-actions.ts +188 -0
  11. package/src/app-runtime/prompt-injection.ts +123 -0
  12. package/src/app-runtime/selection.ts +29 -0
  13. package/src/app-runtime/settings-actions.ts +156 -0
  14. package/src/app-runtime/setup-actions.ts +237 -0
  15. package/src/app-runtime/side-effect-context.ts +33 -0
  16. package/src/app-runtime/side-effects.ts +302 -1541
  17. package/src/app-runtime/snippet-actions.ts +3 -2
  18. package/src/app-runtime/tab-actions.ts +218 -0
  19. package/src/app-runtime/tab-runtime-timeouts.ts +1 -1
  20. package/src/app-runtime/use-auto-commit-driver.ts +14 -13
  21. package/src/app-runtime/use-backend-runtime.ts +12 -11
  22. package/src/app-runtime/use-directory-search.ts +5 -4
  23. package/src/app-runtime/use-mouse-handlers.ts +2 -1
  24. package/src/app-runtime/{use-workspace-autosave.ts → use-project-autosave.ts} +4 -4
  25. package/src/app-runtime/use-renderer-bindings.ts +2 -1
  26. package/src/app-runtime/use-setup-runner.ts +96 -0
  27. package/src/app-runtime/use-terminal-resize.ts +18 -14
  28. package/src/app-runtime/workspace-actions.ts +366 -0
  29. package/src/app-runtime/workspace-activity.ts +138 -0
  30. package/src/app-runtime/workspace-naming.ts +90 -0
  31. package/src/app.tsx +93 -48
  32. package/src/assets/sounds/bell.wav +0 -0
  33. package/src/assets/sounds/ding.wav +0 -0
  34. package/src/assets/sounds/plane.wav +0 -0
  35. package/src/cli/client/daemon-client.ts +2 -2
  36. package/src/cli/client/project-resolver.ts +112 -0
  37. package/src/cli/commands/project/close.ts +32 -0
  38. package/src/cli/commands/project/create.ts +93 -0
  39. package/src/cli/commands/project/list.ts +25 -0
  40. package/src/cli/commands/project/show.ts +32 -0
  41. package/src/cli/commands/{workspace → project}/switch.ts +19 -19
  42. package/src/cli/commands/tab/await.ts +2 -2
  43. package/src/cli/commands/tab/close.ts +3 -3
  44. package/src/cli/commands/tab/create.ts +74 -73
  45. package/src/cli/commands/tab/focus.ts +3 -3
  46. package/src/cli/commands/tab/list.ts +6 -6
  47. package/src/cli/commands/tab/run.ts +2 -2
  48. package/src/cli/commands/tab/send.ts +2 -2
  49. package/src/cli/commands/tab/snapshot.ts +2 -2
  50. package/src/cli/commands/tab/tail.ts +2 -2
  51. package/src/cli/commands/tab/wait.ts +2 -2
  52. package/src/cli/commands/worker/await.ts +3 -3
  53. package/src/cli/commands/worker/doctor.ts +30 -30
  54. package/src/cli/commands/worker/list.ts +15 -15
  55. package/src/cli/commands/worker/prompt.ts +3 -3
  56. package/src/cli/commands/worker/run.ts +23 -23
  57. package/src/cli/commands/worker/shared.ts +53 -59
  58. package/src/cli/commands/worker/stop.ts +29 -29
  59. package/src/cli/commands/worker/submit.ts +3 -3
  60. package/src/cli/commands/{worktree → workspace}/create-core.ts +26 -26
  61. package/src/cli/commands/workspace/create.ts +30 -68
  62. package/src/cli/commands/workspace/list.ts +52 -9
  63. package/src/cli/commands/workspace/remove.ts +81 -0
  64. package/src/cli/completion/plan.ts +2 -2
  65. package/src/cli/completion/sources.ts +18 -18
  66. package/src/cli/context.ts +13 -13
  67. package/src/cli/flags.ts +30 -10
  68. package/src/cli/index.ts +26 -16
  69. package/src/cli/output.ts +1 -1
  70. package/src/cli/registry.ts +12 -12
  71. package/src/config/loader.ts +16 -5
  72. package/src/config.ts +70 -112
  73. package/src/daemon/catalog-writer.ts +67 -67
  74. package/src/daemon/daemon.ts +205 -201
  75. package/src/daemon/session-manager.ts +39 -39
  76. package/src/daemon/session-registry.ts +14 -14
  77. package/src/git/divergence.ts +33 -2
  78. package/src/git/git-poller.ts +1 -1
  79. package/src/git/git-status.ts +2 -2
  80. package/src/git/{move-worktree.ts → move-workspace.ts} +5 -5
  81. package/src/git/pr-status.ts +1 -1
  82. package/src/git/repo-discovery.ts +1 -1
  83. package/src/git/use-repo-discovery.ts +1 -1
  84. package/src/git/workspace-branch-poller.ts +54 -0
  85. package/src/git/workspace-divergence-poller.ts +65 -0
  86. package/src/git/worktree.ts +29 -0
  87. package/src/index.tsx +4 -4
  88. package/src/input/keymap/help-entries.ts +7 -5
  89. package/src/input/modes/bridge.ts +13 -12
  90. package/src/input/modes/handlers/shared.ts +1 -1
  91. package/src/input/modes/transitions.ts +34 -21
  92. package/src/input/modes/types.ts +48 -31
  93. package/src/ipc/manager-protocol.ts +44 -44
  94. package/src/ipc/protocol.ts +192 -166
  95. package/src/platform/play-sound.ts +184 -0
  96. package/src/platform/project-search.ts +8 -8
  97. package/src/platform/worktree-paths.ts +8 -6
  98. package/src/pty/assistant-question-extractor.ts +1 -1
  99. package/src/pty/assistant-status-detection-loop.ts +79 -55
  100. package/src/pty/assistant-status-detector.ts +40 -15
  101. package/src/pty/command-registry.ts +81 -1
  102. package/src/restart-terminal-manager.ts +1 -1
  103. package/src/services/ai-usage/provider.ts +9 -2
  104. package/src/services/ai-usage/spawn.ts +19 -6
  105. package/src/session-backend/bootstrap.ts +4 -4
  106. package/src/session-backend/local-session-backend.ts +78 -75
  107. package/src/session-backend/remote-session-backend.ts +64 -52
  108. package/src/session-backend/types.ts +43 -32
  109. package/src/settings/live.ts +90 -0
  110. package/src/settings/search.ts +43 -0
  111. package/src/settings/sections/about.ts +49 -0
  112. package/src/settings/sections/appearance.ts +54 -0
  113. package/src/settings/sections/automation.ts +105 -0
  114. package/src/settings/sections/commands.ts +72 -0
  115. package/src/settings/sections/editor.ts +56 -0
  116. package/src/settings/sections/experimental.ts +57 -0
  117. package/src/settings/sections/git.ts +112 -0
  118. package/src/settings/sections/index.ts +95 -0
  119. package/src/settings/sections/integrations.ts +23 -0
  120. package/src/settings/sections/layout.ts +72 -0
  121. package/src/settings/sections/notifications.ts +99 -0
  122. package/src/settings/sections/setup.ts +54 -0
  123. package/src/settings/sections/status-bar.ts +63 -0
  124. package/src/settings/settings-store.ts +207 -0
  125. package/src/settings/types.ts +108 -0
  126. package/src/snippets/run-shell-var.ts +48 -22
  127. package/src/state/actions.ts +342 -0
  128. package/src/state/app-store.ts +2 -1
  129. package/src/state/bars.ts +1 -1
  130. package/src/state/dispatch-ref.ts +1 -1
  131. package/src/state/layout-resize.ts +2 -2
  132. package/src/state/project-catalog.ts +275 -0
  133. package/src/state/project-data.ts +143 -0
  134. package/src/state/{session-persistence.ts → project-persistence.ts} +74 -68
  135. package/src/state/project-save.ts +46 -0
  136. package/src/state/project-workspaces.ts +392 -0
  137. package/src/state/reducers/auto-commit-state.ts +11 -10
  138. package/src/state/reducers/git-commit-modal-state.ts +122 -0
  139. package/src/state/reducers/git-mode-state.ts +1 -1
  140. package/src/state/reducers/git-panel-state.ts +10 -6
  141. package/src/state/reducers/modal-state.ts +290 -681
  142. package/src/state/reducers/multi-repo-state.ts +3 -6
  143. package/src/state/reducers/project-state.ts +312 -0
  144. package/src/state/reducers/settings-state.ts +80 -0
  145. package/src/state/reducers/tab-state.ts +107 -60
  146. package/src/state/reducers/ui-state.ts +4 -3
  147. package/src/state/selectors.ts +49 -37
  148. package/src/state/store.ts +57 -24
  149. package/src/state/types.ts +244 -405
  150. package/src/state/validation.ts +14 -12
  151. package/src/terminal-manager/manager-client.ts +32 -32
  152. package/src/terminal-manager/terminal-manager.ts +24 -24
  153. package/src/ui/components/git/git-panel.tsx +2 -2
  154. package/src/ui/components/git/git-view.tsx +12 -12
  155. package/src/ui/components/git/image-diff/image-diff-view.tsx +1 -1
  156. package/src/ui/components/git/pane/git-pane-widget.tsx +7 -7
  157. package/src/ui/components/layout/sidebar/project-list.tsx +444 -0
  158. package/src/ui/components/layout/sidebar/tab-item.tsx +27 -12
  159. package/src/ui/components/layout/sidebar/use-sidebar-auto-scroll.ts +1 -1
  160. package/src/ui/components/layout/sidebar/workspace-row.tsx +253 -0
  161. package/src/ui/components/layout/status-bar.tsx +6 -2
  162. package/src/ui/components/layout/terminal-pane.tsx +34 -29
  163. package/src/ui/components/layout/top-tab-bar.tsx +46 -36
  164. package/src/ui/components/modals/git/git-commit-modal.tsx +8 -8
  165. package/src/ui/components/modals/{sessions/create-session-modal.tsx → projects/create-project-modal.tsx} +12 -12
  166. package/src/ui/components/modals/{sessions/session-name-modal.tsx → projects/project-name-modal.tsx} +2 -2
  167. package/src/ui/components/modals/{sessions/session-picker-modal.tsx → projects/project-picker-modal.tsx} +33 -35
  168. package/src/ui/components/modals/settings/settings-search-modal.tsx +70 -0
  169. package/src/ui/components/modals/shared/form.tsx +34 -22
  170. package/src/ui/components/modals/shared/picker.tsx +10 -10
  171. package/src/ui/components/modals/shared/{worktree-delete-confirm.tsx → workspace-delete-confirm.tsx} +9 -9
  172. package/src/ui/components/modals/tabs/new-tab-modal.tsx +30 -324
  173. package/src/ui/components/modals/workspace/create-workspace-modal.tsx +101 -0
  174. package/src/ui/components/modals/{worktree/worktree-move-confirm-modal.tsx → workspace/workspace-move-confirm-modal.tsx} +5 -5
  175. package/src/ui/components/modals/{worktree/worktree-move-modal.tsx → workspace/workspace-move-modal.tsx} +29 -27
  176. package/src/ui/components/primitives/input-field.tsx +7 -3
  177. package/src/ui/components/primitives/surface.tsx +3 -0
  178. package/src/ui/components/settings/row-value.tsx +74 -0
  179. package/src/ui/components/settings/settings-row.tsx +78 -0
  180. package/src/ui/components/settings/settings-view.tsx +169 -0
  181. package/src/ui/components/setup/setup-widget.tsx +152 -0
  182. package/src/ui/flash/build-labels.ts +27 -27
  183. package/src/ui/hooks/use-activity-sprite.ts +73 -0
  184. package/src/ui/hooks/use-scroll-active-into-view.ts +26 -0
  185. package/src/ui/{session-ordering.ts → project-ordering.ts} +4 -4
  186. package/src/ui/root.tsx +139 -121
  187. package/src/ui/status-bar-model.ts +48 -34
  188. package/src/ui/terminal-graphics/kitty.ts +28 -2
  189. package/src/ui/terminal-graphics/sprites/done@330/0.png +0 -0
  190. package/src/ui/terminal-graphics/sprites/done@330/1.png +0 -0
  191. package/src/ui/terminal-graphics/sprites/idle@1000/0.png +0 -0
  192. package/src/ui/terminal-graphics/sprites/idle@1000/1.png +0 -0
  193. package/src/ui/terminal-graphics/sprites/waiting@170/0.png +0 -0
  194. package/src/ui/terminal-graphics/sprites/waiting@170/1.png +0 -0
  195. package/src/ui/terminal-graphics/sprites/working@150/0.png +0 -0
  196. package/src/ui/terminal-graphics/sprites/working@150/1.png +0 -0
  197. package/src/ui/terminal-graphics/sprites/working@150/2.png +0 -0
  198. package/src/ui/terminal-graphics/sprites/working@150/3.png +0 -0
  199. package/src/ui/terminal-graphics/sprites.ts +246 -0
  200. package/src/ui/truncate.ts +7 -0
  201. package/src/ui/widgets/registry.tsx +6 -3
  202. package/src/ui/widgets/widget-context-menu.ts +26 -10
  203. package/src/update.ts +2 -2
  204. package/src/app-runtime/session-actions.ts +0 -187
  205. package/src/cli/client/workspace-resolver.ts +0 -110
  206. package/src/cli/commands/workspace/close.ts +0 -32
  207. package/src/cli/commands/workspace/show.ts +0 -32
  208. package/src/cli/commands/worktree/create.ts +0 -55
  209. package/src/cli/commands/worktree/list.ts +0 -68
  210. package/src/cli/commands/worktree/remove.ts +0 -81
  211. package/src/git/worktree-branch-poller.ts +0 -54
  212. package/src/git/worktree-divergence-poller.ts +0 -59
  213. package/src/state/reducers/session-state.ts +0 -262
  214. package/src/state/session-catalog.ts +0 -143
  215. package/src/state/session-worktrees.ts +0 -260
  216. package/src/state/workspace-save.ts +0 -43
  217. package/src/ui/components/layout/sidebar/use-top-tab-bar-auto-scroll.ts +0 -30
  218. package/src/ui/components/layout/sidebar/workspace-list.tsx +0 -418
  219. package/src/ui/components/layout/sidebar/worktree-row.tsx +0 -149
  220. /package/src/ui/{components/git/image-diff → terminal-graphics}/dimensions.ts +0 -0
@@ -10,35 +10,35 @@ import { AutoComplete, Form, type FormOptionItem, TextField } from '../shared/fo
10
10
  const VISIBLE_ROWS = 8
11
11
 
12
12
  function getDirectoryResultIcon(result: DirectoryResult): string {
13
- if (result.type === 'worktree') return '\u{e728}'
14
- if (result.type === 'workspace') return '\u{f07c}'
13
+ if (result.type === 'workspace') return '\u{e728}'
14
+ if (result.type === 'project') return '\u{f07c}'
15
15
  return '\u{e702}'
16
16
  }
17
17
 
18
18
  function getDirectoryResultColor(result: DirectoryResult): string {
19
19
  const t = getCurrentTheme()
20
- if (result.type === 'worktree') return t.warning
21
- if (result.type === 'workspace') return t.info
20
+ if (result.type === 'workspace') return t.warning
21
+ if (result.type === 'project') return t.info
22
22
  return t.text
23
23
  }
24
24
 
25
- interface CreateSessionModalProps {
25
+ interface CreateProjectModalProps {
26
26
  activeField: 'directory' | 'name'
27
27
  directoryQuery: string
28
- sessionName: string
28
+ projectName: string
29
29
  results: DirectoryResult[]
30
30
  selectedIndex: number
31
31
  pendingProjectPath: string | null
32
32
  }
33
33
 
34
- export function CreateSessionModal({
34
+ export function CreateProjectModal({
35
35
  activeField,
36
36
  directoryQuery,
37
37
  pendingProjectPath,
38
+ projectName,
38
39
  results,
39
40
  selectedIndex,
40
- sessionName,
41
- }: CreateSessionModalProps) {
41
+ }: CreateProjectModalProps) {
42
42
  const t = useTheme()
43
43
  const dirActive = activeField === 'directory'
44
44
  const nameActive = activeField === 'name'
@@ -57,8 +57,8 @@ export function CreateSessionModal({
57
57
 
58
58
  return (
59
59
  <Form
60
- title="Create workspace"
61
- keybindsModeId="modal.create-session"
60
+ title="Create project"
61
+ keybindsModeId="modal.create-project"
62
62
  width={uiTokens.modalWidth.xl}
63
63
  >
64
64
  <AutoComplete
@@ -81,7 +81,7 @@ export function CreateSessionModal({
81
81
  }
82
82
  />
83
83
 
84
- <TextField active={nameActive} label="Workspace name" value={sessionName} />
84
+ <TextField active={nameActive} label="Project name" value={projectName} />
85
85
  </Form>
86
86
  )
87
87
  }
@@ -1,9 +1,9 @@
1
1
  import { uiTokens } from '../../../ui-tokens'
2
2
  import { Form, TextField } from '../shared/form'
3
3
 
4
- export function SessionNameModal({ title, value }: { title: string; value: string }) {
4
+ export function ProjectNameModal({ title, value }: { title: string; value: string }) {
5
5
  return (
6
- <Form title={title} keybindsModeId="modal.session-name" width={uiTokens.modalWidth.md}>
6
+ <Form title={title} keybindsModeId="modal.project-name" width={uiTokens.modalWidth.md}>
7
7
  <TextField active value={value} />
8
8
  </Form>
9
9
  )
@@ -1,86 +1,84 @@
1
1
  import { useCallback, useMemo } from 'react'
2
2
 
3
- import type { SessionRecord } from '../../../../state/types'
3
+ import type { ProjectRecord } from '../../../../state/types'
4
4
 
5
5
  import { dispatchGlobal, runSideEffectGlobal } from '../../../../state/dispatch-ref'
6
- import { filterSessions } from '../../../../state/selectors'
6
+ import { filterProjects } from '../../../../state/selectors'
7
7
  import { abbreviatePath } from '../../../path-format'
8
- import { orderSessionsForDisplay } from '../../../session-ordering'
8
+ import { orderProjectsForDisplay } from '../../../project-ordering'
9
9
  import { useTheme } from '../../../theme'
10
10
  import { uiTokens } from '../../../ui-tokens'
11
11
  import { Picker, type PickerItem } from '../shared/picker'
12
12
 
13
- interface SessionPickerModalProps {
14
- sessions: SessionRecord[]
13
+ interface ProjectPickerModalProps {
14
+ projects: ProjectRecord[]
15
15
  selectedIndex: number
16
- currentSessionId: string | null
16
+ currentProjectId: string | null
17
17
  currentTabCount: number
18
18
  filter: string | null
19
19
  cursorPos?: number
20
20
  }
21
21
 
22
- function formatSessionLine(
23
- session: SessionRecord,
24
- currentSessionId: string | null,
22
+ function formatProjectLine(
23
+ project: ProjectRecord,
24
+ currentProjectId: string | null,
25
25
  currentTabCount: number,
26
26
  displayIndex: number
27
27
  ): string {
28
28
  const tabCount =
29
- session.id === currentSessionId
30
- ? currentTabCount
31
- : (session.workspaceSnapshot?.tabs.length ?? 0)
32
- return `[${displayIndex}] ${session.name} (${tabCount} tab${tabCount === 1 ? '' : 's'})`
29
+ project.id === currentProjectId ? currentTabCount : (project.projectSnapshot?.tabs.length ?? 0)
30
+ return `[${displayIndex}] ${project.name} (${tabCount} tab${tabCount === 1 ? '' : 's'})`
33
31
  }
34
32
 
35
33
  function getEmptyStateMessage(hasFilter: boolean): string {
36
- if (hasFilter) return 'No matching workspaces.'
37
- return 'No workspaces yet. Press Enter or n to create your first workspace.'
34
+ if (hasFilter) return 'No matching projects.'
35
+ return 'No projects yet. Press Enter or n to create your first project.'
38
36
  }
39
37
 
40
- export function SessionPickerModal({
41
- currentSessionId,
38
+ export function ProjectPickerModal({
39
+ currentProjectId,
42
40
  currentTabCount,
43
41
  cursorPos,
44
42
  filter,
43
+ projects,
45
44
  selectedIndex,
46
- sessions,
47
- }: SessionPickerModalProps) {
45
+ }: ProjectPickerModalProps) {
48
46
  const t = useTheme()
49
- const ordered = useMemo(() => orderSessionsForDisplay(sessions), [sessions])
50
- const filtered = useMemo(() => filterSessions(ordered, filter), [filter, ordered])
47
+ const ordered = useMemo(() => orderProjectsForDisplay(projects), [projects])
48
+ const filtered = useMemo(() => filterProjects(ordered, filter), [filter, ordered])
51
49
  const hasFilter = !!(filter != null && filter !== '')
52
50
 
53
51
  const items = useMemo<PickerItem[]>(() => {
54
52
  const baselineOrder = ordered.map((s) => s.id)
55
- const sessionItems: PickerItem[] = filtered.map((session, index) => {
53
+ const projectItems: PickerItem[] = filtered.map((project, index) => {
56
54
  const active = index === selectedIndex
57
- const displayIndex = baselineOrder.indexOf(session.id) + 1
55
+ const displayIndex = baselineOrder.indexOf(project.id) + 1
58
56
  return {
59
- key: session.id,
60
- onClick: () => runSideEffectGlobal({ type: 'confirm-selected-session' }),
61
- onDelete: () => runSideEffectGlobal({ type: 'delete-selected-session' }),
57
+ key: project.id,
58
+ onClick: () => runSideEffectGlobal({ type: 'confirm-selected-project' }),
59
+ onDelete: () => runSideEffectGlobal({ type: 'delete-selected-project' }),
62
60
  subtitle:
63
- session.projectPath != null && session.projectPath !== '' ? (
64
- <text fg={t.textMuted}>{abbreviatePath(session.projectPath)}</text>
61
+ project.projectPath != null && project.projectPath !== '' ? (
62
+ <text fg={t.textMuted}>{abbreviatePath(project.projectPath)}</text>
65
63
  ) : undefined,
66
64
  title: (
67
65
  <text fg={active ? t.text : t.textMuted}>
68
- {formatSessionLine(session, currentSessionId, currentTabCount, displayIndex)}
66
+ {formatProjectLine(project, currentProjectId, currentTabCount, displayIndex)}
69
67
  </text>
70
68
  ),
71
69
  }
72
70
  })
73
71
  const createNewItem: PickerItem = {
74
72
  key: '__create-new__',
75
- onClick: () => runSideEffectGlobal({ type: 'confirm-selected-session' }),
73
+ onClick: () => runSideEffectGlobal({ type: 'confirm-selected-project' }),
76
74
  title: (
77
75
  <text fg={selectedIndex === filtered.length ? t.text : t.textMuted}>
78
- Create new workspace
76
+ Create new project
79
77
  </text>
80
78
  ),
81
79
  }
82
- return [...sessionItems, createNewItem]
83
- }, [currentSessionId, currentTabCount, filtered, ordered, selectedIndex, t])
80
+ return [...projectItems, createNewItem]
81
+ }, [currentProjectId, currentTabCount, filtered, ordered, selectedIndex, t])
84
82
 
85
83
  const handleHover = useCallback(
86
84
  (index: number) => dispatchGlobal({ index, type: 'set-modal-selection-index' }),
@@ -89,8 +87,8 @@ export function SessionPickerModal({
89
87
 
90
88
  return (
91
89
  <Picker
92
- title="Workspaces"
93
- keybindsModeId="modal.session-picker.filtering"
90
+ title="Projects"
91
+ keybindsModeId="modal.project-picker.filtering"
94
92
  width={uiTokens.modalWidth.lg}
95
93
  gap={1}
96
94
  filter={filter}
@@ -0,0 +1,70 @@
1
+ import { useCallback, useMemo } from 'react'
2
+
3
+ import { filterSettingRows } from '../../../../settings/search'
4
+ import { useAppStore } from '../../../../state/app-store'
5
+ import { dispatchGlobal, runSideEffectGlobal } from '../../../../state/dispatch-ref'
6
+ import { useTheme } from '../../../theme'
7
+ import { uiTokens } from '../../../ui-tokens'
8
+ import { RowValue } from '../../settings/row-value'
9
+ import { Picker, type PickerItem } from '../shared/picker'
10
+
11
+ interface SettingsSearchModalProps {
12
+ filter: string | null
13
+ selectedIndex: number
14
+ cursorPos?: number
15
+ }
16
+
17
+ /**
18
+ * Every setting in one list, grouped by section and laid out the way the screen
19
+ * lays them out — label and value on one line, what it does underneath. A result
20
+ * you cannot read the current value of is a result you have to go and check.
21
+ *
22
+ * The filter is the one `getModalOptionCount` counts with, so the list and the
23
+ * cursor moving through it never disagree.
24
+ */
25
+ export function SettingsSearchModal({
26
+ cursorPos,
27
+ filter,
28
+ selectedIndex,
29
+ }: SettingsSearchModalProps) {
30
+ const t = useTheme()
31
+ const projects = useAppStore((s) => s.projects)
32
+ const hits = useMemo(() => filterSettingRows(projects, filter), [projects, filter])
33
+
34
+ const items = useMemo<PickerItem[]>(
35
+ () =>
36
+ hits.map((hit) => ({
37
+ group: hit.sectionLabel,
38
+ key: hit.row.id,
39
+ // Acts on the selection, which the hover just set — the same two-step
40
+ // every other picker's rows use.
41
+ onClick: () => runSideEffectGlobal({ type: 'confirm-settings-search' }),
42
+ subtitle:
43
+ hit.row.description != null && hit.row.description !== '' ? (
44
+ <text fg={t.textMuted}>{hit.row.description}</text>
45
+ ) : undefined,
46
+ title: <text fg={t.text}>{hit.row.label}</text>,
47
+ trailing: <RowValue row={hit.row} />,
48
+ })),
49
+ [hits, t]
50
+ )
51
+
52
+ const handleHover = useCallback(
53
+ (index: number) => dispatchGlobal({ index, type: 'set-modal-selection-index' }),
54
+ []
55
+ )
56
+
57
+ return (
58
+ <Picker
59
+ title="Search settings"
60
+ keybindsModeId="modal.settings-search.filtering"
61
+ width={uiTokens.modalWidth.xl}
62
+ filter={filter}
63
+ cursorPos={cursorPos}
64
+ items={items}
65
+ selectedIndex={selectedIndex}
66
+ emptyState={<text fg={t.textMuted}>No setting matches.</text>}
67
+ onHover={handleHover}
68
+ />
69
+ )
70
+ }
@@ -27,6 +27,7 @@ interface TextFieldProps {
27
27
  cursorPos?: number
28
28
  description?: ReactNode
29
29
  label?: ReactNode
30
+ minLines?: number
30
31
  placeholder?: string
31
32
  value: string
32
33
  }
@@ -94,6 +95,7 @@ export function TextField({
94
95
  cursorPos,
95
96
  description,
96
97
  label,
98
+ minLines,
97
99
  placeholder,
98
100
  value,
99
101
  }: TextFieldProps) {
@@ -104,7 +106,13 @@ export function TextField({
104
106
  {label}
105
107
  </FieldLabel>
106
108
  ) : null}
107
- <InputField active={active} value={value} cursorPos={cursorPos} placeholder={placeholder} />
109
+ <InputField
110
+ active={active}
111
+ value={value}
112
+ cursorPos={cursorPos}
113
+ placeholder={placeholder}
114
+ minLines={minLines}
115
+ />
108
116
  </box>
109
117
  )
110
118
  }
@@ -138,27 +146,31 @@ export function AutoComplete({
138
146
  cursorPos={cursorPos}
139
147
  placeholder={placeholder}
140
148
  />
141
- <box flexDirection="column" height={maxVisibleRows}>
142
- {items.length === 0
143
- ? (emptyState ?? null)
144
- : visibleItems.map((item, index) => {
145
- const optionIndex = scrollOffset + index
146
- const optionActive = active && optionIndex === selectedIndex
147
- return (
148
- <ListItem
149
- key={item.key}
150
- index={optionIndex}
151
- active={optionActive}
152
- leading={resolveItemContent(item.leading, optionActive)}
153
- title={resolveItemContent(item.title, optionActive)}
154
- subtitle={resolveItemContent(item.subtitle, optionActive)}
155
- trailing={resolveItemContent(item.trailing, optionActive)}
156
- onHoverIndex={onHover}
157
- onClick={item.onClick}
158
- />
159
- )
160
- })}
161
- </box>
149
+ {/* Collapsed unless focused: a dropdown nobody is driving is noise, and
150
+ it pushes the rest of the form off the modal. */}
151
+ {active ? (
152
+ <box flexDirection="column" height={maxVisibleRows}>
153
+ {items.length === 0
154
+ ? (emptyState ?? null)
155
+ : visibleItems.map((item, index) => {
156
+ const optionIndex = scrollOffset + index
157
+ const optionActive = active && optionIndex === selectedIndex
158
+ return (
159
+ <ListItem
160
+ key={item.key}
161
+ index={optionIndex}
162
+ active={optionActive}
163
+ leading={resolveItemContent(item.leading, optionActive)}
164
+ title={resolveItemContent(item.title, optionActive)}
165
+ subtitle={resolveItemContent(item.subtitle, optionActive)}
166
+ trailing={resolveItemContent(item.trailing, optionActive)}
167
+ onHoverIndex={onHover}
168
+ onClick={item.onClick}
169
+ />
170
+ )
171
+ })}
172
+ </box>
173
+ ) : null}
162
174
  </box>
163
175
  )
164
176
  }
@@ -18,7 +18,7 @@ export interface PickerItem {
18
18
  onClick?: () => void
19
19
  onEdit?: () => void
20
20
  onDelete?: () => void
21
- onWorktree?: () => void
21
+ onWorkspace?: () => void
22
22
  }
23
23
 
24
24
  interface PickerProps {
@@ -42,11 +42,11 @@ const MODAL_CHROME_ROWS = 6
42
42
  function PickerItemCtas({
43
43
  onDelete,
44
44
  onEdit,
45
- onWorktree,
45
+ onWorkspace,
46
46
  }: {
47
47
  onEdit?: () => void
48
48
  onDelete?: () => void
49
- onWorktree?: () => void
49
+ onWorkspace?: () => void
50
50
  }) {
51
51
  const t = useTheme()
52
52
  const handleEdit = useCallback(
@@ -56,12 +56,12 @@ function PickerItemCtas({
56
56
  },
57
57
  [onEdit]
58
58
  )
59
- const handleWorktree = useCallback(
59
+ const handleWorkspace = useCallback(
60
60
  (event: OtuiMouseEvent) => {
61
61
  event.stopPropagation()
62
- onWorktree?.()
62
+ onWorkspace?.()
63
63
  },
64
- [onWorktree]
64
+ [onWorkspace]
65
65
  )
66
66
  const handleDelete = useCallback(
67
67
  (event: OtuiMouseEvent) => {
@@ -77,8 +77,8 @@ function PickerItemCtas({
77
77
  <text fg={t.primary}>[edit]</text>
78
78
  </box>
79
79
  ) : null}
80
- {onWorktree ? (
81
- <box onMouseDown={handleWorktree}>
80
+ {onWorkspace ? (
81
+ <box onMouseDown={handleWorkspace}>
82
82
  <text fg={t.warning}>[WT]</text>
83
83
  </box>
84
84
  ) : null}
@@ -189,11 +189,11 @@ export function Picker({
189
189
  const capturedIndex = index
190
190
  const trailing =
191
191
  item.trailing ??
192
- (active && (item.onEdit || item.onDelete || item.onWorktree) ? (
192
+ (active && (item.onEdit || item.onDelete || item.onWorkspace) ? (
193
193
  <PickerItemCtas
194
194
  onEdit={item.onEdit}
195
195
  onDelete={item.onDelete}
196
- onWorktree={item.onWorktree}
196
+ onWorkspace={item.onWorkspace}
197
197
  />
198
198
  ) : null)
199
199
  nodes.push(
@@ -4,33 +4,33 @@ import { useTheme } from '../../../theme'
4
4
  import { uiTokens } from '../../../ui-tokens'
5
5
  import { Form } from './form'
6
6
 
7
- interface WorktreeDeleteConfirmProps {
7
+ interface WorkspaceDeleteConfirmProps {
8
8
  keybindsModeId: ModeId
9
9
  reason: string
10
- worktreeLabel: string
10
+ workspaceLabel: string
11
11
  }
12
12
 
13
13
  /**
14
- * Shared confirmation dialog for a recoverable worktree delete. Used both inside
15
- * the new-tab picker and as a standalone modal (sidebar "Remove worktree"); the
14
+ * Shared confirmation dialog for a recoverable workspace delete. Used both inside
15
+ * the new-tab picker and as a standalone modal (sidebar "Remove workspace"); the
16
16
  * keybinds mode wires Enter/y to confirm and Esc/n to cancel for each context.
17
17
  */
18
- export function WorktreeDeleteConfirm({
18
+ export function WorkspaceDeleteConfirm({
19
19
  keybindsModeId,
20
20
  reason,
21
- worktreeLabel,
22
- }: WorktreeDeleteConfirmProps) {
21
+ workspaceLabel,
22
+ }: WorkspaceDeleteConfirmProps) {
23
23
  const t = useTheme()
24
24
  return (
25
25
  <Form
26
- title="Delete worktree?"
26
+ title="Delete workspace?"
27
27
  keybindsModeId={keybindsModeId}
28
28
  width={uiTokens.modalWidth.md}
29
29
  footer={<text fg={t.textMuted}>Enter / y to delete · Esc / n to cancel</text>}
30
30
  >
31
31
  <box flexDirection="column" gap={1}>
32
32
  <text fg={t.text}>
33
- Delete <strong>{worktreeLabel}</strong>?
33
+ Delete <strong>{workspaceLabel}</strong>?
34
34
  </text>
35
35
  <text fg={t.warning}>{reason}</text>
36
36
  </box>