detached 7dfd596

fix(graphics): stop anonymous Kitty reply leaks

commit
7dfd596eca8a85550584d09e1194324d47015fa7
author
brice · · 2026-08-27 06:13 UTC
parent
a539e25
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +26 -3
M go.mod +1 -1
@@ -3,7 +3,7 @@ module github.com/bnema/vev
33 go 1.26
44
55 require (
6- github.com/bnema/vev-vt v0.4.1
6+ github.com/bnema/vev-vt v0.4.2-0.20260827060951-f53f75f19d7f
77 github.com/stretchr/testify v1.11.1
88 )
99
@@ -3,7 +3,7 @@ module github.com/bnema/vev
3 go 1.263 go 1.26
4 4
5 require (5 require (
6- github.com/bnema/vev-vt v0.4.16+ github.com/bnema/vev-vt v0.4.2-0.20260827060951-f53f75f19d7f
7 github.com/stretchr/testify v1.11.17 github.com/stretchr/testify v1.11.1
8 )8 )
9 9
M go.sum +2 -2
@@ -1,5 +1,5 @@
1-github.com/bnema/vev-vt v0.4.1 h1:KbYSFY9EvrjJ1VTahWnuxTe5A9Aeml96FqBdkgatjKo=
2-github.com/bnema/vev-vt v0.4.1/go.mod h1:QdV0QI7vUnlAF+7+CiPhThHDpDSkxMEZmCYl/Eo+xCA=
1+github.com/bnema/vev-vt v0.4.2-0.20260827060951-f53f75f19d7f h1:1lMAFApMUWklMwPm1RJS68RisXLeJYwVKXOLc4vhAzY=
2+github.com/bnema/vev-vt v0.4.2-0.20260827060951-f53f75f19d7f/go.mod h1:QdV0QI7vUnlAF+7+CiPhThHDpDSkxMEZmCYl/Eo+xCA=
33 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
44 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
55 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -1,5 +1,5 @@
1-github.com/bnema/vev-vt v0.4.1 h1:KbYSFY9EvrjJ1VTahWnuxTe5A9Aeml96FqBdkgatjKo=1+github.com/bnema/vev-vt v0.4.2-0.20260827060951-f53f75f19d7f h1:1lMAFApMUWklMwPm1RJS68RisXLeJYwVKXOLc4vhAzY=
2-github.com/bnema/vev-vt v0.4.1/go.mod h1:QdV0QI7vUnlAF+7+CiPhThHDpDSkxMEZmCYl/Eo+xCA=2+github.com/bnema/vev-vt v0.4.2-0.20260827060951-f53f75f19d7f/go.mod h1:QdV0QI7vUnlAF+7+CiPhThHDpDSkxMEZmCYl/Eo+xCA=
3 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=3 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
4 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=4 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=5 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
M internal/usecase/daemon/render_test.go +23 -0
@@ -991,6 +991,29 @@ func TestSendErrorKeepsEphemeralHeadless(t *testing.T) {
991991 d.waitNotifies()
992992 }
993993
994+func TestPTYKittyAnonymousGraphicsDoesNotWriteResponseToPTY(t *testing.T) {
995+ d := newTestDaemon(t, nil, stubClock{})
996+ p := portsmocks.NewMockPTY(t)
997+ chunks := [][]byte{[]byte("\x1b_Ga=T,f=32,s=1,v=1,C=1;AQIDBA\x1b\\")}
998+ p.EXPECT().Read(mock.Anything).RunAndReturn(func(buf []byte) (int, error) {
999+ if len(chunks) == 0 {
1000+ return 0, io.EOF
1001+ }
1002+ n := copy(buf, chunks[0])
1003+ chunks = chunks[1:]
1004+ return n, nil
1005+ })
1006+ p.EXPECT().Close().Return(nil).Maybe()
1007+
1008+ sctx, cancel := context.WithCancel(context.Background())
1009+ win := newTestTabWithContext(p, sctx, cancel)
1010+ sess := &session{sessionCore: sessionCore{id: "anonymous", name: "anonymous"}, tabs: []*tab{win}, ctx: sctx, cancel: cancel}
1011+ d.sessions[sess.id] = sess
1012+ d.sessWg.Add(1)
1013+
1014+ d.ptyReader(sess, win, win.focusedPane())
1015+}
1016+
9941017 func TestPTYKittyIcatDetectionGetsResponsesWrittenBackToPTY(t *testing.T) {
9951018 d := newTestDaemon(t, nil, stubClock{})
9961019 p := portsmocks.NewMockPTY(t)
@@ -991,6 +991,29 @@ func TestSendErrorKeepsEphemeralHeadless(t *testing.T) {
991 d.waitNotifies()991 d.waitNotifies()
992 }992 }
993 993
994+func TestPTYKittyAnonymousGraphicsDoesNotWriteResponseToPTY(t *testing.T) {
995+ d := newTestDaemon(t, nil, stubClock{})
996+ p := portsmocks.NewMockPTY(t)
997+ chunks := [][]byte{[]byte("\x1b_Ga=T,f=32,s=1,v=1,C=1;AQIDBA\x1b\\")}
998+ p.EXPECT().Read(mock.Anything).RunAndReturn(func(buf []byte) (int, error) {
999+ if len(chunks) == 0 {
1000+ return 0, io.EOF
1001+ }
1002+ n := copy(buf, chunks[0])
1003+ chunks = chunks[1:]
1004+ return n, nil
1005+ })
1006+ p.EXPECT().Close().Return(nil).Maybe()
1007+
1008+ sctx, cancel := context.WithCancel(context.Background())
1009+ win := newTestTabWithContext(p, sctx, cancel)
1010+ sess := &session{sessionCore: sessionCore{id: "anonymous", name: "anonymous"}, tabs: []*tab{win}, ctx: sctx, cancel: cancel}
1011+ d.sessions[sess.id] = sess
1012+ d.sessWg.Add(1)
1013+
1014+ d.ptyReader(sess, win, win.focusedPane())
1015+}
1016+
994 func TestPTYKittyIcatDetectionGetsResponsesWrittenBackToPTY(t *testing.T) {1017 func TestPTYKittyIcatDetectionGetsResponsesWrittenBackToPTY(t *testing.T) {
995 d := newTestDaemon(t, nil, stubClock{})1018 d := newTestDaemon(t, nil, stubClock{})
996 p := portsmocks.NewMockPTY(t)1019 p := portsmocks.NewMockPTY(t)

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 12

Tags 11

↑↓ moveenter openesc close