Skip to content

Commit fad239e

Browse files
Name the iTerm2 overlay pane so the tab keeps its label (#309)
* Name the iTerm2 overlay pane so the tab keeps its label A tab's label and the OS window title both come from the tab's active session, and the overlay pane is created without a name of its own. Where the parent session's name interpolates variables, the split inherits the name setting but not the variables behind it, so both surfaces read empty for as long as the review is up: measured on iTerm2 3.6.11, the overlay's name evaluates to `<b></b>` while the parent reads `<b>PARENT</b>`. Every other backend already labels its overlay from OVERLAY_TITLE — tmux via -T, zellij --name, herdr --label, kitty --title, the emacs frame name. This passes the same title into the iTerm2 branch and sets it on the session the split returns, which also reverts on its own when the pane closes, the name being the session's. The title goes after the existing argv items on purpose: the test stub's osascript arm dispatches on argument count and on `$3` being executable, so an earlier insertion would shift the launch script out from under it. Fixes #307 * Name the plan-review iTerm2 pane too The planning hook's launcher splits the same way and computes its own OVERLAY_TITLE, so plan reviews kept the unlabelled tab this branch fixes for diff reviews. Also trims the comment beside the fix: the pane falls back to the profile's default title rather than rendering empty, and the OS window title is not the session name, so only the tab label is at stake. * Cover the iTerm2 overlay title with an args-capture test The other backends pass OVERLAY_TITLE as a CLI flag the exit-code matrix already runs; iTerm2 hands it to AppleScript through argv, which no test read, so dropping the argument again would go unnoticed. FAKE_OSASCRIPT_ARGS_FILE records each osascript invocation in the fake backend, mirroring FAKE_AGTERM_ARGS_FILE, and the new test asserts the split call ends with the title for all three launchers.
1 parent 92c716f commit fad239e

5 files changed

Lines changed: 92 additions & 3 deletions

File tree

.claude-plugin/skills/revdiff/scripts/launch-revdiff.sh

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -518,12 +518,13 @@ LAUNCHER
518518
ITERM_UUID="${ITERM_SESSION_ID##*:}"
519519

520520
# find target session by UUID, auto-detect split direction, capture new session id
521-
ITERM_NEW_SESSION=$(osascript - "$ITERM_UUID" "$LAUNCH_SCRIPT" "$CWD" "$SENTINEL" <<'APPLESCRIPT' 2>&1
521+
ITERM_NEW_SESSION=$(osascript - "$ITERM_UUID" "$LAUNCH_SCRIPT" "$CWD" "$SENTINEL" "$OVERLAY_TITLE" <<'APPLESCRIPT' 2>&1
522522
on run argv
523523
set targetId to item 1 of argv
524524
set launchScript to item 2 of argv
525525
set cwd to item 3 of argv
526526
set sentinel to item 4 of argv
527+
set overlayTitle to item 5 of argv
527528
set cmd to quoted form of launchScript & " " & quoted form of cwd & " " & quoted form of sentinel
528529
tell application id "com.googlecode.iterm2"
529530
repeat with w in windows
@@ -539,6 +540,11 @@ on run argv
539540
set newSession to split horizontally with same profile command cmd
540541
end if
541542
end tell
543+
-- the tab label comes from its active session's name,
544+
-- and the split gets none of its own: it copies the
545+
-- parent's profile but not the session variables that
546+
-- profile's name may interpolate
547+
set name of newSession to overlayTitle
542548
return id of newSession
543549
end if
544550
end repeat

app/plugin_exit_code_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,74 @@ func TestShellLaunchersPreserveAnnotationExitCode(t *testing.T) {
177177
}
178178
}
179179

180+
// every other backend labels its overlay through a flag the exit-code matrix
181+
// already runs (tmux -T, kitty --title); iTerm2 names the session it splits from
182+
// an AppleScript argv, which nothing else in the suite reads
183+
func TestIterm2OverlayNamesSession(t *testing.T) {
184+
if runtime.GOOS == "windows" {
185+
t.Skip("shell launchers are not used on windows")
186+
}
187+
188+
root := testRepoRoot(t)
189+
planFile := filepath.Join(t.TempDir(), "plan.md")
190+
writeTestFile(t, planFile, "# Plan\n")
191+
diffTitle := "rd: " + filepath.Base(root) + " [HEAD~1]"
192+
193+
launchers := []struct {
194+
name string
195+
path string
196+
args []string
197+
title string
198+
}{
199+
{
200+
name: "claude",
201+
path: ".claude-plugin/skills/revdiff/scripts/launch-revdiff.sh",
202+
args: []string{"HEAD~1"},
203+
title: diffTitle,
204+
},
205+
{
206+
name: "codex",
207+
path: "plugins/codex/skills/revdiff/scripts/launch-revdiff.sh",
208+
args: []string{"HEAD~1"},
209+
title: diffTitle,
210+
},
211+
{
212+
name: "plan review",
213+
path: "plugins/revdiff-planning/scripts/launch-plan-review.sh",
214+
args: []string{planFile},
215+
title: "plan: plan.md",
216+
},
217+
}
218+
219+
output := "## file.go:1 (+)\ncomment\n"
220+
backend := launcherBackend{name: "iterm2", command: "osascript", env: map[string]string{"ITERM_SESSION_ID": "w0t0p0:ABC"}}
221+
for _, launcher := range launchers {
222+
t.Run(launcher.name, func(t *testing.T) {
223+
env := fakeLauncherEnv(t, launcherRun{backend: backend, code: exitCodeAnnotations, output: output})
224+
argsFile := filepath.Join(env["TMPDIR"], "osascript-args")
225+
env["FAKE_OSASCRIPT_ARGS_FILE"] = argsFile
226+
227+
res := runTestCmd(t, cmdReq{
228+
dir: root,
229+
name: "bash",
230+
args: append([]string{filepath.Join(root, launcher.path)}, launcher.args...),
231+
env: env,
232+
})
233+
assert.Equal(t, exitCodeAnnotations, res.code)
234+
assert.Equal(t, output, res.stdout)
235+
236+
raw, err := os.ReadFile(argsFile) //nolint:gosec // path is a test-owned temp file
237+
require.NoError(t, err)
238+
calls := strings.Split(strings.TrimSpace(string(raw)), "\n")
239+
require.NotEmpty(t, calls)
240+
// the title is the last argv item so the stub's positional dispatch
241+
// on the launch script keeps matching
242+
assert.True(t, strings.HasSuffix(calls[0], " "+launcher.title),
243+
"split osascript argv should end with the overlay title, got %q", calls[0])
244+
})
245+
}
246+
}
247+
180248
func TestAgtermPaneOverlayOptIn(t *testing.T) {
181249
if runtime.GOOS == "windows" {
182250
t.Skip("shell launchers are not used on windows")

app/testdata/plugin-exit-code/fake-overlay-backend.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ case "$cmd_name" in
6767
esac
6868
;;
6969
osascript)
70+
if [ -n "${FAKE_OSASCRIPT_ARGS_FILE:-}" ]; then
71+
printf '%s\n' "$*" >> "$FAKE_OSASCRIPT_ARGS_FILE"
72+
fi
7073
if [ "$#" -ge 5 ] && [ -x "${3:-}" ]; then
7174
"$3" "$4" "$5"
7275
echo "session:1"

plugins/codex/skills/revdiff/scripts/launch-revdiff.sh

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -519,12 +519,13 @@ LAUNCHER
519519
ITERM_UUID="${ITERM_SESSION_ID##*:}"
520520

521521
# find target session by UUID, auto-detect split direction, capture new session id
522-
ITERM_NEW_SESSION=$(osascript - "$ITERM_UUID" "$LAUNCH_SCRIPT" "$CWD" "$SENTINEL" <<'APPLESCRIPT' 2>&1
522+
ITERM_NEW_SESSION=$(osascript - "$ITERM_UUID" "$LAUNCH_SCRIPT" "$CWD" "$SENTINEL" "$OVERLAY_TITLE" <<'APPLESCRIPT' 2>&1
523523
on run argv
524524
set targetId to item 1 of argv
525525
set launchScript to item 2 of argv
526526
set cwd to item 3 of argv
527527
set sentinel to item 4 of argv
528+
set overlayTitle to item 5 of argv
528529
set cmd to quoted form of launchScript & " " & quoted form of cwd & " " & quoted form of sentinel
529530
tell application id "com.googlecode.iterm2"
530531
repeat with w in windows
@@ -540,6 +541,11 @@ on run argv
540541
set newSession to split horizontally with same profile command cmd
541542
end if
542543
end tell
544+
-- the tab label comes from its active session's name,
545+
-- and the split gets none of its own: it copies the
546+
-- parent's profile but not the session variables that
547+
-- profile's name may interpolate
548+
set name of newSession to overlayTitle
543549
return id of newSession
544550
end if
545551
end repeat

plugins/revdiff-planning/scripts/launch-plan-review.sh

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,11 +397,12 @@ LAUNCHER
397397

398398
ITERM_UUID="${ITERM_SESSION_ID##*:}"
399399

400-
ITERM_NEW_SESSION=$(osascript - "$ITERM_UUID" "$LAUNCH_SCRIPT" "$SENTINEL" <<'APPLESCRIPT' 2>&1
400+
ITERM_NEW_SESSION=$(osascript - "$ITERM_UUID" "$LAUNCH_SCRIPT" "$SENTINEL" "$OVERLAY_TITLE" <<'APPLESCRIPT' 2>&1
401401
on run argv
402402
set targetId to item 1 of argv
403403
set launchScript to item 2 of argv
404404
set sentinel to item 3 of argv
405+
set overlayTitle to item 4 of argv
405406
set cmd to quoted form of launchScript & " " & quoted form of sentinel
406407
tell application id "com.googlecode.iterm2"
407408
repeat with w in windows
@@ -417,6 +418,11 @@ on run argv
417418
set newSession to split horizontally with same profile command cmd
418419
end if
419420
end tell
421+
-- the tab label comes from its active session's name,
422+
-- and the split gets none of its own: it copies the
423+
-- parent's profile but not the session variables that
424+
-- profile's name may interpolate
425+
set name of newSession to overlayTitle
420426
return id of newSession
421427
end if
422428
end repeat

0 commit comments

Comments
 (0)