From 1903d153d487e86c8e80e7dcee6e54d43c5b0672 Mon Sep 17 00:00:00 2001
From: Jun Peng <61768526+minorole@users.noreply.github.com>
Date: Mon, 16 Feb 2026 10:04:54 -0500
Subject: [PATCH 1/2] fix: explicitly cd to project directory in every
non-first pane
Panes created after pane 1 relied on Ghostty's directory inheritance,
which fails when pane 1 runs a foreground process (e.g. claude, npm run
dev). Now every non-first pane gets an explicit cd before its command.
---
scripts/layout-dynamic.applescript | 16 ++++++++++++++++
scripts/layout-hybrid.applescript | 16 ++++++++++++++++
2 files changed, 32 insertions(+)
diff --git a/scripts/layout-dynamic.applescript b/scripts/layout-dynamic.applescript
index 5dd2b0b..4f686c8 100644
--- a/scripts/layout-dynamic.applescript
+++ b/scripts/layout-dynamic.applescript
@@ -90,6 +90,16 @@ on run argv
repeat with rowIdx from 1 to numRows
set panesInRow to item rowIdx of rowCounts
+ -- Ensure non-first panes start in project directory
+ -- (pane 1 already got cd at start; row 2+ anchors
+ -- may not inherit if pane 1 is running a foreground process)
+ if spatialIdx > 1 then
+ set the clipboard to "cd " & (quoted form of projectDir) & " && clear"
+ keystroke "v" using {command down}
+ key code 36 -- Enter
+ delay 0.3
+ end if
+
-- Type command for this row's anchor (we're already here)
if spatialIdx <= (count of commands) then
set cmd to item spatialIdx of commands
@@ -108,6 +118,12 @@ on run argv
keystroke "d" using {command down} -- Split right
delay 0.6 -- Increased for split stability
+ -- Ensure split pane starts in project directory
+ set the clipboard to "cd " & (quoted form of projectDir) & " && clear"
+ keystroke "v" using {command down}
+ key code 36 -- Enter
+ delay 0.3
+
-- Now in the new split pane, type its command
if spatialIdx <= (count of commands) then
set cmd to item spatialIdx of commands
diff --git a/scripts/layout-hybrid.applescript b/scripts/layout-hybrid.applescript
index 5f68cbc..e4aca67 100644
--- a/scripts/layout-hybrid.applescript
+++ b/scripts/layout-hybrid.applescript
@@ -105,6 +105,16 @@ on run argv
repeat with rowIdx from 1 to numRows
set panesInRow to item rowIdx of rowCounts
+ -- Ensure non-first panes start in project directory
+ -- (pane 1 already got cd at tab start; row 2+ anchors
+ -- may not inherit if pane 1 is running a foreground process)
+ if spatialIdx > 1 then
+ set the clipboard to "cd " & (quoted form of projectDir) & " && clear"
+ keystroke "v" using {command down}
+ key code 36 -- Enter
+ delay 0.3
+ end if
+
-- Type command for this row's anchor (we're already here)
set cmdIndex to tabCmdBase + spatialIdx
if cmdIndex <= (count of commands) then
@@ -124,6 +134,12 @@ on run argv
keystroke "d" using {command down} -- Split right
delay 0.6 -- Increased for split stability
+ -- Ensure split pane starts in project directory
+ set the clipboard to "cd " & (quoted form of projectDir) & " && clear"
+ keystroke "v" using {command down}
+ key code 36 -- Enter
+ delay 0.3
+
-- Now in the new split pane, type its command
set cmdIndex to tabCmdBase + spatialIdx
if cmdIndex <= (count of commands) then
From 3716f3af6d181d1a3affbf2be7541dd76b85a6fa Mon Sep 17 00:00:00 2001
From: Jun Peng <61768526+minorole@users.noreply.github.com>
Date: Mon, 16 Feb 2026 11:50:27 -0500
Subject: [PATCH 2/2] fix: move pane directory handling from AppleScript to zsh
orchestration layer
Prepend cd
to every command in zsh before passing to AppleScript,
making each pane command self-contained. Removes 6 fragile cd-injection
blocks from AppleScript that depended on clipboard timing delays.
---
lib/layouts.zsh | 40 +++++++++++++++++++++++++++---
scripts/layout-dynamic.applescript | 22 ----------------
scripts/layout-hybrid.applescript | 22 ----------------
3 files changed, 37 insertions(+), 47 deletions(-)
diff --git a/lib/layouts.zsh b/lib/layouts.zsh
index 8b7de07..538c513 100644
--- a/lib/layouts.zsh
+++ b/lib/layouts.zsh
@@ -53,6 +53,24 @@ compute_total_panes() {
echo $total
}
+# Prepend cd to project directory for every pane command
+# Empty commands become "cd "
+# Non-empty commands become "cd && "
+prepend_cd_to_commands() {
+ local project_dir=$1
+ shift
+ local -a cmds=("$@")
+ local -a result=()
+ for cmd in "${cmds[@]}"; do
+ if [[ -z "$cmd" ]]; then
+ result+=("cd ${(q)project_dir}")
+ else
+ result+=("cd ${(q)project_dir} && ${cmd}")
+ fi
+ done
+ PREPARED_COMMANDS=("${result[@]}")
+}
+
# Validate layout spec
# Returns 0 if valid, 1 if invalid (with error message to stderr)
validate_layout() {
@@ -149,7 +167,16 @@ layout_hybrid() {
return 1
fi
- run_layout "layout-hybrid" "${label}" "${reuse_window}" "${project_dir}" "${layout_spec}" "${tabs_count}" "${cmds[@]}"
+ # Pad commands to fill all pane slots across all tabs
+ local panes_per_tab
+ panes_per_tab=$(compute_total_panes "$layout_spec")
+ local total_slots=$((panes_per_tab * tabs_count))
+ while (( ${#cmds[@]} < total_slots )); do
+ cmds+=("")
+ done
+
+ prepend_cd_to_commands "${project_dir}" "${cmds[@]}"
+ run_layout "layout-hybrid" "${label}" "${reuse_window}" "${project_dir}" "${layout_spec}" "${tabs_count}" "${PREPARED_COMMANDS[@]}"
}
# Check for deprecated layout: tabs syntax
@@ -196,8 +223,15 @@ layout_dynamic() {
return 1
fi
- # Pass commands directly in spatial order - AppleScript handles them inline
- run_layout "layout-dynamic" "${label}" "${reuse_window}" "${project_dir}" "${layout_spec}" "${spatial_cmds[@]}"
+ # Pad commands to fill all pane slots
+ local total_panes
+ total_panes=$(compute_total_panes "$layout_spec")
+ while (( ${#spatial_cmds[@]} < total_panes )); do
+ spatial_cmds+=("")
+ done
+
+ prepend_cd_to_commands "${project_dir}" "${spatial_cmds[@]}"
+ run_layout "layout-dynamic" "${label}" "${reuse_window}" "${project_dir}" "${layout_spec}" "${PREPARED_COMMANDS[@]}"
}
# Run a layout script with progress dots
diff --git a/scripts/layout-dynamic.applescript b/scripts/layout-dynamic.applescript
index 4f686c8..eec334a 100644
--- a/scripts/layout-dynamic.applescript
+++ b/scripts/layout-dynamic.applescript
@@ -60,12 +60,6 @@ on run argv
set frontmost to true
delay 0.5
- -- cd to project directory (this is pane 1, spatial position 0)
- set the clipboard to "cd " & (quoted form of projectDir) & " && clear"
- keystroke "v" using {command down}
- key code 36 -- Enter
- delay 0.5
-
-- PHASE 1: Create row spine (n-1 vertical splits)
-- This creates one anchor pane per row
if numRows > 1 then
@@ -90,16 +84,6 @@ on run argv
repeat with rowIdx from 1 to numRows
set panesInRow to item rowIdx of rowCounts
- -- Ensure non-first panes start in project directory
- -- (pane 1 already got cd at start; row 2+ anchors
- -- may not inherit if pane 1 is running a foreground process)
- if spatialIdx > 1 then
- set the clipboard to "cd " & (quoted form of projectDir) & " && clear"
- keystroke "v" using {command down}
- key code 36 -- Enter
- delay 0.3
- end if
-
-- Type command for this row's anchor (we're already here)
if spatialIdx <= (count of commands) then
set cmd to item spatialIdx of commands
@@ -118,12 +102,6 @@ on run argv
keystroke "d" using {command down} -- Split right
delay 0.6 -- Increased for split stability
- -- Ensure split pane starts in project directory
- set the clipboard to "cd " & (quoted form of projectDir) & " && clear"
- keystroke "v" using {command down}
- key code 36 -- Enter
- delay 0.3
-
-- Now in the new split pane, type its command
if spatialIdx <= (count of commands) then
set cmd to item spatialIdx of commands
diff --git a/scripts/layout-hybrid.applescript b/scripts/layout-hybrid.applescript
index e4aca67..05b66b4 100644
--- a/scripts/layout-hybrid.applescript
+++ b/scripts/layout-hybrid.applescript
@@ -75,12 +75,6 @@ on run argv
-- cmdIndex = (tabIdx - 1) * panesPerTab + spatialIdx
set tabCmdBase to (tabIdx - 1) * panesPerTab
- -- cd to project directory (this is pane 1 of current tab)
- set the clipboard to "cd " & (quoted form of projectDir) & " && clear"
- keystroke "v" using {command down}
- key code 36 -- Enter
- delay 0.5
-
-- === PHASE 1: Create row spine (n-1 vertical splits) ===
-- This creates one anchor pane per row
if numRows > 1 then
@@ -105,16 +99,6 @@ on run argv
repeat with rowIdx from 1 to numRows
set panesInRow to item rowIdx of rowCounts
- -- Ensure non-first panes start in project directory
- -- (pane 1 already got cd at tab start; row 2+ anchors
- -- may not inherit if pane 1 is running a foreground process)
- if spatialIdx > 1 then
- set the clipboard to "cd " & (quoted form of projectDir) & " && clear"
- keystroke "v" using {command down}
- key code 36 -- Enter
- delay 0.3
- end if
-
-- Type command for this row's anchor (we're already here)
set cmdIndex to tabCmdBase + spatialIdx
if cmdIndex <= (count of commands) then
@@ -134,12 +118,6 @@ on run argv
keystroke "d" using {command down} -- Split right
delay 0.6 -- Increased for split stability
- -- Ensure split pane starts in project directory
- set the clipboard to "cd " & (quoted form of projectDir) & " && clear"
- keystroke "v" using {command down}
- key code 36 -- Enter
- delay 0.3
-
-- Now in the new split pane, type its command
set cmdIndex to tabCmdBase + spatialIdx
if cmdIndex <= (count of commands) then