@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.
- package/README.md +27 -25
- package/package.json +2 -2
- package/src/app-runtime/auto-commit-driver.ts +22 -27
- package/src/app-runtime/backend-attach-runtime.ts +43 -36
- package/src/app-runtime/backend-runtime-events.ts +107 -88
- package/src/app-runtime/editor-actions.ts +201 -0
- package/src/app-runtime/git-actions.ts +197 -0
- package/src/app-runtime/measure-ref.ts +19 -0
- package/src/app-runtime/navigation-actions.ts +213 -0
- package/src/app-runtime/project-actions.ts +188 -0
- package/src/app-runtime/prompt-injection.ts +123 -0
- package/src/app-runtime/selection.ts +29 -0
- package/src/app-runtime/settings-actions.ts +156 -0
- package/src/app-runtime/setup-actions.ts +237 -0
- package/src/app-runtime/side-effect-context.ts +33 -0
- package/src/app-runtime/side-effects.ts +302 -1541
- package/src/app-runtime/snippet-actions.ts +3 -2
- package/src/app-runtime/tab-actions.ts +218 -0
- package/src/app-runtime/tab-runtime-timeouts.ts +1 -1
- package/src/app-runtime/use-auto-commit-driver.ts +14 -13
- package/src/app-runtime/use-backend-runtime.ts +12 -11
- package/src/app-runtime/use-directory-search.ts +5 -4
- package/src/app-runtime/use-mouse-handlers.ts +2 -1
- package/src/app-runtime/{use-workspace-autosave.ts → use-project-autosave.ts} +4 -4
- package/src/app-runtime/use-renderer-bindings.ts +2 -1
- package/src/app-runtime/use-setup-runner.ts +96 -0
- package/src/app-runtime/use-terminal-resize.ts +18 -14
- package/src/app-runtime/workspace-actions.ts +366 -0
- package/src/app-runtime/workspace-activity.ts +138 -0
- package/src/app-runtime/workspace-naming.ts +90 -0
- package/src/app.tsx +93 -48
- package/src/assets/sounds/bell.wav +0 -0
- package/src/assets/sounds/ding.wav +0 -0
- package/src/assets/sounds/plane.wav +0 -0
- package/src/cli/client/daemon-client.ts +2 -2
- package/src/cli/client/project-resolver.ts +112 -0
- package/src/cli/commands/project/close.ts +32 -0
- package/src/cli/commands/project/create.ts +93 -0
- package/src/cli/commands/project/list.ts +25 -0
- package/src/cli/commands/project/show.ts +32 -0
- package/src/cli/commands/{workspace → project}/switch.ts +19 -19
- package/src/cli/commands/tab/await.ts +2 -2
- package/src/cli/commands/tab/close.ts +3 -3
- package/src/cli/commands/tab/create.ts +74 -73
- package/src/cli/commands/tab/focus.ts +3 -3
- package/src/cli/commands/tab/list.ts +6 -6
- package/src/cli/commands/tab/run.ts +2 -2
- package/src/cli/commands/tab/send.ts +2 -2
- package/src/cli/commands/tab/snapshot.ts +2 -2
- package/src/cli/commands/tab/tail.ts +2 -2
- package/src/cli/commands/tab/wait.ts +2 -2
- package/src/cli/commands/worker/await.ts +3 -3
- package/src/cli/commands/worker/doctor.ts +30 -30
- package/src/cli/commands/worker/list.ts +15 -15
- package/src/cli/commands/worker/prompt.ts +3 -3
- package/src/cli/commands/worker/run.ts +23 -23
- package/src/cli/commands/worker/shared.ts +53 -59
- package/src/cli/commands/worker/stop.ts +29 -29
- package/src/cli/commands/worker/submit.ts +3 -3
- package/src/cli/commands/{worktree → workspace}/create-core.ts +26 -26
- package/src/cli/commands/workspace/create.ts +30 -68
- package/src/cli/commands/workspace/list.ts +52 -9
- package/src/cli/commands/workspace/remove.ts +81 -0
- package/src/cli/completion/plan.ts +2 -2
- package/src/cli/completion/sources.ts +18 -18
- package/src/cli/context.ts +13 -13
- package/src/cli/flags.ts +30 -10
- package/src/cli/index.ts +26 -16
- package/src/cli/output.ts +1 -1
- package/src/cli/registry.ts +12 -12
- package/src/config/loader.ts +16 -5
- package/src/config.ts +70 -112
- package/src/daemon/catalog-writer.ts +67 -67
- package/src/daemon/daemon.ts +205 -201
- package/src/daemon/session-manager.ts +39 -39
- package/src/daemon/session-registry.ts +14 -14
- package/src/git/divergence.ts +33 -2
- package/src/git/git-poller.ts +1 -1
- package/src/git/git-status.ts +2 -2
- package/src/git/{move-worktree.ts → move-workspace.ts} +5 -5
- package/src/git/pr-status.ts +1 -1
- package/src/git/repo-discovery.ts +1 -1
- package/src/git/use-repo-discovery.ts +1 -1
- package/src/git/workspace-branch-poller.ts +54 -0
- package/src/git/workspace-divergence-poller.ts +65 -0
- package/src/git/worktree.ts +29 -0
- package/src/index.tsx +4 -4
- package/src/input/keymap/help-entries.ts +7 -5
- package/src/input/modes/bridge.ts +13 -12
- package/src/input/modes/handlers/shared.ts +1 -1
- package/src/input/modes/transitions.ts +34 -21
- package/src/input/modes/types.ts +48 -31
- package/src/ipc/manager-protocol.ts +44 -44
- package/src/ipc/protocol.ts +192 -166
- package/src/platform/play-sound.ts +184 -0
- package/src/platform/project-search.ts +8 -8
- package/src/platform/worktree-paths.ts +8 -6
- package/src/pty/assistant-question-extractor.ts +1 -1
- package/src/pty/assistant-status-detection-loop.ts +79 -55
- package/src/pty/assistant-status-detector.ts +40 -15
- package/src/pty/command-registry.ts +81 -1
- package/src/restart-terminal-manager.ts +1 -1
- package/src/services/ai-usage/provider.ts +9 -2
- package/src/services/ai-usage/spawn.ts +19 -6
- package/src/session-backend/bootstrap.ts +4 -4
- package/src/session-backend/local-session-backend.ts +78 -75
- package/src/session-backend/remote-session-backend.ts +64 -52
- package/src/session-backend/types.ts +43 -32
- package/src/settings/live.ts +90 -0
- package/src/settings/search.ts +43 -0
- package/src/settings/sections/about.ts +49 -0
- package/src/settings/sections/appearance.ts +54 -0
- package/src/settings/sections/automation.ts +105 -0
- package/src/settings/sections/commands.ts +72 -0
- package/src/settings/sections/editor.ts +56 -0
- package/src/settings/sections/experimental.ts +57 -0
- package/src/settings/sections/git.ts +112 -0
- package/src/settings/sections/index.ts +95 -0
- package/src/settings/sections/integrations.ts +23 -0
- package/src/settings/sections/layout.ts +72 -0
- package/src/settings/sections/notifications.ts +99 -0
- package/src/settings/sections/setup.ts +54 -0
- package/src/settings/sections/status-bar.ts +63 -0
- package/src/settings/settings-store.ts +207 -0
- package/src/settings/types.ts +108 -0
- package/src/snippets/run-shell-var.ts +48 -22
- package/src/state/actions.ts +342 -0
- package/src/state/app-store.ts +2 -1
- package/src/state/bars.ts +1 -1
- package/src/state/dispatch-ref.ts +1 -1
- package/src/state/layout-resize.ts +2 -2
- package/src/state/project-catalog.ts +275 -0
- package/src/state/project-data.ts +143 -0
- package/src/state/{session-persistence.ts → project-persistence.ts} +74 -68
- package/src/state/project-save.ts +46 -0
- package/src/state/project-workspaces.ts +392 -0
- package/src/state/reducers/auto-commit-state.ts +11 -10
- package/src/state/reducers/git-commit-modal-state.ts +122 -0
- package/src/state/reducers/git-mode-state.ts +1 -1
- package/src/state/reducers/git-panel-state.ts +10 -6
- package/src/state/reducers/modal-state.ts +290 -681
- package/src/state/reducers/multi-repo-state.ts +3 -6
- package/src/state/reducers/project-state.ts +312 -0
- package/src/state/reducers/settings-state.ts +80 -0
- package/src/state/reducers/tab-state.ts +107 -60
- package/src/state/reducers/ui-state.ts +4 -3
- package/src/state/selectors.ts +49 -37
- package/src/state/store.ts +57 -24
- package/src/state/types.ts +244 -405
- package/src/state/validation.ts +14 -12
- package/src/terminal-manager/manager-client.ts +32 -32
- package/src/terminal-manager/terminal-manager.ts +24 -24
- package/src/ui/components/git/git-panel.tsx +2 -2
- package/src/ui/components/git/git-view.tsx +12 -12
- package/src/ui/components/git/image-diff/image-diff-view.tsx +1 -1
- package/src/ui/components/git/pane/git-pane-widget.tsx +7 -7
- package/src/ui/components/layout/sidebar/project-list.tsx +444 -0
- package/src/ui/components/layout/sidebar/tab-item.tsx +27 -12
- package/src/ui/components/layout/sidebar/use-sidebar-auto-scroll.ts +1 -1
- package/src/ui/components/layout/sidebar/workspace-row.tsx +253 -0
- package/src/ui/components/layout/status-bar.tsx +6 -2
- package/src/ui/components/layout/terminal-pane.tsx +34 -29
- package/src/ui/components/layout/top-tab-bar.tsx +46 -36
- package/src/ui/components/modals/git/git-commit-modal.tsx +8 -8
- package/src/ui/components/modals/{sessions/create-session-modal.tsx → projects/create-project-modal.tsx} +12 -12
- package/src/ui/components/modals/{sessions/session-name-modal.tsx → projects/project-name-modal.tsx} +2 -2
- package/src/ui/components/modals/{sessions/session-picker-modal.tsx → projects/project-picker-modal.tsx} +33 -35
- package/src/ui/components/modals/settings/settings-search-modal.tsx +70 -0
- package/src/ui/components/modals/shared/form.tsx +34 -22
- package/src/ui/components/modals/shared/picker.tsx +10 -10
- package/src/ui/components/modals/shared/{worktree-delete-confirm.tsx → workspace-delete-confirm.tsx} +9 -9
- package/src/ui/components/modals/tabs/new-tab-modal.tsx +30 -324
- package/src/ui/components/modals/workspace/create-workspace-modal.tsx +101 -0
- package/src/ui/components/modals/{worktree/worktree-move-confirm-modal.tsx → workspace/workspace-move-confirm-modal.tsx} +5 -5
- package/src/ui/components/modals/{worktree/worktree-move-modal.tsx → workspace/workspace-move-modal.tsx} +29 -27
- package/src/ui/components/primitives/input-field.tsx +7 -3
- package/src/ui/components/primitives/surface.tsx +3 -0
- package/src/ui/components/settings/row-value.tsx +74 -0
- package/src/ui/components/settings/settings-row.tsx +78 -0
- package/src/ui/components/settings/settings-view.tsx +169 -0
- package/src/ui/components/setup/setup-widget.tsx +152 -0
- package/src/ui/flash/build-labels.ts +27 -27
- package/src/ui/hooks/use-activity-sprite.ts +73 -0
- package/src/ui/hooks/use-scroll-active-into-view.ts +26 -0
- package/src/ui/{session-ordering.ts → project-ordering.ts} +4 -4
- package/src/ui/root.tsx +139 -121
- package/src/ui/status-bar-model.ts +48 -34
- package/src/ui/terminal-graphics/kitty.ts +28 -2
- package/src/ui/terminal-graphics/sprites/done@330/0.png +0 -0
- package/src/ui/terminal-graphics/sprites/done@330/1.png +0 -0
- package/src/ui/terminal-graphics/sprites/idle@1000/0.png +0 -0
- package/src/ui/terminal-graphics/sprites/idle@1000/1.png +0 -0
- package/src/ui/terminal-graphics/sprites/waiting@170/0.png +0 -0
- package/src/ui/terminal-graphics/sprites/waiting@170/1.png +0 -0
- package/src/ui/terminal-graphics/sprites/working@150/0.png +0 -0
- package/src/ui/terminal-graphics/sprites/working@150/1.png +0 -0
- package/src/ui/terminal-graphics/sprites/working@150/2.png +0 -0
- package/src/ui/terminal-graphics/sprites/working@150/3.png +0 -0
- package/src/ui/terminal-graphics/sprites.ts +246 -0
- package/src/ui/truncate.ts +7 -0
- package/src/ui/widgets/registry.tsx +6 -3
- package/src/ui/widgets/widget-context-menu.ts +26 -10
- package/src/update.ts +2 -2
- package/src/app-runtime/session-actions.ts +0 -187
- package/src/cli/client/workspace-resolver.ts +0 -110
- package/src/cli/commands/workspace/close.ts +0 -32
- package/src/cli/commands/workspace/show.ts +0 -32
- package/src/cli/commands/worktree/create.ts +0 -55
- package/src/cli/commands/worktree/list.ts +0 -68
- package/src/cli/commands/worktree/remove.ts +0 -81
- package/src/git/worktree-branch-poller.ts +0 -54
- package/src/git/worktree-divergence-poller.ts +0 -59
- package/src/state/reducers/session-state.ts +0 -262
- package/src/state/session-catalog.ts +0 -143
- package/src/state/session-worktrees.ts +0 -260
- package/src/state/workspace-save.ts +0 -43
- package/src/ui/components/layout/sidebar/use-top-tab-bar-auto-scroll.ts +0 -30
- package/src/ui/components/layout/sidebar/workspace-list.tsx +0 -418
- package/src/ui/components/layout/sidebar/worktree-row.tsx +0 -149
- /package/src/ui/{components/git/image-diff → terminal-graphics}/dimensions.ts +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events'
|
|
2
2
|
import { connect, type Socket } from 'node:net'
|
|
3
3
|
|
|
4
|
-
import type { AssistantId,
|
|
5
|
-
import type { ResizeOptions, SessionBackend
|
|
4
|
+
import type { AssistantId, ProjectSnapshotV1 } from '../state/types'
|
|
5
|
+
import type { ProjectBackendEvents, ResizeOptions, SessionBackend } from './types'
|
|
6
6
|
|
|
7
7
|
import { getIpcDaemonSocketPath } from '../daemon/runtime-paths'
|
|
8
8
|
import { logDebug } from '../debug/input-log'
|
|
@@ -24,7 +24,7 @@ const IPC_REQUEST_TIMEOUT_MS = 10_000
|
|
|
24
24
|
const RECONNECT_DELAY_MS = 250
|
|
25
25
|
|
|
26
26
|
export class RemoteSessionBackend
|
|
27
|
-
extends EventEmitter<
|
|
27
|
+
extends EventEmitter<ProjectBackendEvents>
|
|
28
28
|
implements SessionBackend
|
|
29
29
|
{
|
|
30
30
|
private socket: Socket | null = null
|
|
@@ -38,12 +38,12 @@ export class RemoteSessionBackend
|
|
|
38
38
|
>()
|
|
39
39
|
private decoder = new MessageDecoder<ServerResponse | ServerEvent>(parseServerMessage)
|
|
40
40
|
private attached = false
|
|
41
|
-
private
|
|
41
|
+
private currentProjectId: string | null = null
|
|
42
42
|
private attachOptions: {
|
|
43
|
-
|
|
43
|
+
projectId: string
|
|
44
44
|
cols: number
|
|
45
45
|
rows: number
|
|
46
|
-
|
|
46
|
+
projectSnapshot?: ProjectSnapshotV1
|
|
47
47
|
} | null = null
|
|
48
48
|
private selectedProtocolVersion: number | null = null
|
|
49
49
|
private daemonCapabilities: ReadonlySet<string> = new Set()
|
|
@@ -68,7 +68,7 @@ export class RemoteSessionBackend
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
private closeSocket(reason: string,
|
|
71
|
+
private closeSocket(reason: string, preserveProject = false): void {
|
|
72
72
|
const socket = this.socket
|
|
73
73
|
this.socket = null
|
|
74
74
|
this.attached = false
|
|
@@ -77,8 +77,8 @@ export class RemoteSessionBackend
|
|
|
77
77
|
this.decoder.reset()
|
|
78
78
|
this.rejectPendingRequests(new Error(reason))
|
|
79
79
|
|
|
80
|
-
if (!
|
|
81
|
-
this.
|
|
80
|
+
if (!preserveProject) {
|
|
81
|
+
this.currentProjectId = null
|
|
82
82
|
this.attachOptions = null
|
|
83
83
|
}
|
|
84
84
|
|
|
@@ -94,7 +94,7 @@ export class RemoteSessionBackend
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
private handleConnectionLoss(reason: string): void {
|
|
97
|
-
logDebug('backend.remote.connectionLoss', {
|
|
97
|
+
logDebug('backend.remote.connectionLoss', { projectId: this.currentProjectId, reason })
|
|
98
98
|
this.closeSocket(reason, true)
|
|
99
99
|
if (this.shouldReconnect && this.attachOptions) {
|
|
100
100
|
void this.scheduleReconnect()
|
|
@@ -187,59 +187,71 @@ export class RemoteSessionBackend
|
|
|
187
187
|
break
|
|
188
188
|
case 'tabStatus':
|
|
189
189
|
logDebug('backend.remote.tabStatus', {
|
|
190
|
-
|
|
190
|
+
projectId: message.payload.projectId,
|
|
191
191
|
status: message.payload.status,
|
|
192
192
|
tabId: message.payload.tabId,
|
|
193
193
|
})
|
|
194
|
-
this.emit(
|
|
194
|
+
this.emit(
|
|
195
|
+
'tabActivity',
|
|
196
|
+
message.payload.tabId,
|
|
197
|
+
message.payload.status,
|
|
198
|
+
message.payload.workspaceId
|
|
199
|
+
)
|
|
195
200
|
break
|
|
196
201
|
case 'tabTurnComplete':
|
|
202
|
+
this.emit(
|
|
203
|
+
'tabTurnComplete',
|
|
204
|
+
message.payload.tabId,
|
|
205
|
+
message.payload.projectId,
|
|
206
|
+
message.payload.workspaceId
|
|
207
|
+
)
|
|
208
|
+
break
|
|
197
209
|
case 'tabQuestion':
|
|
198
|
-
// v13 orchestration
|
|
199
|
-
// backend. Enumerated so the switch stays exhaustive
|
|
210
|
+
// v13 orchestration signal — consumed by the headless CLI, not the UI
|
|
211
|
+
// backend. Enumerated so the switch stays exhaustive.
|
|
200
212
|
break
|
|
201
|
-
case '
|
|
202
|
-
logDebug('backend.remote.
|
|
203
|
-
|
|
213
|
+
case 'projectStatus':
|
|
214
|
+
logDebug('backend.remote.projectStatus', {
|
|
215
|
+
projectId: message.payload.projectId,
|
|
204
216
|
status: message.payload.status,
|
|
205
217
|
})
|
|
206
|
-
this.emit('
|
|
218
|
+
this.emit('projectActivity', message.payload.projectId, message.payload.status)
|
|
207
219
|
break
|
|
208
220
|
case 'tabAdded':
|
|
209
221
|
logDebug('backend.remote.tabAdded', {
|
|
210
|
-
|
|
222
|
+
projectId: message.payload.projectId,
|
|
211
223
|
tabId: message.payload.tab.id,
|
|
212
224
|
})
|
|
213
|
-
this.emit('tabAdded', message.payload.
|
|
225
|
+
this.emit('tabAdded', message.payload.projectId, message.payload.tab)
|
|
214
226
|
break
|
|
215
227
|
case 'tabMetadataUpdated':
|
|
216
|
-
this.emit('tabMetadataUpdated', message.payload.
|
|
228
|
+
this.emit('tabMetadataUpdated', message.payload.projectId, message.payload.tabId, {
|
|
217
229
|
autoRenameStatus: message.payload.autoRenameStatus,
|
|
218
230
|
title: message.payload.title,
|
|
219
231
|
})
|
|
220
232
|
break
|
|
221
|
-
case '
|
|
233
|
+
case 'projectCreateRequested':
|
|
222
234
|
this.emit(
|
|
223
|
-
'
|
|
235
|
+
'projectCreateRequested',
|
|
224
236
|
message.payload.name,
|
|
225
237
|
message.payload.projectPath,
|
|
226
238
|
message.payload.switch === true
|
|
227
239
|
)
|
|
228
240
|
break
|
|
229
|
-
case '
|
|
230
|
-
this.emit('
|
|
241
|
+
case 'projectSwitchRequested':
|
|
242
|
+
this.emit('projectSwitchRequested', message.payload.targetProjectId)
|
|
231
243
|
break
|
|
232
|
-
case '
|
|
233
|
-
this.emit('
|
|
244
|
+
case 'projectCloseRequested':
|
|
245
|
+
this.emit('projectCloseRequested', message.payload.targetProjectId)
|
|
234
246
|
break
|
|
235
|
-
case '
|
|
236
|
-
this.emit('
|
|
247
|
+
case 'projectSwitched':
|
|
248
|
+
this.emit('projectSwitched', message.payload.projectId)
|
|
237
249
|
break
|
|
238
|
-
case '
|
|
239
|
-
this.emit('
|
|
250
|
+
case 'workspaceAdded':
|
|
251
|
+
this.emit('workspaceAdded', message.payload.projectId, message.payload.workspace)
|
|
240
252
|
break
|
|
241
|
-
case '
|
|
242
|
-
this.emit('
|
|
253
|
+
case 'workspaceRemoved':
|
|
254
|
+
this.emit('workspaceRemoved', message.payload.projectId, message.payload.workspaceId)
|
|
243
255
|
break
|
|
244
256
|
}
|
|
245
257
|
}
|
|
@@ -314,10 +326,10 @@ export class RemoteSessionBackend
|
|
|
314
326
|
}
|
|
315
327
|
|
|
316
328
|
private async performAttach(options: {
|
|
317
|
-
|
|
329
|
+
projectId: string
|
|
318
330
|
cols: number
|
|
319
331
|
rows: number
|
|
320
|
-
|
|
332
|
+
projectSnapshot?: ProjectSnapshotV1
|
|
321
333
|
}): Promise<AttachResult> {
|
|
322
334
|
await this.connectAndHandshake()
|
|
323
335
|
|
|
@@ -360,13 +372,13 @@ export class RemoteSessionBackend
|
|
|
360
372
|
try {
|
|
361
373
|
await this.performAttach(reconnectOptions)
|
|
362
374
|
logDebug('backend.remote.reconnect.success', {
|
|
363
|
-
|
|
375
|
+
projectId: reconnectOptions.projectId,
|
|
364
376
|
})
|
|
365
377
|
return
|
|
366
378
|
} catch (error) {
|
|
367
379
|
logDebug('backend.remote.reconnect.retry', {
|
|
368
380
|
error: error instanceof Error ? error.message : String(error),
|
|
369
|
-
|
|
381
|
+
projectId: reconnectOptions.projectId,
|
|
370
382
|
})
|
|
371
383
|
await Bun.sleep(RECONNECT_DELAY_MS)
|
|
372
384
|
}
|
|
@@ -381,27 +393,27 @@ export class RemoteSessionBackend
|
|
|
381
393
|
}
|
|
382
394
|
|
|
383
395
|
async attach(options: {
|
|
384
|
-
|
|
396
|
+
projectId: string
|
|
385
397
|
cols: number
|
|
386
398
|
rows: number
|
|
387
|
-
|
|
399
|
+
projectSnapshot?: ProjectSnapshotV1
|
|
388
400
|
}): Promise<AttachResult> {
|
|
389
401
|
this.shouldReconnect = true
|
|
390
|
-
this.
|
|
402
|
+
this.currentProjectId = options.projectId
|
|
391
403
|
this.attachOptions = options
|
|
392
404
|
|
|
393
405
|
logDebug('backend.remote.attach.start', {
|
|
394
406
|
cols: options.cols,
|
|
407
|
+
projectId: options.projectId,
|
|
395
408
|
rows: options.rows,
|
|
396
|
-
|
|
397
|
-
snapshotTabs: options.workspaceSnapshot?.tabs.length ?? 0,
|
|
409
|
+
snapshotTabs: options.projectSnapshot?.tabs.length ?? 0,
|
|
398
410
|
socketPath: getIpcDaemonSocketPath(),
|
|
399
411
|
})
|
|
400
412
|
|
|
401
413
|
const result = await this.performAttach(options)
|
|
402
414
|
logDebug('backend.remote.attach.success', {
|
|
403
415
|
activeTabId: result.activeTabId,
|
|
404
|
-
|
|
416
|
+
projectId: options.projectId,
|
|
405
417
|
tabs: result.tabs.length,
|
|
406
418
|
})
|
|
407
419
|
|
|
@@ -417,7 +429,7 @@ export class RemoteSessionBackend
|
|
|
417
429
|
cols: number
|
|
418
430
|
rows: number
|
|
419
431
|
cwd?: string
|
|
420
|
-
|
|
432
|
+
workspaceId?: string
|
|
421
433
|
autoRenameCandidate?: boolean
|
|
422
434
|
}): void {
|
|
423
435
|
if (!this.attached) {
|
|
@@ -490,7 +502,7 @@ export class RemoteSessionBackend
|
|
|
490
502
|
logDebug('backend.remote.skipResizeBeforeAttach', { cols, rows })
|
|
491
503
|
return
|
|
492
504
|
}
|
|
493
|
-
logDebug('backend.remote.resize', { cols,
|
|
505
|
+
logDebug('backend.remote.resize', { cols, projectId: this.currentProjectId, rows })
|
|
494
506
|
this.dispatchCommand(
|
|
495
507
|
{ id: crypto.randomUUID(), payload: { cols, rows }, type: 'resizeClient' },
|
|
496
508
|
'resizeClient'
|
|
@@ -533,17 +545,17 @@ export class RemoteSessionBackend
|
|
|
533
545
|
this.dispatchCommand({ id: crypto.randomUUID(), payload: {}, type: 'disposeAll' }, 'disposeAll')
|
|
534
546
|
}
|
|
535
547
|
|
|
536
|
-
|
|
537
|
-
// Fire-and-forget so `
|
|
538
|
-
// the daemon roundtrip. The daemon relays this as a `
|
|
539
|
-
// broadcast that unblocks any `aimux
|
|
548
|
+
announceProjectSwitched(projectId: string): void {
|
|
549
|
+
// Fire-and-forget so `handleSwitchProjectEffect` doesn't block waiting on
|
|
550
|
+
// the daemon roundtrip. The daemon relays this as a `projectSwitched`
|
|
551
|
+
// broadcast that unblocks any `aimux project switch --wait` CLI.
|
|
540
552
|
this.dispatchCommand(
|
|
541
553
|
{
|
|
542
554
|
id: crypto.randomUUID(),
|
|
543
|
-
payload: {
|
|
544
|
-
type: '
|
|
555
|
+
payload: { projectId },
|
|
556
|
+
type: 'announceProjectSwitched',
|
|
545
557
|
},
|
|
546
|
-
'
|
|
558
|
+
'announceProjectSwitched'
|
|
547
559
|
)
|
|
548
560
|
}
|
|
549
561
|
|
|
@@ -1,60 +1,71 @@
|
|
|
1
1
|
import type { EventEmitter } from 'node:events'
|
|
2
2
|
|
|
3
3
|
import type {
|
|
4
|
-
|
|
4
|
+
ProjectSnapshotV1,
|
|
5
|
+
ProjectStatus,
|
|
5
6
|
TabActivity,
|
|
6
7
|
TabSession,
|
|
7
8
|
TerminalModeState,
|
|
8
9
|
TerminalSnapshot,
|
|
9
|
-
|
|
10
|
-
WorktreeRecord,
|
|
10
|
+
WorkspaceRecord,
|
|
11
11
|
} from '../state/types'
|
|
12
12
|
|
|
13
|
-
export interface
|
|
13
|
+
export interface ProjectBackendEvents {
|
|
14
14
|
render: [tabId: string, viewport: TerminalSnapshot, terminalModes: TerminalModeState]
|
|
15
15
|
exit: [tabId: string, exitCode: number]
|
|
16
16
|
error: [tabId: string, message: string]
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
projectActivity: [projectId: string, status: ProjectStatus]
|
|
18
|
+
/**
|
|
19
|
+
* `workspaceId` is present when the backend knows which workspace the tab
|
|
20
|
+
* belongs to. The UI holds tabs for the attached project only, so this is
|
|
21
|
+
* what lets a *foreign* project's workspace row show a status glyph.
|
|
22
|
+
*/
|
|
23
|
+
tabActivity: [tabId: string, activity: TabActivity, workspaceId?: string]
|
|
24
|
+
/**
|
|
25
|
+
* A tab's turn ended: its `idle` held for the daemon's settle window. Edge
|
|
26
|
+
* triggered, once per turn — the honest "the agent has finished" signal,
|
|
27
|
+
* unlike a raw `idle` which flickers between tool calls.
|
|
28
|
+
*/
|
|
29
|
+
tabTurnComplete: [tabId: string, projectId: string, workspaceId?: string]
|
|
19
30
|
/**
|
|
20
31
|
* Fired when a tab was added by a sibling client (e.g. the CLI control
|
|
21
|
-
* plane creating a tab in the same
|
|
32
|
+
* plane creating a tab in the same project). The UI subscribes and
|
|
22
33
|
* dispatches `add-tab` so its store learns about the new tab before any
|
|
23
34
|
* `tabRender` event lands.
|
|
24
35
|
*/
|
|
25
|
-
tabAdded: [
|
|
36
|
+
tabAdded: [projectId: string, tab: TabSession]
|
|
26
37
|
tabMetadataUpdated: [
|
|
27
|
-
|
|
38
|
+
projectId: string,
|
|
28
39
|
tabId: string,
|
|
29
40
|
patch: { title?: string; autoRenameStatus?: 'eligible' | 'attempted' },
|
|
30
41
|
]
|
|
31
42
|
/**
|
|
32
|
-
* v12
|
|
33
|
-
* `
|
|
43
|
+
* v12 project-lifecycle events. Fired when a CLI issued
|
|
44
|
+
* `createProject` / `switchProject` / `closeProject` and the daemon
|
|
34
45
|
* relays as an event because a UI is attached. The UI reducer owns the
|
|
35
46
|
* catalog write; see `backend-runtime-events.ts` for the wiring.
|
|
36
47
|
*/
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
48
|
+
projectCreateRequested: [name: string, projectPath: string | undefined, doSwitch: boolean]
|
|
49
|
+
projectSwitchRequested: [targetProjectId: string]
|
|
50
|
+
projectCloseRequested: [targetProjectId: string]
|
|
51
|
+
projectSwitched: [projectId: string]
|
|
41
52
|
/**
|
|
42
|
-
* v12
|
|
43
|
-
* `
|
|
53
|
+
* v12 workspace-lifecycle events. Fired when a CLI issued
|
|
54
|
+
* `addWorkspaceRecord` / `removeWorkspaceRecord` and the daemon relays.
|
|
44
55
|
*/
|
|
45
|
-
|
|
46
|
-
|
|
56
|
+
workspaceAdded: [projectId: string, workspace: WorkspaceRecord]
|
|
57
|
+
workspaceRemoved: [projectId: string, workspaceId: string]
|
|
47
58
|
}
|
|
48
59
|
|
|
49
60
|
export interface ResizeOptions {
|
|
50
61
|
/** When set, opentui's flush is invoked synchronously so the resize lands
|
|
51
62
|
* in the same commit. Used by the chrome cascade to keep the cols/rows
|
|
52
|
-
* state and the backend buffer in lockstep on sidebar/
|
|
63
|
+
* state and the backend buffer in lockstep on sidebar/project-bar toggles. */
|
|
53
64
|
sync?: boolean
|
|
54
65
|
/** When true, this resize is the closed-loop measurement of the rendered
|
|
55
66
|
* content box — the authoritative size for the visible viewport. The
|
|
56
67
|
* `LocalSessionBackend` uses this to lift the per-tab snapshot gate that
|
|
57
|
-
* suppresses renders between
|
|
68
|
+
* suppresses renders between project/attach time and the first measurement,
|
|
58
69
|
* so the frontend never paints a snapshot whose row count disagrees with
|
|
59
70
|
* the pane's actual height. */
|
|
60
71
|
confirmedFromMeasurement?: boolean
|
|
@@ -64,19 +75,19 @@ export interface BackendAttachResult {
|
|
|
64
75
|
tabs: TabSession[]
|
|
65
76
|
activeTabId: string | null
|
|
66
77
|
/**
|
|
67
|
-
* Per-
|
|
78
|
+
* Per-project status snapshot taken at attach time and applied
|
|
68
79
|
* atomically with tab hydration to prevent an unknown-tab race on
|
|
69
|
-
* separate `
|
|
80
|
+
* separate `projectActivity` events.
|
|
70
81
|
*/
|
|
71
|
-
|
|
82
|
+
initialProjectStatuses: { projectId: string; status: ProjectStatus }[]
|
|
72
83
|
}
|
|
73
84
|
|
|
74
|
-
export interface SessionBackend extends EventEmitter<
|
|
85
|
+
export interface SessionBackend extends EventEmitter<ProjectBackendEvents> {
|
|
75
86
|
attach(options: {
|
|
76
|
-
|
|
87
|
+
projectId: string
|
|
77
88
|
cols: number
|
|
78
89
|
rows: number
|
|
79
|
-
|
|
90
|
+
projectSnapshot?: ProjectSnapshotV1
|
|
80
91
|
}): Promise<BackendAttachResult | null>
|
|
81
92
|
createSession(options: {
|
|
82
93
|
tabId: string
|
|
@@ -87,10 +98,10 @@ export interface SessionBackend extends EventEmitter<SessionBackendEvents> {
|
|
|
87
98
|
cols: number
|
|
88
99
|
rows: number
|
|
89
100
|
cwd?: string
|
|
90
|
-
/**
|
|
101
|
+
/** Workspace the tab belongs to. Passed through to the daemon so its
|
|
91
102
|
* registry surfaces the right grouping in `listTabs` for headless
|
|
92
103
|
* consumers (CLI control plane). */
|
|
93
|
-
|
|
104
|
+
workspaceId?: string
|
|
94
105
|
autoRenameCandidate?: boolean
|
|
95
106
|
}): void
|
|
96
107
|
write(tabId: string, input: string): void
|
|
@@ -104,9 +115,9 @@ export interface SessionBackend extends EventEmitter<SessionBackendEvents> {
|
|
|
104
115
|
disposeAll(): void
|
|
105
116
|
destroy(keepSessions?: boolean): Promise<void> | void
|
|
106
117
|
/**
|
|
107
|
-
* v12 — the UI calls this after `
|
|
108
|
-
* daemon can broadcast `
|
|
118
|
+
* v12 — the UI calls this after `handleSwitchProjectEffect` finishes so the
|
|
119
|
+
* daemon can broadcast `projectSwitched` and any `aimux project switch
|
|
109
120
|
* --wait` CLI can exit. No-op on local backends (no daemon).
|
|
110
121
|
*/
|
|
111
|
-
|
|
122
|
+
announceProjectSwitched(projectId: string): void
|
|
112
123
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { AIUsageToolConfig, AutoCommitConfig } from '@brimveyn/aimux-config'
|
|
2
|
+
|
|
3
|
+
import { useMemo } from 'react'
|
|
4
|
+
|
|
5
|
+
import { AUTO_COMMIT_ENABLED, AUTO_COMMIT_TIMEOUT } from './sections/automation'
|
|
6
|
+
import { AUTO_COMMIT_MODEL_PREFIX, SNIPPET_TRIGGER_CHAR } from './sections/commands'
|
|
7
|
+
import { ACTIVITY_SPRITES, HARMONIZE_CLAUDE_THEME } from './sections/experimental'
|
|
8
|
+
import { AI_USAGE_ENABLED, AI_USAGE_POLL_SECONDS } from './sections/status-bar'
|
|
9
|
+
import { useSettingsStore } from './settings-store'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The read side of the settings screen: where a consumer used to take a value
|
|
13
|
+
* straight off the resolved config, it takes it from here instead, and the change
|
|
14
|
+
* lands without a restart.
|
|
15
|
+
*
|
|
16
|
+
* One hook per consumer, all in this file, so the row ids are read in exactly one
|
|
17
|
+
* place — and the ids themselves come from the section that defines them.
|
|
18
|
+
*/
|
|
19
|
+
export function useAutoCommitConfig(fromConfigFile: AutoCommitConfig): AutoCommitConfig {
|
|
20
|
+
const values = useSettingsStore((s) => s.values)
|
|
21
|
+
const timeoutMs = values[AUTO_COMMIT_TIMEOUT]
|
|
22
|
+
const models = { ...fromConfigFile.models }
|
|
23
|
+
for (const [id, value] of Object.entries(values)) {
|
|
24
|
+
if (!id.startsWith(AUTO_COMMIT_MODEL_PREFIX)) continue
|
|
25
|
+
// An empty row means "leave the default alone", not "use no model".
|
|
26
|
+
if (typeof value === 'string' && value !== '') {
|
|
27
|
+
models[id.slice(AUTO_COMMIT_MODEL_PREFIX.length)] = value
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
enabled: values[AUTO_COMMIT_ENABLED] === true,
|
|
32
|
+
models,
|
|
33
|
+
timeoutMs: typeof timeoutMs === 'number' ? timeoutMs : fromConfigFile.timeoutMs,
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The character that opens an inline snippet trigger. Clamped to one character,
|
|
39
|
+
* the same rule the config resolver applies — a two-character value would simply
|
|
40
|
+
* never match.
|
|
41
|
+
*/
|
|
42
|
+
export function useSnippetTriggerChar(fromConfigFile: string): string {
|
|
43
|
+
const stored = useSettingsStore((s) => s.values[SNIPPET_TRIGGER_CHAR])
|
|
44
|
+
if (typeof stored === 'string' && stored.length === 1) return stored
|
|
45
|
+
return fromConfigFile
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Memoized on purpose: its consumer restarts the polling service whenever this
|
|
50
|
+
* object changes, so a fresh identity per render would restart it per render.
|
|
51
|
+
*/
|
|
52
|
+
export function useAIUsageConfig(fromConfigFile: AIUsageToolConfig | undefined): AIUsageToolConfig {
|
|
53
|
+
const values = useSettingsStore((s) => s.values)
|
|
54
|
+
const enabled = values[AI_USAGE_ENABLED] === true
|
|
55
|
+
const poll = values[AI_USAGE_POLL_SECONDS]
|
|
56
|
+
const pollSeconds = typeof poll === 'number' ? poll : fromConfigFile?.pollSeconds
|
|
57
|
+
return useMemo(
|
|
58
|
+
() => ({ ...fromConfigFile, enabled, pollSeconds }),
|
|
59
|
+
[enabled, fromConfigFile, pollSeconds]
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The model that would write the commit message for this assistant, or undefined
|
|
65
|
+
* when nothing overrides the built-in default. The generating overlay names it, so
|
|
66
|
+
* an override set on the settings screen is visible where it applies.
|
|
67
|
+
*/
|
|
68
|
+
export function useAutoCommitModel(assistant: string | undefined): string | undefined {
|
|
69
|
+
const stored = useSettingsStore((s) =>
|
|
70
|
+
assistant == null || assistant === ''
|
|
71
|
+
? undefined
|
|
72
|
+
: s.values[`${AUTO_COMMIT_MODEL_PREFIX}${assistant}`]
|
|
73
|
+
)
|
|
74
|
+
return typeof stored === 'string' && stored !== '' ? stored : undefined
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Whether agent state is drawn as a sprite. Read straight off the store rather
|
|
79
|
+
* than through the resolved config: hydration already folds in whatever
|
|
80
|
+
* `aimux.config.ts` declares, and this way the toggle lands without a restart.
|
|
81
|
+
*/
|
|
82
|
+
export function useActivitySprites(): boolean {
|
|
83
|
+
return useSettingsStore((s) => s.values[ACTIVITY_SPRITES] === true)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Whether to keep Claude Code's theme in step with the active aimux theme. */
|
|
87
|
+
export function useHarmonizeClaudeTheme(fromConfigFile: boolean | undefined): boolean {
|
|
88
|
+
const stored = useSettingsStore((s) => s.values[HARMONIZE_CLAUDE_THEME])
|
|
89
|
+
return typeof stored === 'boolean' ? stored : fromConfigFile === true
|
|
90
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ProjectRecord } from '../state/types'
|
|
2
|
+
import type { SettingRow } from './types'
|
|
3
|
+
|
|
4
|
+
import { sectionRows, SETTING_SECTIONS } from './sections'
|
|
5
|
+
|
|
6
|
+
export interface SettingSearchHit {
|
|
7
|
+
row: SettingRow
|
|
8
|
+
sectionId: string
|
|
9
|
+
sectionLabel: string
|
|
10
|
+
/** Index within its own section, which is what the cursor is expressed in. */
|
|
11
|
+
rowIndex: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Every setting the query matches, across every section. Ten sections is more
|
|
16
|
+
* than anyone will scan, and this is the only list in the app you could not type
|
|
17
|
+
* into to find something.
|
|
18
|
+
*
|
|
19
|
+
* Shared with `getModalOptionCount` so the picker and the reducer that moves its
|
|
20
|
+
* cursor count the same list — two filters would drift the moment one changed.
|
|
21
|
+
*/
|
|
22
|
+
export function filterSettingRows(
|
|
23
|
+
projects: readonly ProjectRecord[],
|
|
24
|
+
query: string | null
|
|
25
|
+
): SettingSearchHit[] {
|
|
26
|
+
const needle = (query ?? '').trim().toLowerCase()
|
|
27
|
+
const hits: SettingSearchHit[] = []
|
|
28
|
+
|
|
29
|
+
for (const section of SETTING_SECTIONS) {
|
|
30
|
+
const rows = sectionRows(section, projects)
|
|
31
|
+
for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
|
|
32
|
+
const row = rows[rowIndex]
|
|
33
|
+
if (!row) continue
|
|
34
|
+
// The id is in the haystack deliberately: it is the key in `aimux.json`, so
|
|
35
|
+
// someone who saw it in that file can search for it.
|
|
36
|
+
const haystack = `${row.label} ${row.description ?? ''} ${row.id} ${section.label}`
|
|
37
|
+
if (needle !== '' && !haystack.toLowerCase().includes(needle)) continue
|
|
38
|
+
hits.push({ row, rowIndex, sectionId: section.id, sectionLabel: section.label })
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return hits
|
|
43
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { SettingSection } from '../types'
|
|
2
|
+
|
|
3
|
+
import { version as APP_VERSION } from '../../../package.json'
|
|
4
|
+
import { getConfigPath } from '../../config'
|
|
5
|
+
import { getProfileConfigDir, getProfileName } from '../../profile-paths'
|
|
6
|
+
import { settingsStore } from '../settings-store'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Read-only. Everything here answers a question you would otherwise answer by
|
|
10
|
+
* digging through `~/.config`: which build is running, which profile, where its
|
|
11
|
+
* files are, and how many settings your own config file is holding onto.
|
|
12
|
+
*/
|
|
13
|
+
export const ABOUT_SECTION: SettingSection = {
|
|
14
|
+
id: 'about',
|
|
15
|
+
label: 'About',
|
|
16
|
+
rows: [
|
|
17
|
+
{ id: 'about.version', kind: 'info', label: 'Version', value: () => APP_VERSION },
|
|
18
|
+
{
|
|
19
|
+
description: 'Set with --profile, or AIMUX_PROFILE in the environment.',
|
|
20
|
+
id: 'about.profile',
|
|
21
|
+
kind: 'info',
|
|
22
|
+
label: 'Profile',
|
|
23
|
+
value: () => getProfileName(),
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: 'about.configDir',
|
|
27
|
+
kind: 'info',
|
|
28
|
+
label: 'Config directory',
|
|
29
|
+
value: () => getProfileConfigDir(),
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
description: 'Settings written here land in this file.',
|
|
33
|
+
id: 'about.configFile',
|
|
34
|
+
kind: 'info',
|
|
35
|
+
label: 'App config',
|
|
36
|
+
value: () => getConfigPath(),
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
description: 'Those come back from your config file every launch.',
|
|
40
|
+
id: 'about.fromConfigFile',
|
|
41
|
+
kind: 'info',
|
|
42
|
+
label: 'Set in aimux.config.ts',
|
|
43
|
+
value: () => {
|
|
44
|
+
const count = settingsStore.getState().fromConfigFile.size
|
|
45
|
+
return count === 0 ? 'nothing' : `${String(count)} setting(s)`
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { SettingSection } from '../types'
|
|
2
|
+
|
|
3
|
+
import { dispatchGlobal, runSideEffectGlobal } from '../../state/dispatch-ref'
|
|
4
|
+
import { getCurrentMode, getCurrentThemeId, getTransparent } from '../../ui/theme'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The theme lives in its own store, not in `AppState`. Rows over it read it
|
|
8
|
+
* directly and write through the side effects that already apply it live and
|
|
9
|
+
* persist it — every row on this screen calls `useTheme`, so a theme change
|
|
10
|
+
* re-renders them all and these reads never go stale.
|
|
11
|
+
*/
|
|
12
|
+
export const APPEARANCE_SECTION: SettingSection = {
|
|
13
|
+
id: 'appearance',
|
|
14
|
+
label: 'Appearance',
|
|
15
|
+
rows: [
|
|
16
|
+
{
|
|
17
|
+
// Hands over to the picker rather than being a list of 30-odd options on
|
|
18
|
+
// this screen: it filters as you type and previews each theme as you move.
|
|
19
|
+
// It comes back here when it closes, which is what `returnTo` is for.
|
|
20
|
+
description: 'Filters as you type, and previews as you move.',
|
|
21
|
+
id: 'appearance.theme',
|
|
22
|
+
kind: 'action',
|
|
23
|
+
label: 'Theme',
|
|
24
|
+
run: () => {
|
|
25
|
+
dispatchGlobal({ returnTo: 'settings', type: 'open-theme-picker' })
|
|
26
|
+
runSideEffectGlobal({ action: 'open', type: 'apply-theme' })
|
|
27
|
+
},
|
|
28
|
+
value: () => `${getCurrentThemeId()} ›`,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: 'appearance.mode',
|
|
32
|
+
kind: 'select',
|
|
33
|
+
label: 'Mode',
|
|
34
|
+
options: [
|
|
35
|
+
{ label: 'dark', value: 'dark' },
|
|
36
|
+
{ label: 'light', value: 'light' },
|
|
37
|
+
],
|
|
38
|
+
read: () => getCurrentMode(),
|
|
39
|
+
storage: 'app',
|
|
40
|
+
// Flips rather than sets, which is safe because `writeRow` never hands a
|
|
41
|
+
// row the value it already has.
|
|
42
|
+
write: () => runSideEffectGlobal({ type: 'toggle-mode' }),
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
description: "Let the host terminal's own background show through.",
|
|
46
|
+
id: 'appearance.transparent',
|
|
47
|
+
kind: 'toggle',
|
|
48
|
+
label: 'Transparent background',
|
|
49
|
+
read: () => getTransparent(),
|
|
50
|
+
storage: 'app',
|
|
51
|
+
write: () => runSideEffectGlobal({ type: 'toggle-transparent' }),
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
}
|