From 4fba807c1df7e7500718234b5948f5fde45e87da Mon Sep 17 00:00:00 2001 From: Martin Laporte Date: Wed, 8 Apr 2026 11:33:06 +0200 Subject: [PATCH 1/2] feat: implement Ghostty tab naming for Open in Ghostty button and remove dedicated Open Opencode button - Modified launchGhostty function to use AppleScript for tab management and naming - Implemented tab switching logic to reuse existing tabs for same worktree path - Added tab title setting using worktree directory name - Removed dedicated Open Opencode button from LaunchButtons component - Updated test to verify AppleScript usage instead of open command --- .../src/bun/services/launcher.test.ts | 4 +-- .../treebeard/src/bun/services/launcher.ts | 32 +++++++++++++------ .../src/components/LaunchButtons.tsx | 17 ---------- 3 files changed, 25 insertions(+), 28 deletions(-) diff --git a/packages/treebeard/src/bun/services/launcher.test.ts b/packages/treebeard/src/bun/services/launcher.test.ts index 4568330..f80271e 100644 --- a/packages/treebeard/src/bun/services/launcher.test.ts +++ b/packages/treebeard/src/bun/services/launcher.test.ts @@ -29,13 +29,13 @@ describe('launcher service', () => { ) }) - it('launches ghostty with open -a and path argument', async () => { + it('launches ghostty with AppleScript and sets tab title', async () => { const spawn = setBunSpawnQueue([{ stdout: '' }]) await launchGhostty('/repo/worktree') expect(spawn).toHaveBeenCalledWith( - ['open', '-a', 'Ghostty.app', '/repo/worktree'], + ['/usr/bin/osascript', '-e', expect.stringContaining('tell application "Ghostty"')], expect.objectContaining({ stdout: 'ignore', stderr: 'ignore' }) ) }) diff --git a/packages/treebeard/src/bun/services/launcher.ts b/packages/treebeard/src/bun/services/launcher.ts index f46deae..7269680 100644 --- a/packages/treebeard/src/bun/services/launcher.ts +++ b/packages/treebeard/src/bun/services/launcher.ts @@ -12,15 +12,29 @@ export async function launchIde(ideId: IdeId, worktreePath: string): Promise { - // Pass the path as a file argument so open(1) forwards it to the running - // instance via application:openFile:, which Ghostty maps to a new tab/window - // in the correct directory without spawning a second process. - const env = await getShellEnv() - Bun.spawn(['open', '-a', 'Ghostty.app', worktreePath], { - stdout: 'ignore', - stderr: 'ignore', - env - }) + // Use AppleScript to switch to an existing Ghostty tab for this worktree, + // or open a new tab in the correct directory if none exists. + // This ensures proper tab naming and tab management like the opencode launcher. + const tabTitle = path.basename(worktreePath) + const script = ` +tell application "Ghostty" + set targetPath to "${worktreePath}" + set targetWindow to window 1 + repeat with t in every tab of targetWindow + set term to focused terminal of t + if working directory of term is targetPath then + select tab t + focus term + return + end if + end repeat + set cfg to new surface configuration + set initial working directory of cfg to targetPath + set newTab to new tab in targetWindow with configuration cfg + perform action "set_tab_title:${tabTitle}" on (focused terminal of newTab) +end tell +` + Bun.spawn(['/usr/bin/osascript', '-e', script], { stdout: 'ignore', stderr: 'ignore' }) } export async function launchOpencode(worktreePath: string): Promise { diff --git a/packages/treebeard/src/components/LaunchButtons.tsx b/packages/treebeard/src/components/LaunchButtons.tsx index 7837239..8ed9950 100644 --- a/packages/treebeard/src/components/LaunchButtons.tsx +++ b/packages/treebeard/src/components/LaunchButtons.tsx @@ -2,7 +2,6 @@ import { useEffect, useState } from 'react' import { ActionIcon, Group, Tooltip } from '@mantine/core' import { IconGhost } from '@tabler/icons-react' import { IdeIcon } from './IdeIcon' -import { OpencodeIcon } from './OpencodeIcon' import { IDE_REGISTRY } from '../shared/ide-registry' import { rpc } from '../rpc' import type { IdeId } from '../shared/types' @@ -14,11 +13,6 @@ interface LaunchButtonsProps { export function LaunchButtons({ worktreePath, defaultIde }: LaunchButtonsProps) { const ide = IDE_REGISTRY[defaultIde] - const [opencodePath, setOpencodePath] = useState(null) - - useEffect(() => { - rpc().request['system:opencodePath']({}).then(setOpencodePath).catch(() => setOpencodePath(null)) - }, []) const handleIde = async () => { await rpc().request['launch:ide']({ ideId: defaultIde, worktreePath }) @@ -28,10 +22,6 @@ export function LaunchButtons({ worktreePath, defaultIde }: LaunchButtonsProps) await rpc().request['launch:ghostty']({ worktreePath }) } - const handleOpencode = async () => { - await rpc().request['launch:opencode']({ worktreePath }) - } - return ( @@ -44,13 +34,6 @@ export function LaunchButtons({ worktreePath, defaultIde }: LaunchButtonsProps) - {opencodePath && ( - - - - - - )} ) } From c7de30a0871059e45d97259ba2c5c0b92efdb525 Mon Sep 17 00:00:00 2001 From: Martin Laporte Date: Wed, 8 Apr 2026 11:45:07 +0200 Subject: [PATCH 2/2] feat: enhance Ghostty tab naming to include project and worktree names - Updated tab naming logic to show "project-name / worktree-name" format - Extract project name from worktree path parent directory - Maintained existing tab switching and creation behavior - Updated test to verify new naming format --- packages/treebeard/src/bun/services/launcher.test.ts | 2 +- packages/treebeard/src/bun/services/launcher.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/treebeard/src/bun/services/launcher.test.ts b/packages/treebeard/src/bun/services/launcher.test.ts index f80271e..b3717f9 100644 --- a/packages/treebeard/src/bun/services/launcher.test.ts +++ b/packages/treebeard/src/bun/services/launcher.test.ts @@ -32,7 +32,7 @@ describe('launcher service', () => { it('launches ghostty with AppleScript and sets tab title', async () => { const spawn = setBunSpawnQueue([{ stdout: '' }]) - await launchGhostty('/repo/worktree') + await launchGhostty('/Users/user/projects/node-commons/this-is-the-worktree') expect(spawn).toHaveBeenCalledWith( ['/usr/bin/osascript', '-e', expect.stringContaining('tell application "Ghostty"')], diff --git a/packages/treebeard/src/bun/services/launcher.ts b/packages/treebeard/src/bun/services/launcher.ts index 7269680..f6f63cd 100644 --- a/packages/treebeard/src/bun/services/launcher.ts +++ b/packages/treebeard/src/bun/services/launcher.ts @@ -15,7 +15,10 @@ export async function launchGhostty(worktreePath: string): Promise { // Use AppleScript to switch to an existing Ghostty tab for this worktree, // or open a new tab in the correct directory if none exists. // This ensures proper tab naming and tab management like the opencode launcher. - const tabTitle = path.basename(worktreePath) + const pathParts = worktreePath.split('/') + const worktreeName = pathParts.pop() || '' + const projectName = pathParts.pop() || '' + const tabTitle = `${projectName} / ${worktreeName}` const script = ` tell application "Ghostty" set targetPath to "${worktreePath}"