diff --git a/src/renderer/src/components/settings/ExperimentalPane.tsx b/src/renderer/src/components/settings/ExperimentalPane.tsx
index b02e25a89e0..222456af0cb 100644
--- a/src/renderer/src/components/settings/ExperimentalPane.tsx
+++ b/src/renderer/src/components/settings/ExperimentalPane.tsx
@@ -162,8 +162,8 @@ export function ExperimentalPane({
Terminal Macros
- Define named terminal launch presets that open a new tab, optionally create a split
- right or down, and seed the new terminal with startup text. This remains
+ Define named terminal launch presets that open a new tab in the current tab group or
+ a new tab split, then seed the new terminal with startup text. This remains
experimental and appears in the tab-bar new-tab menu only when enabled.
diff --git a/src/renderer/src/components/settings/TerminalMacrosSection.tsx b/src/renderer/src/components/settings/TerminalMacrosSection.tsx
index 107c886e7f2..0c5bcc5f172 100644
--- a/src/renderer/src/components/settings/TerminalMacrosSection.tsx
+++ b/src/renderer/src/components/settings/TerminalMacrosSection.tsx
@@ -21,10 +21,10 @@ type EditorState =
function layoutLabel(layout: TerminalMacro['layout']): string {
if (layout === 'split-right') {
- return 'Split Right'
+ return 'Tab Split Right'
}
if (layout === 'split-down') {
- return 'Split Down'
+ return 'Tab Split Down'
}
return 'Tab'
}
@@ -54,7 +54,7 @@ export function TerminalMacrosSection({
Saved Macros
- Each macro opens a named tab and can seed one startup split.
+ Each macro opens a named tab in the current tab group or a new tab split.
- Macros open a named terminal tab and optionally create one startup split.
+ Macros open a named terminal tab and can place it in a tab split.
@@ -108,8 +108,8 @@ export function TerminalMacroDialog({
className="justify-start"
>
Tab
- Split Right
- Split Down
+ Tab Split Right
+ Tab Split Down
@@ -124,24 +124,15 @@ export function TerminalMacroDialog({
rows={5}
className="min-h-28 w-full resize-y rounded-md border border-input bg-transparent px-3 py-2 text-sm font-mono shadow-xs outline-none transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50"
/>
- {targetsSplitPane ? (
-
- This startup text runs only in the newly created split pane. The original pane is
- left alone.
-
- ) : (
+ {!targetsSplitPane && (
Optional startup text for the new tab.
)}
setDraft((current) => ({ ...current, appendEnter: !current.appendEnter }))
}
diff --git a/src/renderer/src/lib/launch-terminal-macro.test.ts b/src/renderer/src/lib/launch-terminal-macro.test.ts
index 251241dc426..77ef64b3d4f 100644
--- a/src/renderer/src/lib/launch-terminal-macro.test.ts
+++ b/src/renderer/src/lib/launch-terminal-macro.test.ts
@@ -2,11 +2,18 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
const state = {
createTab: vi.fn(),
+ createEmptySplitGroup: vi.fn(),
setTabCustomTitle: vi.fn(),
queueTabStartupCommand: vi.fn(),
queueTabSetupSplit: vi.fn(),
setActiveTabType: vi.fn(),
setTabBarOrder: vi.fn(),
+ activeGroupIdByWorktree: {
+ 'wt-1': 'group-1'
+ },
+ groupsByWorktree: {
+ 'wt-1': [{ id: 'group-1' }]
+ },
tabsByWorktree: {
'wt-1': [{ id: 'existing-tab' }, { id: 'tab-1' }]
},
@@ -29,6 +36,7 @@ describe('launchTerminalMacro', () => {
beforeEach(() => {
vi.clearAllMocks()
state.createTab.mockReturnValue({ id: 'tab-1', title: 'Terminal 1' })
+ state.createEmptySplitGroup.mockReturnValue('group-2')
})
it('creates a named tab and queues startup for tab macros', async () => {
@@ -62,7 +70,7 @@ describe('launchTerminalMacro', () => {
expect(result).toEqual({ tabId: 'tab-1' })
})
- it('queues an initial split and sends startup only to the new split pane', async () => {
+ it('creates a tab group split and queues startup on the new tab', async () => {
const { launchTerminalMacro } = await import('./launch-terminal-macro')
launchTerminalMacro({
@@ -76,11 +84,12 @@ describe('launchTerminalMacro', () => {
worktreeId: 'wt-1'
})
- expect(state.queueTabStartupCommand).not.toHaveBeenCalled()
- expect(state.queueTabSetupSplit).toHaveBeenCalledWith('tab-1', {
- direction: 'vertical',
+ expect(state.createEmptySplitGroup).toHaveBeenCalledWith('wt-1', 'group-1', 'right')
+ expect(state.createTab).toHaveBeenCalledWith('wt-1', 'group-2')
+ expect(state.queueTabStartupCommand).toHaveBeenCalledWith('tab-1', {
command: 'npm run dev\r'
})
+ expect(state.queueTabSetupSplit).not.toHaveBeenCalled()
})
it('returns null for blank names', async () => {
diff --git a/src/renderer/src/lib/launch-terminal-macro.ts b/src/renderer/src/lib/launch-terminal-macro.ts
index 8ebb07ebbf7..ad2784513fe 100644
--- a/src/renderer/src/lib/launch-terminal-macro.ts
+++ b/src/renderer/src/lib/launch-terminal-macro.ts
@@ -20,27 +20,25 @@ export function launchTerminalMacro({
return null
}
- const tab = store.createTab(worktreeId, groupId)
+ const targetGroupId =
+ macro.layout === 'split-right' || macro.layout === 'split-down'
+ ? resolveTerminalMacroSplitGroupId({
+ worktreeId,
+ groupId,
+ direction: macro.layout === 'split-right' ? 'right' : 'down'
+ })
+ : groupId
+
+ const tab = store.createTab(worktreeId, targetGroupId)
store.setTabCustomTitle(tab.id, name)
const command = macro.command.trimEnd()
- if (command && macro.layout === 'tab') {
+ if (command) {
store.queueTabStartupCommand(tab.id, {
command: buildTerminalMacroInput(command, macro.appendEnter !== false)
})
}
- if (macro.layout === 'split-right' || macro.layout === 'split-down') {
- store.queueTabSetupSplit(tab.id, {
- direction: macro.layout === 'split-right' ? 'vertical' : 'horizontal',
- ...(command
- ? {
- command: buildTerminalMacroInput(command, macro.appendEnter !== false)
- }
- : {})
- })
- }
-
// Why: macro launches should surface the new terminal immediately even when
// the user currently has an editor/browser active in the same worktree.
store.setActiveTabType('terminal')
@@ -63,3 +61,26 @@ export function launchTerminalMacro({
return { tabId: tab.id }
}
+
+function resolveTerminalMacroSplitGroupId({
+ worktreeId,
+ groupId,
+ direction
+}: {
+ worktreeId: string
+ groupId?: string
+ direction: 'right' | 'down'
+}): string | undefined {
+ const store = useAppStore.getState()
+ const sourceGroupId =
+ groupId ??
+ store.activeGroupIdByWorktree[worktreeId] ??
+ store.groupsByWorktree[worktreeId]?.[0]?.id
+ if (!sourceGroupId) {
+ return undefined
+ }
+
+ // Why: macro "split right/down" means an Orca tab-group split, not an
+ // xterm pane split. Keeping it here avoids remounting the source terminal.
+ return store.createEmptySplitGroup(worktreeId, sourceGroupId, direction) ?? undefined
+}