detached d5c777e

fix(ui): preserve palette backdrop in copy mode

commit
d5c777e6c4c9c2418d1f72fc3bce054170876105
author
brice · · 2026-07-10 05:30 UTC
parent
ed1dd0a
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +33 -3
M internal/usecase/daemon/render.go +3 -3
@@ -343,13 +343,13 @@ func (d *Daemon) paint(sess *session, ac *attachedClient, reset bool) {
343343 } else {
344344 frame, damage = composeClientFrameWithLayoutCachedConsumeDamage(bars, tb, reset, layoutSnap, &ac.bars, &ac.composed)
345345 }
346+ if overlays.copyActive {
347+ frame, damage = composeCopyClientFrame(overlays.copyMode, tb, bars)
348+ }
346349 if overlays.paletteActive {
347350 contentArea := domain.Rect{Y: 1, Width: frame.Width, Height: max(0, frame.Height-2)}
348351 (overlayBackdrop{DimPaneContents: true}).apply(frame, contentArea, layoutSnap, bars.theme)
349352 }
350- if overlays.copyActive {
351- frame, damage = composeCopyClientFrame(overlays.copyMode, tb, bars)
352- }
353353 if overlays.copySearchModel != nil {
354354 frame, damage = composeCopySearchClientFrame(overlays.copySearchModel, frame, styles)
355355 }
@@ -343,13 +343,13 @@ func (d *Daemon) paint(sess *session, ac *attachedClient, reset bool) {
343 } else {343 } else {
344 frame, damage = composeClientFrameWithLayoutCachedConsumeDamage(bars, tb, reset, layoutSnap, &ac.bars, &ac.composed)344 frame, damage = composeClientFrameWithLayoutCachedConsumeDamage(bars, tb, reset, layoutSnap, &ac.bars, &ac.composed)
345 }345 }
346+ if overlays.copyActive {
347+ frame, damage = composeCopyClientFrame(overlays.copyMode, tb, bars)
348+ }
346 if overlays.paletteActive {349 if overlays.paletteActive {
347 contentArea := domain.Rect{Y: 1, Width: frame.Width, Height: max(0, frame.Height-2)}350 contentArea := domain.Rect{Y: 1, Width: frame.Width, Height: max(0, frame.Height-2)}
348 (overlayBackdrop{DimPaneContents: true}).apply(frame, contentArea, layoutSnap, bars.theme)351 (overlayBackdrop{DimPaneContents: true}).apply(frame, contentArea, layoutSnap, bars.theme)
349 }352 }
350- if overlays.copyActive {
351- frame, damage = composeCopyClientFrame(overlays.copyMode, tb, bars)
352- }
353 if overlays.copySearchModel != nil {353 if overlays.copySearchModel != nil {
354 frame, damage = composeCopySearchClientFrame(overlays.copySearchModel, frame, styles)354 frame, damage = composeCopySearchClientFrame(overlays.copySearchModel, frame, styles)
355 }355 }
M internal/usecase/daemon/render_test.go +30 -0
@@ -25,6 +25,36 @@ import (
2525 "github.com/bnema/vev/pkg/vt"
2626 )
2727
28+func TestPaletteBackdropDimsSimultaneousCopyMode(t *testing.T) {
29+ p, release := newBlockingPTY(t)
30+ defer release()
31+ d, sess, ac, sends := newManualSessionWithPTYs(t, p)
32+ theme := backdropTheme()
33+ ac.setTheme(theme)
34+ client := vt.NewScreen(80, 25)
35+ pane := sess.tabs[0].focusedPane()
36+ pane.screen.Write([]byte("\x1b[38;2;180;90;30mX"))
37+
38+ d.enterCopyMode(sess, ac)
39+ mustApplyOutput(t, client, awaitFrame(t, sends, ports.MsgOutput))
40+ undimmed := client.Frame.At(0, 1)
41+ require.Equal(t, 'X', undimmed.Rune, "fixture must address copy-mode pane content")
42+ copyBar := append([]renderer.Cell(nil), client.Frame.Row(client.Frame.Height-1)...)
43+ require.Contains(t, rowText(copyBar), "[SCROLL]", "fixture must capture the copy status bar")
44+
45+ d.enterPalette(sess, ac)
46+ mustApplyOutput(t, client, awaitFrame(t, sends, ports.MsgOutput))
47+ dimmed := client.Frame.At(0, 1)
48+ require.Equal(t, undimmed.Rune, dimmed.Rune, "palette backdrop must preserve copy content")
49+ require.Equal(t, themeui.DimStyle(undimmed.Style, theme), dimmed.Style, "palette backdrop must dim the composed copy frame")
50+ require.Equal(t, copyBar, client.Frame.Row(client.Frame.Height-1), "copy status bar must remain crisp")
51+ paletteVisible := false
52+ for y := range client.Frame.Height {
53+ paletteVisible = paletteVisible || strings.Contains(rowText(client.Frame.Row(y)), "Commands")
54+ }
55+ require.True(t, paletteVisible, "palette must remain composed above copy mode")
56+}
57+
2858 func TestPaletteBackdropKeepsSimultaneousPickerCrisp(t *testing.T) {
2959 p, release := newBlockingPTY(t)
3060 defer release()
@@ -25,6 +25,36 @@ import (
25 "github.com/bnema/vev/pkg/vt"25 "github.com/bnema/vev/pkg/vt"
26 )26 )
27 27
28+func TestPaletteBackdropDimsSimultaneousCopyMode(t *testing.T) {
29+ p, release := newBlockingPTY(t)
30+ defer release()
31+ d, sess, ac, sends := newManualSessionWithPTYs(t, p)
32+ theme := backdropTheme()
33+ ac.setTheme(theme)
34+ client := vt.NewScreen(80, 25)
35+ pane := sess.tabs[0].focusedPane()
36+ pane.screen.Write([]byte("\x1b[38;2;180;90;30mX"))
37+
38+ d.enterCopyMode(sess, ac)
39+ mustApplyOutput(t, client, awaitFrame(t, sends, ports.MsgOutput))
40+ undimmed := client.Frame.At(0, 1)
41+ require.Equal(t, 'X', undimmed.Rune, "fixture must address copy-mode pane content")
42+ copyBar := append([]renderer.Cell(nil), client.Frame.Row(client.Frame.Height-1)...)
43+ require.Contains(t, rowText(copyBar), "[SCROLL]", "fixture must capture the copy status bar")
44+
45+ d.enterPalette(sess, ac)
46+ mustApplyOutput(t, client, awaitFrame(t, sends, ports.MsgOutput))
47+ dimmed := client.Frame.At(0, 1)
48+ require.Equal(t, undimmed.Rune, dimmed.Rune, "palette backdrop must preserve copy content")
49+ require.Equal(t, themeui.DimStyle(undimmed.Style, theme), dimmed.Style, "palette backdrop must dim the composed copy frame")
50+ require.Equal(t, copyBar, client.Frame.Row(client.Frame.Height-1), "copy status bar must remain crisp")
51+ paletteVisible := false
52+ for y := range client.Frame.Height {
53+ paletteVisible = paletteVisible || strings.Contains(rowText(client.Frame.Row(y)), "Commands")
54+ }
55+ require.True(t, paletteVisible, "palette must remain composed above copy mode")
56+}
57+
28 func TestPaletteBackdropKeepsSimultaneousPickerCrisp(t *testing.T) {58 func TestPaletteBackdropKeepsSimultaneousPickerCrisp(t *testing.T) {
29 p, release := newBlockingPTY(t)59 p, release := newBlockingPTY(t)
30 defer release()60 defer release()

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