detached 123b9fb

fix(palette): preserve batched tab invalidation

commit
123b9fb668b12fe9b2f04a3f1cd925d69ca852dc
author
brice · · 2026-07-14 11:36 UTC
parent
b16caf0
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +20 -1
M internal/usecase/daemon/palette.go +1 -1
@@ -125,7 +125,7 @@ func (d *Daemon) handlePaletteInput(ac *attachedClient, data []byte) {
125125 ac.overlays.palette.Backspace()
126126 changed = true
127127 },
128- tab: func() { changed = ac.overlays.palette.CompleteSelected() },
128+ tab: func() { changed = ac.overlays.palette.CompleteSelected() || changed },
129129 up: func() { ac.overlays.palette.Up(); changed = true },
130130 down: func() { ac.overlays.palette.Down(); changed = true },
131131 cancel: func() { cancel = true },
@@ -125,7 +125,7 @@ func (d *Daemon) handlePaletteInput(ac *attachedClient, data []byte) {
125 ac.overlays.palette.Backspace()125 ac.overlays.palette.Backspace()
126 changed = true126 changed = true
127 },127 },
128- tab: func() { changed = ac.overlays.palette.CompleteSelected() },128+ tab: func() { changed = ac.overlays.palette.CompleteSelected() || changed },
129 up: func() { ac.overlays.palette.Up(); changed = true },129 up: func() { ac.overlays.palette.Up(); changed = true },
130 down: func() { ac.overlays.palette.Down(); changed = true },130 down: func() { ac.overlays.palette.Down(); changed = true },
131 cancel: func() { cancel = true },131 cancel: func() { cancel = true },
M internal/usecase/daemon/palette_test.go +19 -0
@@ -586,6 +586,25 @@ func TestPaletteTabCompletesSelectedCommandWithoutForwardingToPTY(t *testing.T)
586586 }
587587 }
588588
589+func TestPaletteBatchedTabCompletionPreservesRepaintInvalidation(t *testing.T) {
590+ writes := make(chan []byte, 1)
591+ p, release := newBlockingPTYWithWrites(t, writes)
592+ defer release()
593+ d, sess, ac, sends := newManualSessionWithPTYs(t, p)
594+
595+ d.handleInput(sess, ac, []byte("\x1b "))
596+ awaitFrame(t, sends, ports.MsgOutput) // drain palette-open repaint
597+
598+ d.handleInput(sess, ac, []byte("NX\t\t"))
599+ ac.overlays.paletteMu.Lock()
600+ got := ac.overlays.palette.Query()
601+ ac.overlays.paletteMu.Unlock()
602+ require.Equal(t, "NXT", got)
603+ awaitFrame(t, sends, ports.MsgOutput)
604+ requireNoPaletteFrame(t, sends)
605+ requireNoPTYWrite(t, writes)
606+}
607+
589608 func requireNoPaletteFrame(t *testing.T, sends <-chan ports.Frame) {
590609 t.Helper()
591610 select {
@@ -586,6 +586,25 @@ func TestPaletteTabCompletesSelectedCommandWithoutForwardingToPTY(t *testing.T)
586 }586 }
587 }587 }
588 588
589+func TestPaletteBatchedTabCompletionPreservesRepaintInvalidation(t *testing.T) {
590+ writes := make(chan []byte, 1)
591+ p, release := newBlockingPTYWithWrites(t, writes)
592+ defer release()
593+ d, sess, ac, sends := newManualSessionWithPTYs(t, p)
594+
595+ d.handleInput(sess, ac, []byte("\x1b "))
596+ awaitFrame(t, sends, ports.MsgOutput) // drain palette-open repaint
597+
598+ d.handleInput(sess, ac, []byte("NX\t\t"))
599+ ac.overlays.paletteMu.Lock()
600+ got := ac.overlays.palette.Query()
601+ ac.overlays.paletteMu.Unlock()
602+ require.Equal(t, "NXT", got)
603+ awaitFrame(t, sends, ports.MsgOutput)
604+ requireNoPaletteFrame(t, sends)
605+ requireNoPTYWrite(t, writes)
606+}
607+
589 func requireNoPaletteFrame(t *testing.T, sends <-chan ports.Frame) {608 func requireNoPaletteFrame(t *testing.T, sends <-chan ports.Frame) {
590 t.Helper()609 t.Helper()
591 select {610 select {

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