From ab50d6be900f291dbc5506799070ecc1f27c650b Mon Sep 17 00:00:00 2001 From: Taiki Katayama / Ankh Date: Wed, 25 Mar 2026 17:50:21 +0900 Subject: [PATCH 1/2] fix: add placement ID to Kitty graphics commands for cross-terminal compatibility Without a placement ID (p=), each Display() call creates a new placement instead of replacing the previous one. WezTerm handles this gracefully by mapping placements to cells, but spec-faithful terminals like Ghostty accumulate stale placements, causing zoom/pan to appear unresponsive. Adding p=1 ensures each frame's placement replaces the previous one, producing consistent behavior across all Kitty Graphics Protocol terminals. Co-Authored-By: Claude Opus 4.6 --- internal/adapter/renderer/kitty_renderer.go | 4 ++-- internal/adapter/renderer/kitty_renderer_test.go | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/internal/adapter/renderer/kitty_renderer.go b/internal/adapter/renderer/kitty_renderer.go index 389c924..54529c0 100644 --- a/internal/adapter/renderer/kitty_renderer.go +++ b/internal/adapter/renderer/kitty_renderer.go @@ -96,7 +96,7 @@ func (r *KittyRenderer) Display(vp *domain.Viewport) (string, error) { // Clear previous display and show new frame // Move cursor to top-left, clear screen area, then display output := "\x1b[H" // move cursor to top-left - output += fmt.Sprintf("\x1b_Ga=p,i=%d,x=%d,y=%d,w=%d,h=%d,c=%d,r=%d,q=2\x1b\\", + output += fmt.Sprintf("\x1b_Ga=p,i=%d,p=1,x=%d,y=%d,w=%d,h=%d,c=%d,r=%d,q=2\x1b\\", r.imageID, srcX, srcY, srcW, srcH, displayCols, displayRows) return output, nil @@ -217,7 +217,7 @@ func (r *KittyRenderer) DisplayMinimap(vp *domain.Viewport, cols, rows int, bord } // Build placement command (always needed since main image re-render may overwrite) - placeCmd := fmt.Sprintf("\x1b[%d;%dH\x1b_Ga=p,i=%d,c=%d,r=%d,z=1,q=2\x1b\\", + placeCmd := fmt.Sprintf("\x1b[%d;%dH\x1b_Ga=p,i=%d,p=1,c=%d,r=%d,z=1,q=2\x1b\\", startRow, startCol, r.minimapID, cols, rows) // Skip re-upload if indicator rectangle and border color haven't changed. diff --git a/internal/adapter/renderer/kitty_renderer_test.go b/internal/adapter/renderer/kitty_renderer_test.go index ebb41e8..76feb4b 100644 --- a/internal/adapter/renderer/kitty_renderer_test.go +++ b/internal/adapter/renderer/kitty_renderer_test.go @@ -35,6 +35,9 @@ func TestKittyRenderer_Display(t *testing.T) { if !strings.Contains(output, "a=p") { t.Error("output should contain action=place") } + if !strings.Contains(output, "p=1") { + t.Error("output should contain placement ID for consistent cross-terminal behavior") + } if !strings.Contains(output, "w=800") { t.Error("output should contain source width") } @@ -152,6 +155,9 @@ func TestKittyRenderer_DisplayMinimap(t *testing.T) { if !strings.Contains(output, "a=p") { t.Error("output should contain action=place") } + if !strings.Contains(output, "p=1") { + t.Error("output should contain placement ID for consistent cross-terminal behavior") + } // Uses raw RGBA format if !strings.Contains(output, "f=32") { t.Error("output should use raw RGBA format (f=32)") From 9b5d2ee06b3f65b8f3d7176d88c789abf4e4e305 Mon Sep 17 00:00:00 2001 From: Taiki Katayama / Ankh Date: Wed, 25 Mar 2026 17:58:35 +0900 Subject: [PATCH 2/2] test: scope placement ID assertion to command portion in DisplayMinimap test The previous strings.Contains(output, "p=1") check could false-positive against base64-encoded RGBA payload data. Extract the placement command (a=p...ESC\) and verify p=1 within that range only. Co-Authored-By: Claude Opus 4.6 --- internal/adapter/renderer/kitty_renderer_test.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/internal/adapter/renderer/kitty_renderer_test.go b/internal/adapter/renderer/kitty_renderer_test.go index 76feb4b..24bf2c4 100644 --- a/internal/adapter/renderer/kitty_renderer_test.go +++ b/internal/adapter/renderer/kitty_renderer_test.go @@ -155,8 +155,18 @@ func TestKittyRenderer_DisplayMinimap(t *testing.T) { if !strings.Contains(output, "a=p") { t.Error("output should contain action=place") } - if !strings.Contains(output, "p=1") { - t.Error("output should contain placement ID for consistent cross-terminal behavior") + // Verify p=1 within the placement command only (not in base64 payload) + aPos := strings.Index(output, "a=p") + if aPos == -1 { + t.Fatal("output should contain action=place (a=p) command") + } + termPosRel := strings.Index(output[aPos:], "\x1b\\") + if termPosRel == -1 { + t.Fatalf("output should contain kitty graphics terminator after placement command: %q", output) + } + placementCmd := output[aPos : aPos+termPosRel] + if !strings.Contains(placementCmd, "p=1") { + t.Errorf("placement command should contain placement ID p=1 for consistent cross-terminal behavior, got: %q", placementCmd) } // Uses raw RGBA format if !strings.Contains(output, "f=32") {