fix(ui): preserve palette backdrop in copy mode
- commit
- d5c777e6c4c9c2418d1f72fc3bce054170876105
- author
- brice · · 2026-07-10 05:30 UTC
- parent
- ed1dd0a
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· 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) { | |||
| 343 | 343 | } else { | |
| 344 | 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 | 349 | if overlays.paletteActive { | |
| 347 | 350 | contentArea := domain.Rect{Y: 1, Width: frame.Width, Height: max(0, frame.Height-2)} | |
| 348 | 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 | 353 | if overlays.copySearchModel != nil { | |
| 354 | 354 | frame, damage = composeCopySearchClientFrame(overlays.copySearchModel, frame, styles) | |
| 355 | 355 | } | |
| @@ -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 ( | |||
| 25 | 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 | 58 | func TestPaletteBackdropKeepsSimultaneousPickerCrisp(t *testing.T) { | |
| 29 | 59 | p, release := newBlockingPTY(t) | |
| 30 | 60 | 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() | ||