detached 2b23600

Merge pull request #119 from bnema/fix/modal-neutral-backgrounds

fix(theme): keep overlay backgrounds neutral
commit
2b236000ce009896f54930c39a40a7307c32c3f6
author
bnema · · 2026-07-21 14:56 UTC
committer
GitHub · · 2026-07-21 14:56 UTC
merge of
0d707da + ba52044 · diff against the first parent
signature
GPG UnverifiedB5690EEEBB952194 The key is unknown
11 files · +84 -54
M internal/usecase/daemon/notices_overlay_test.go +27 -0
@@ -11,8 +11,35 @@ import (
1111
1212 "github.com/bnema/vev/internal/domain"
1313 "github.com/bnema/vev/internal/ports"
14+ "github.com/bnema/vev/internal/usecase/notices"
15+ themeui "github.com/bnema/vev/internal/usecase/theme"
16+ "github.com/bnema/vev/pkg/renderer"
1417 )
1518
19+func TestCaptureNoticesOverlayUsesTerminalBackgroundOutsideSelection(t *testing.T) {
20+ terminal := renderer.DefaultStyle()
21+ inactive := renderer.Style{HasBackgroundRGB: true, BackgroundRGB: renderer.RGB{R: 10, G: 20, B: 30}}
22+ selection := renderer.Style{HasBackgroundRGB: true, BackgroundRGB: renderer.RGB{R: 40, G: 50, B: 60}}
23+ styles := themeui.Styles{
24+ PickerBase: terminal, SurfaceInactive: inactive, PickerSelection: selection,
25+ PickerSelectionName: selection, PickerSelectionMuted: selection,
26+ PickerDescription: terminal,
27+ }
28+ model := notices.New([]domain.Notification{
29+ {Code: domain.NoticePaneSpawn, Message: "selected", Time: time.Unix(2, 0)},
30+ {Code: domain.NoticeTabSpawn, Message: "inactive", Time: time.Unix(1, 0)},
31+ }, time.Unix(3, 0))
32+ state := capturedRenderState{styles: styles, layout: capturedTabLayout{area: domain.Rect{Width: 100, Height: 38}}}
33+ snap := &overlayRenderSnapshot{noticesOverlayActive: true, noticesOverlayModel: model}
34+
35+ captureOverlayLayers(&state, snap, domain.PaletteConfig{})
36+
37+ inner := state.overlays.noticesOverlay.inner
38+ require.True(t, inner.At(inner.Width-1, 1).Style.Equal(terminal), "inactive row keeps the terminal background")
39+ require.True(t, inner.At(inner.Width-1, 2).Style.Equal(terminal), "unused interior keeps the terminal background")
40+ require.True(t, inner.At(inner.Width-1, 0).Style.Equal(selection), "selected row keeps the accent background")
41+}
42+
1643 func TestNoticesOpenViaPaletteCommandShowsModalWithHistory(t *testing.T) {
1744 p, release := newBlockingPTY(t)
1845 d, sess, ac, sends := newManualSessionWithPTYs(t, p)
@@ -11,8 +11,35 @@ import (
11 11
12 "github.com/bnema/vev/internal/domain"12 "github.com/bnema/vev/internal/domain"
13 "github.com/bnema/vev/internal/ports"13 "github.com/bnema/vev/internal/ports"
14+ "github.com/bnema/vev/internal/usecase/notices"
15+ themeui "github.com/bnema/vev/internal/usecase/theme"
16+ "github.com/bnema/vev/pkg/renderer"
14 )17 )
15 18
19+func TestCaptureNoticesOverlayUsesTerminalBackgroundOutsideSelection(t *testing.T) {
20+ terminal := renderer.DefaultStyle()
21+ inactive := renderer.Style{HasBackgroundRGB: true, BackgroundRGB: renderer.RGB{R: 10, G: 20, B: 30}}
22+ selection := renderer.Style{HasBackgroundRGB: true, BackgroundRGB: renderer.RGB{R: 40, G: 50, B: 60}}
23+ styles := themeui.Styles{
24+ PickerBase: terminal, SurfaceInactive: inactive, PickerSelection: selection,
25+ PickerSelectionName: selection, PickerSelectionMuted: selection,
26+ PickerDescription: terminal,
27+ }
28+ model := notices.New([]domain.Notification{
29+ {Code: domain.NoticePaneSpawn, Message: "selected", Time: time.Unix(2, 0)},
30+ {Code: domain.NoticeTabSpawn, Message: "inactive", Time: time.Unix(1, 0)},
31+ }, time.Unix(3, 0))
32+ state := capturedRenderState{styles: styles, layout: capturedTabLayout{area: domain.Rect{Width: 100, Height: 38}}}
33+ snap := &overlayRenderSnapshot{noticesOverlayActive: true, noticesOverlayModel: model}
34+
35+ captureOverlayLayers(&state, snap, domain.PaletteConfig{})
36+
37+ inner := state.overlays.noticesOverlay.inner
38+ require.True(t, inner.At(inner.Width-1, 1).Style.Equal(terminal), "inactive row keeps the terminal background")
39+ require.True(t, inner.At(inner.Width-1, 2).Style.Equal(terminal), "unused interior keeps the terminal background")
40+ require.True(t, inner.At(inner.Width-1, 0).Style.Equal(selection), "selected row keeps the accent background")
41+}
42+
16 func TestNoticesOpenViaPaletteCommandShowsModalWithHistory(t *testing.T) {43 func TestNoticesOpenViaPaletteCommandShowsModalWithHistory(t *testing.T) {
17 p, release := newBlockingPTY(t)44 p, release := newBlockingPTY(t)
18 d, sess, ac, sends := newManualSessionWithPTYs(t, p)45 d, sess, ac, sends := newManualSessionWithPTYs(t, p)
M internal/usecase/daemon/palette.go +1 -1
@@ -520,6 +520,6 @@ func composePaletteClientFrame(model *palette.Model, base renderer.Frame, cfg do
520520 styleSet := resolveStyles(styles)
521521 modal := paletteModalFor(domain.Size{Cols: base.Width, Rows: base.Height}, cfg)
522522 return composeModalClientFrame(base, modal, styleSet, func(size domain.Size) renderer.Frame {
523- return model.Render(size, palette.RenderOptions{Styles: palette.RenderStyles{Base: styleSet.PickerBase, Row: styleSet.SurfaceInactive, Selection: styleSet.PickerSelection, Description: styleSet.PickerDescription, SelectionDescription: styleSet.PickerSelectionMuted}, Guidance: guidance})
523+ return model.Render(size, palette.RenderOptions{Styles: palette.RenderStyles{Base: styleSet.PickerBase, Row: styleSet.PickerBase, Selection: styleSet.PickerSelection, Description: styleSet.PickerDescription, SelectionDescription: styleSet.PickerSelectionMuted}, Guidance: guidance})
524524 })
525525 }
@@ -520,6 +520,6 @@ func composePaletteClientFrame(model *palette.Model, base renderer.Frame, cfg do
520 styleSet := resolveStyles(styles)520 styleSet := resolveStyles(styles)
521 modal := paletteModalFor(domain.Size{Cols: base.Width, Rows: base.Height}, cfg)521 modal := paletteModalFor(domain.Size{Cols: base.Width, Rows: base.Height}, cfg)
522 return composeModalClientFrame(base, modal, styleSet, func(size domain.Size) renderer.Frame {522 return composeModalClientFrame(base, modal, styleSet, func(size domain.Size) renderer.Frame {
523- return model.Render(size, palette.RenderOptions{Styles: palette.RenderStyles{Base: styleSet.PickerBase, Row: styleSet.SurfaceInactive, Selection: styleSet.PickerSelection, Description: styleSet.PickerDescription, SelectionDescription: styleSet.PickerSelectionMuted}, Guidance: guidance})523+ return model.Render(size, palette.RenderOptions{Styles: palette.RenderStyles{Base: styleSet.PickerBase, Row: styleSet.PickerBase, Selection: styleSet.PickerSelection, Description: styleSet.PickerDescription, SelectionDescription: styleSet.PickerSelectionMuted}, Guidance: guidance})
524 })524 })
525 }525 }
M internal/usecase/daemon/palette_test.go +5 -4
@@ -74,13 +74,14 @@ func TestCaptureOverlayLayersPreservesPaletteDescriptionSurfaceAcrossFallbacks(t
7474 captureOverlayLayers(&state, snap, domain.PaletteConfig{})
7575
7676 inactive := state.overlays.palette.inner.At(4, 1).Style
77- require.Equal(t, state.styles.SurfaceInactive.Background, inactive.Background)
78- require.Equal(t, state.styles.SurfaceInactive.HasBackgroundRGB, inactive.HasBackgroundRGB)
79- require.Equal(t, state.styles.SurfaceInactive.BackgroundRGB, inactive.BackgroundRGB)
77+ require.Equal(t, state.styles.PickerDescription.Foreground, inactive.Foreground)
78+ require.Equal(t, state.styles.PickerDescription.HasForegroundRGB, inactive.HasForegroundRGB)
79+ require.Equal(t, state.styles.PickerDescription.ForegroundRGB, inactive.ForegroundRGB)
80+ require.False(t, inactive.HasBackgroundRGB)
8081 if tt.indexed {
8182 require.Equal(t, 2, inactive.Foreground)
82- require.False(t, inactive.HasBackgroundRGB)
8383 }
84+ require.True(t, state.overlays.palette.inner.At(31, 1).Style.Equal(state.styles.PickerBase), "inactive row filler keeps the terminal background")
8485
8586 selected := state.overlays.palette.inner.At(4, 2).Style
8687 require.True(t, selected.Equal(state.styles.PickerSelectionMuted))
@@ -74,13 +74,14 @@ func TestCaptureOverlayLayersPreservesPaletteDescriptionSurfaceAcrossFallbacks(t
74 captureOverlayLayers(&state, snap, domain.PaletteConfig{})74 captureOverlayLayers(&state, snap, domain.PaletteConfig{})
75 75
76 inactive := state.overlays.palette.inner.At(4, 1).Style76 inactive := state.overlays.palette.inner.At(4, 1).Style
77- require.Equal(t, state.styles.SurfaceInactive.Background, inactive.Background)77+ require.Equal(t, state.styles.PickerDescription.Foreground, inactive.Foreground)
78- require.Equal(t, state.styles.SurfaceInactive.HasBackgroundRGB, inactive.HasBackgroundRGB)78+ require.Equal(t, state.styles.PickerDescription.HasForegroundRGB, inactive.HasForegroundRGB)
79- require.Equal(t, state.styles.SurfaceInactive.BackgroundRGB, inactive.BackgroundRGB)79+ require.Equal(t, state.styles.PickerDescription.ForegroundRGB, inactive.ForegroundRGB)
80+ require.False(t, inactive.HasBackgroundRGB)
80 if tt.indexed {81 if tt.indexed {
81 require.Equal(t, 2, inactive.Foreground)82 require.Equal(t, 2, inactive.Foreground)
82- require.False(t, inactive.HasBackgroundRGB)
83 }83 }
84+ require.True(t, state.overlays.palette.inner.At(31, 1).Style.Equal(state.styles.PickerBase), "inactive row filler keeps the terminal background")
84 85
85 selected := state.overlays.palette.inner.At(4, 2).Style86 selected := state.overlays.palette.inner.At(4, 2).Style
86 require.True(t, selected.Equal(state.styles.PickerSelectionMuted))87 require.True(t, selected.Equal(state.styles.PickerSelectionMuted))
M internal/usecase/daemon/picker_test.go +5 -10
@@ -717,28 +717,23 @@ func TestCaptureOverlayLayersPreservesPickerSemanticSurfacesAcrossFallbacks(t *t
717717
718718 captureOverlayLayers(&state, snap, domain.PaletteConfig{})
719719
720- require.Equal(t, state.styles.SurfaceInactive.Background, state.styles.PickerDescription.Background)
721- require.Equal(t, state.styles.SurfaceInactive.HasBackgroundRGB, state.styles.PickerDescription.HasBackgroundRGB)
722- require.Equal(t, state.styles.SurfaceInactive.BackgroundRGB, state.styles.PickerDescription.BackgroundRGB)
723- require.Equal(t, state.styles.SurfaceBar.Background, state.styles.PickerSeparator.Background)
724- require.Equal(t, state.styles.SurfaceBar.HasBackgroundRGB, state.styles.PickerSeparator.HasBackgroundRGB)
725- require.Equal(t, state.styles.SurfaceBar.BackgroundRGB, state.styles.PickerSeparator.BackgroundRGB)
720+ require.False(t, state.styles.PickerDescription.HasBackgroundRGB)
721+ require.False(t, state.styles.PickerSeparator.HasBackgroundRGB)
726722 if tt.indexed {
727723 require.Equal(t, 2, state.styles.PickerDescription.Foreground)
728724 require.Equal(t, 2, state.styles.PickerSeparator.Foreground)
729- require.False(t, state.styles.PickerDescription.HasBackgroundRGB)
730- require.False(t, state.styles.PickerSeparator.HasBackgroundRGB)
731725 }
732726
733727 inner := state.overlays.picker.inner
734728 layout := picker.ChooseLayout(domain.Size{Cols: inner.Width, Rows: inner.Height})
735729 require.Equal(t, picker.LayoutHorizontal, layout.Mode)
736- require.True(t, inner.At(5, 3).Style.Equal(state.styles.PickerDescription), "description text retains the inactive row surface")
730+ require.True(t, inner.At(5, 3).Style.Equal(state.styles.PickerDescription), "description text keeps a muted foreground on the terminal background")
731+ require.True(t, inner.At(layout.List.Width-1, 3).Style.Equal(state.styles.PickerBase), "inactive row filler keeps the terminal background")
737732 for y := layout.Separator.Y; y < layout.Separator.Y+layout.Separator.Height; y++ {
738733 for x := layout.Separator.X; x < layout.Separator.X+layout.Separator.Width; x++ {
739734 cell := inner.At(x, y)
740735 require.Equal(t, '│', cell.Rune)
741- require.True(t, cell.Style.Equal(state.styles.PickerSeparator), "separator retains the bar surface")
736+ require.True(t, cell.Style.Equal(state.styles.PickerSeparator), "separator keeps a foreground-only style on the terminal background")
742737 }
743738 }
744739 })
@@ -717,28 +717,23 @@ func TestCaptureOverlayLayersPreservesPickerSemanticSurfacesAcrossFallbacks(t *t
717 717
718 captureOverlayLayers(&state, snap, domain.PaletteConfig{})718 captureOverlayLayers(&state, snap, domain.PaletteConfig{})
719 719
720- require.Equal(t, state.styles.SurfaceInactive.Background, state.styles.PickerDescription.Background)720+ require.False(t, state.styles.PickerDescription.HasBackgroundRGB)
721- require.Equal(t, state.styles.SurfaceInactive.HasBackgroundRGB, state.styles.PickerDescription.HasBackgroundRGB)721+ require.False(t, state.styles.PickerSeparator.HasBackgroundRGB)
722- require.Equal(t, state.styles.SurfaceInactive.BackgroundRGB, state.styles.PickerDescription.BackgroundRGB)
723- require.Equal(t, state.styles.SurfaceBar.Background, state.styles.PickerSeparator.Background)
724- require.Equal(t, state.styles.SurfaceBar.HasBackgroundRGB, state.styles.PickerSeparator.HasBackgroundRGB)
725- require.Equal(t, state.styles.SurfaceBar.BackgroundRGB, state.styles.PickerSeparator.BackgroundRGB)
726 if tt.indexed {722 if tt.indexed {
727 require.Equal(t, 2, state.styles.PickerDescription.Foreground)723 require.Equal(t, 2, state.styles.PickerDescription.Foreground)
728 require.Equal(t, 2, state.styles.PickerSeparator.Foreground)724 require.Equal(t, 2, state.styles.PickerSeparator.Foreground)
729- require.False(t, state.styles.PickerDescription.HasBackgroundRGB)
730- require.False(t, state.styles.PickerSeparator.HasBackgroundRGB)
731 }725 }
732 726
733 inner := state.overlays.picker.inner727 inner := state.overlays.picker.inner
734 layout := picker.ChooseLayout(domain.Size{Cols: inner.Width, Rows: inner.Height})728 layout := picker.ChooseLayout(domain.Size{Cols: inner.Width, Rows: inner.Height})
735 require.Equal(t, picker.LayoutHorizontal, layout.Mode)729 require.Equal(t, picker.LayoutHorizontal, layout.Mode)
736- require.True(t, inner.At(5, 3).Style.Equal(state.styles.PickerDescription), "description text retains the inactive row surface")730+ require.True(t, inner.At(5, 3).Style.Equal(state.styles.PickerDescription), "description text keeps a muted foreground on the terminal background")
731+ require.True(t, inner.At(layout.List.Width-1, 3).Style.Equal(state.styles.PickerBase), "inactive row filler keeps the terminal background")
737 for y := layout.Separator.Y; y < layout.Separator.Y+layout.Separator.Height; y++ {732 for y := layout.Separator.Y; y < layout.Separator.Y+layout.Separator.Height; y++ {
738 for x := layout.Separator.X; x < layout.Separator.X+layout.Separator.Width; x++ {733 for x := layout.Separator.X; x < layout.Separator.X+layout.Separator.Width; x++ {
739 cell := inner.At(x, y)734 cell := inner.At(x, y)
740 require.Equal(t, '│', cell.Rune)735 require.Equal(t, '│', cell.Rune)
741- require.True(t, cell.Style.Equal(state.styles.PickerSeparator), "separator retains the bar surface")736+ require.True(t, cell.Style.Equal(state.styles.PickerSeparator), "separator keeps a foreground-only style on the terminal background")
742 }737 }
743 }738 }
744 })739 })
M internal/usecase/daemon/render_pipeline.go +3 -3
@@ -229,13 +229,13 @@ func captureOverlayLayers(state *capturedRenderState, snap *overlayRenderSnapsho
229229 if snap.pickerActive && snap.pickerModel != nil {
230230 o.picker.modal = pickerModal
231231 o.picker.focused = true
232- renderStyles := picker.RenderStyles{Background: styles.PickerBase, Selection: styles.PickerSelection, SelectionName: styles.PickerSelectionName, SelectionMuted: styles.PickerSelectionMuted, Name: styles.SurfaceInactive, Detail: styles.PickerDescription, Base: styles.SurfaceInactive, Separator: styles.PickerSeparator}
232+ renderStyles := picker.RenderStyles{Background: styles.PickerBase, Selection: styles.PickerSelection, SelectionName: styles.PickerSelectionName, SelectionMuted: styles.PickerSelectionMuted, Name: styles.PickerName, Detail: styles.PickerDescription, Base: styles.PickerBase, Separator: styles.PickerSeparator}
233233 o.picker.inner = snap.pickerModel.Render(rectSize(pickerModal.Inner(size)), state.preview, renderStyles)
234234 }
235235 if snap.noticesOverlayActive && snap.noticesOverlayModel != nil {
236236 o.noticesOverlay.modal = noticesModal
237237 o.noticesOverlay.focused = true
238- renderStyles := notices.RenderStyles{Background: styles.PickerBase, Base: styles.SurfaceInactive, Selection: styles.PickerSelection, Text: styles.SurfaceInactive, SelectionText: styles.PickerSelectionName, Muted: styles.PickerDescription, SelectionMuted: styles.PickerSelectionMuted}
238+ renderStyles := notices.RenderStyles{Background: styles.PickerBase, Base: styles.PickerBase, Selection: styles.PickerSelection, Text: styles.PickerBase, SelectionText: styles.PickerSelectionName, Muted: styles.PickerDescription, SelectionMuted: styles.PickerSelectionMuted}
239239 o.noticesOverlay.inner = snap.noticesOverlayModel.Render(rectSize(noticesModal.Inner(size)), renderStyles)
240240 }
241241 if snap.paletteActive && snap.paletteModel != nil {
@@ -246,7 +246,7 @@ func captureOverlayLayers(state *capturedRenderState, snap *overlayRenderSnapsho
246246 guidance = snap.paletteHints.Feedback
247247 }
248248 o.paletteGuidance = snap.paletteFeedback
249- o.palette.inner = snap.paletteModel.Render(rectSize(o.palette.modal.Inner(size)), palette.RenderOptions{Styles: palette.RenderStyles{Base: styles.PickerBase, Row: styles.SurfaceInactive, Selection: styles.PickerSelection, Description: styles.PickerDescription, SelectionDescription: styles.PickerSelectionMuted}, Guidance: guidance, Feedback: snap.paletteFeedback})
249+ o.palette.inner = snap.paletteModel.Render(rectSize(o.palette.modal.Inner(size)), palette.RenderOptions{Styles: palette.RenderStyles{Base: styles.PickerBase, Row: styles.PickerBase, Selection: styles.PickerSelection, Description: styles.PickerDescription, SelectionDescription: styles.PickerSelectionMuted}, Guidance: guidance, Feedback: snap.paletteFeedback})
250250 }
251251 if snap.promptActive && snap.promptModel != nil {
252252 o.prompt.modal = promptModalFor(snap.promptModel.Title())
@@ -229,13 +229,13 @@ func captureOverlayLayers(state *capturedRenderState, snap *overlayRenderSnapsho
229 if snap.pickerActive && snap.pickerModel != nil {229 if snap.pickerActive && snap.pickerModel != nil {
230 o.picker.modal = pickerModal230 o.picker.modal = pickerModal
231 o.picker.focused = true231 o.picker.focused = true
232- renderStyles := picker.RenderStyles{Background: styles.PickerBase, Selection: styles.PickerSelection, SelectionName: styles.PickerSelectionName, SelectionMuted: styles.PickerSelectionMuted, Name: styles.SurfaceInactive, Detail: styles.PickerDescription, Base: styles.SurfaceInactive, Separator: styles.PickerSeparator}232+ renderStyles := picker.RenderStyles{Background: styles.PickerBase, Selection: styles.PickerSelection, SelectionName: styles.PickerSelectionName, SelectionMuted: styles.PickerSelectionMuted, Name: styles.PickerName, Detail: styles.PickerDescription, Base: styles.PickerBase, Separator: styles.PickerSeparator}
233 o.picker.inner = snap.pickerModel.Render(rectSize(pickerModal.Inner(size)), state.preview, renderStyles)233 o.picker.inner = snap.pickerModel.Render(rectSize(pickerModal.Inner(size)), state.preview, renderStyles)
234 }234 }
235 if snap.noticesOverlayActive && snap.noticesOverlayModel != nil {235 if snap.noticesOverlayActive && snap.noticesOverlayModel != nil {
236 o.noticesOverlay.modal = noticesModal236 o.noticesOverlay.modal = noticesModal
237 o.noticesOverlay.focused = true237 o.noticesOverlay.focused = true
238- renderStyles := notices.RenderStyles{Background: styles.PickerBase, Base: styles.SurfaceInactive, Selection: styles.PickerSelection, Text: styles.SurfaceInactive, SelectionText: styles.PickerSelectionName, Muted: styles.PickerDescription, SelectionMuted: styles.PickerSelectionMuted}238+ renderStyles := notices.RenderStyles{Background: styles.PickerBase, Base: styles.PickerBase, Selection: styles.PickerSelection, Text: styles.PickerBase, SelectionText: styles.PickerSelectionName, Muted: styles.PickerDescription, SelectionMuted: styles.PickerSelectionMuted}
239 o.noticesOverlay.inner = snap.noticesOverlayModel.Render(rectSize(noticesModal.Inner(size)), renderStyles)239 o.noticesOverlay.inner = snap.noticesOverlayModel.Render(rectSize(noticesModal.Inner(size)), renderStyles)
240 }240 }
241 if snap.paletteActive && snap.paletteModel != nil {241 if snap.paletteActive && snap.paletteModel != nil {
@@ -246,7 +246,7 @@ func captureOverlayLayers(state *capturedRenderState, snap *overlayRenderSnapsho
246 guidance = snap.paletteHints.Feedback246 guidance = snap.paletteHints.Feedback
247 }247 }
248 o.paletteGuidance = snap.paletteFeedback248 o.paletteGuidance = snap.paletteFeedback
249- o.palette.inner = snap.paletteModel.Render(rectSize(o.palette.modal.Inner(size)), palette.RenderOptions{Styles: palette.RenderStyles{Base: styles.PickerBase, Row: styles.SurfaceInactive, Selection: styles.PickerSelection, Description: styles.PickerDescription, SelectionDescription: styles.PickerSelectionMuted}, Guidance: guidance, Feedback: snap.paletteFeedback})249+ o.palette.inner = snap.paletteModel.Render(rectSize(o.palette.modal.Inner(size)), palette.RenderOptions{Styles: palette.RenderStyles{Base: styles.PickerBase, Row: styles.PickerBase, Selection: styles.PickerSelection, Description: styles.PickerDescription, SelectionDescription: styles.PickerSelectionMuted}, Guidance: guidance, Feedback: snap.paletteFeedback})
250 }250 }
251 if snap.promptActive && snap.promptModel != nil {251 if snap.promptActive && snap.promptModel != nil {
252 o.prompt.modal = promptModalFor(snap.promptModel.Title())252 o.prompt.modal = promptModalFor(snap.promptModel.Title())
M internal/usecase/daemon/render_pipeline_test.go +3 -1
@@ -321,13 +321,15 @@ func TestComposeCapturedOverlaysUsesCachedModalRoles(t *testing.T) {
321321 }
322322
323323 func TestNoticeStylesFromMapsWarnToDedicatedRoleDistinctFromInfo(t *testing.T) {
324+ terminal := renderer.DefaultStyle()
324325 muted := renderer.Style{Foreground: 1}
325326 active := renderer.Style{Foreground: 2}
326327 warn := renderer.Style{Foreground: 3}
327- styles := themeui.Styles{BorderMuted: muted, BorderActive: active, BorderWarn: warn}
328+ styles := themeui.Styles{PickerBase: terminal, BorderMuted: muted, BorderActive: active, BorderWarn: warn}
328329
329330 got := noticeStylesFrom(styles)
330331
332+ require.True(t, got.Text.Equal(terminal), "toast content keeps the terminal background")
331333 require.Equal(t, active, got.BoxError)
332334 require.Equal(t, muted, got.BoxInfo)
333335 // BoxWarn must use the dedicated BorderWarn role, not fall back to the
@@ -321,13 +321,15 @@ func TestComposeCapturedOverlaysUsesCachedModalRoles(t *testing.T) {
321 }321 }
322 322
323 func TestNoticeStylesFromMapsWarnToDedicatedRoleDistinctFromInfo(t *testing.T) {323 func TestNoticeStylesFromMapsWarnToDedicatedRoleDistinctFromInfo(t *testing.T) {
324+ terminal := renderer.DefaultStyle()
324 muted := renderer.Style{Foreground: 1}325 muted := renderer.Style{Foreground: 1}
325 active := renderer.Style{Foreground: 2}326 active := renderer.Style{Foreground: 2}
326 warn := renderer.Style{Foreground: 3}327 warn := renderer.Style{Foreground: 3}
327- styles := themeui.Styles{BorderMuted: muted, BorderActive: active, BorderWarn: warn}328+ styles := themeui.Styles{PickerBase: terminal, BorderMuted: muted, BorderActive: active, BorderWarn: warn}
328 329
329 got := noticeStylesFrom(styles)330 got := noticeStylesFrom(styles)
330 331
332+ require.True(t, got.Text.Equal(terminal), "toast content keeps the terminal background")
331 require.Equal(t, active, got.BoxError)333 require.Equal(t, active, got.BoxError)
332 require.Equal(t, muted, got.BoxInfo)334 require.Equal(t, muted, got.BoxInfo)
333 // BoxWarn must use the dedicated BorderWarn role, not fall back to the335 // BoxWarn must use the dedicated BorderWarn role, not fall back to the
M internal/usecase/picker/model_test.go +7 -12
@@ -350,7 +350,7 @@ func TestRenderStylesFillBackgroundRowsAndSelection(t *testing.T) {
350350 require.True(t, frame.At(19, 1).Style.Equal(selection), "selected row owns active surface")
351351 }
352352
353-func TestPickerDetailKeepsInactiveSurfaceAcrossAccentFallbacks(t *testing.T) {
353+func TestPickerRowsKeepTerminalBackgroundAcrossAccentFallbacks(t *testing.T) {
354354 palette := [16]renderer.RGB{}
355355 palette[2] = renderer.RGB{R: 10, G: 230, B: 120}
356356 palette[10] = palette[2]
@@ -387,27 +387,22 @@ func TestPickerDetailKeepsInactiveSurfaceAcrossAccentFallbacks(t *testing.T) {
387387 for _, tt := range tests {
388388 t.Run(tt.name, func(t *testing.T) {
389389 styles := themeui.Resolve(tt.theme, tt.policy).Styles
390- require.Equal(t, styles.SurfaceInactive.Background, styles.PickerDescription.Background)
391- require.Equal(t, styles.SurfaceInactive.HasBackgroundRGB, styles.PickerDescription.HasBackgroundRGB)
392- require.Equal(t, styles.SurfaceInactive.BackgroundRGB, styles.PickerDescription.BackgroundRGB)
393- require.Equal(t, styles.SurfaceBar.Background, styles.PickerSeparator.Background)
394- require.Equal(t, styles.SurfaceBar.HasBackgroundRGB, styles.PickerSeparator.HasBackgroundRGB)
395- require.Equal(t, styles.SurfaceBar.BackgroundRGB, styles.PickerSeparator.BackgroundRGB)
390+ require.True(t, styles.PickerBase.Equal(renderer.DefaultStyle()))
391+ require.False(t, styles.PickerDescription.HasBackgroundRGB)
392+ require.False(t, styles.PickerSeparator.HasBackgroundRGB)
396393 if tt.name == "indexed only" {
397394 require.Equal(t, 2, styles.PickerDescription.Foreground)
398395 require.Equal(t, 2, styles.PickerSeparator.Foreground)
399- require.False(t, styles.PickerDescription.HasBackgroundRGB)
400- require.False(t, styles.PickerSeparator.HasBackgroundRGB)
401396 }
402397
403398 frame := model.Render(domain.Size{Cols: 32, Rows: 4}, Preview{}, RenderStyles{
404- Background: styles.PickerBase, Base: styles.SurfaceInactive, Name: styles.SurfaceInactive,
399+ Background: styles.PickerBase, Base: styles.PickerBase, Name: styles.PickerName,
405400 Detail: styles.PickerDescription, Selection: styles.PickerSelection,
406401 SelectionName: styles.PickerSelection, SelectionMuted: styles.PickerSelection,
407402 Separator: styles.PickerSeparator,
408403 })
409- require.True(t, frame.At(5, 3).Style.Equal(styles.PickerDescription), "description text must retain its contrast-derived foreground and inactive background")
410- require.True(t, frame.At(31, 3).Style.Equal(styles.SurfaceInactive), "unused row cells must retain the inactive surface")
404+ require.True(t, frame.At(5, 3).Style.Equal(styles.PickerDescription), "description text keeps a contrast-derived foreground without a background tint")
405+ require.True(t, frame.At(31, 3).Style.Equal(styles.PickerBase), "unused row cells retain the terminal background")
411406 })
412407 }
413408 }
@@ -350,7 +350,7 @@ func TestRenderStylesFillBackgroundRowsAndSelection(t *testing.T) {
350 require.True(t, frame.At(19, 1).Style.Equal(selection), "selected row owns active surface")350 require.True(t, frame.At(19, 1).Style.Equal(selection), "selected row owns active surface")
351 }351 }
352 352
353-func TestPickerDetailKeepsInactiveSurfaceAcrossAccentFallbacks(t *testing.T) {353+func TestPickerRowsKeepTerminalBackgroundAcrossAccentFallbacks(t *testing.T) {
354 palette := [16]renderer.RGB{}354 palette := [16]renderer.RGB{}
355 palette[2] = renderer.RGB{R: 10, G: 230, B: 120}355 palette[2] = renderer.RGB{R: 10, G: 230, B: 120}
356 palette[10] = palette[2]356 palette[10] = palette[2]
@@ -387,27 +387,22 @@ func TestPickerDetailKeepsInactiveSurfaceAcrossAccentFallbacks(t *testing.T) {
387 for _, tt := range tests {387 for _, tt := range tests {
388 t.Run(tt.name, func(t *testing.T) {388 t.Run(tt.name, func(t *testing.T) {
389 styles := themeui.Resolve(tt.theme, tt.policy).Styles389 styles := themeui.Resolve(tt.theme, tt.policy).Styles
390- require.Equal(t, styles.SurfaceInactive.Background, styles.PickerDescription.Background)390+ require.True(t, styles.PickerBase.Equal(renderer.DefaultStyle()))
391- require.Equal(t, styles.SurfaceInactive.HasBackgroundRGB, styles.PickerDescription.HasBackgroundRGB)391+ require.False(t, styles.PickerDescription.HasBackgroundRGB)
392- require.Equal(t, styles.SurfaceInactive.BackgroundRGB, styles.PickerDescription.BackgroundRGB)392+ require.False(t, styles.PickerSeparator.HasBackgroundRGB)
393- require.Equal(t, styles.SurfaceBar.Background, styles.PickerSeparator.Background)
394- require.Equal(t, styles.SurfaceBar.HasBackgroundRGB, styles.PickerSeparator.HasBackgroundRGB)
395- require.Equal(t, styles.SurfaceBar.BackgroundRGB, styles.PickerSeparator.BackgroundRGB)
396 if tt.name == "indexed only" {393 if tt.name == "indexed only" {
397 require.Equal(t, 2, styles.PickerDescription.Foreground)394 require.Equal(t, 2, styles.PickerDescription.Foreground)
398 require.Equal(t, 2, styles.PickerSeparator.Foreground)395 require.Equal(t, 2, styles.PickerSeparator.Foreground)
399- require.False(t, styles.PickerDescription.HasBackgroundRGB)
400- require.False(t, styles.PickerSeparator.HasBackgroundRGB)
401 }396 }
402 397
403 frame := model.Render(domain.Size{Cols: 32, Rows: 4}, Preview{}, RenderStyles{398 frame := model.Render(domain.Size{Cols: 32, Rows: 4}, Preview{}, RenderStyles{
404- Background: styles.PickerBase, Base: styles.SurfaceInactive, Name: styles.SurfaceInactive,399+ Background: styles.PickerBase, Base: styles.PickerBase, Name: styles.PickerName,
405 Detail: styles.PickerDescription, Selection: styles.PickerSelection,400 Detail: styles.PickerDescription, Selection: styles.PickerSelection,
406 SelectionName: styles.PickerSelection, SelectionMuted: styles.PickerSelection,401 SelectionName: styles.PickerSelection, SelectionMuted: styles.PickerSelection,
407 Separator: styles.PickerSeparator,402 Separator: styles.PickerSeparator,
408 })403 })
409- require.True(t, frame.At(5, 3).Style.Equal(styles.PickerDescription), "description text must retain its contrast-derived foreground and inactive background")404+ require.True(t, frame.At(5, 3).Style.Equal(styles.PickerDescription), "description text keeps a contrast-derived foreground without a background tint")
410- require.True(t, frame.At(31, 3).Style.Equal(styles.SurfaceInactive), "unused row cells must retain the inactive surface")405+ require.True(t, frame.At(31, 3).Style.Equal(styles.PickerBase), "unused row cells retain the terminal background")
411 })406 })
412 }407 }
413 }408 }
M internal/usecase/theme/phase3_review_test.go +5 -2
@@ -94,10 +94,13 @@ func TestPickerSeparatorUsesSecondaryTextContrast(t *testing.T) {
9494 theme.PaletteKnown = 1<<2 | 1<<10
9595 resolved := Resolve(theme, domain.ThemeAccent{Mode: domain.ThemeAccentAuto})
9696
97- want, ok := secondaryText(resolved.Styles.PickerBase.ForegroundRGB, resolved.Styles.PickerBase.BackgroundRGB)
97+ want, ok := secondaryText(theme.Foreground, theme.Background)
9898 require.True(t, ok)
9999 require.Equal(t, want, resolved.Styles.PickerSeparator.ForegroundRGB)
100- require.GreaterOrEqual(t, ContrastRatio(resolved.Styles.PickerSeparator.ForegroundRGB, resolved.Styles.PickerBase.BackgroundRGB), normalTextContrast)
100+ require.False(t, resolved.Styles.PickerSeparator.HasBackgroundRGB)
101+ require.Equal(t, want, resolved.Styles.PickerDescription.ForegroundRGB)
102+ require.False(t, resolved.Styles.PickerDescription.HasBackgroundRGB)
103+ require.GreaterOrEqual(t, ContrastRatio(resolved.Styles.PickerSeparator.ForegroundRGB, theme.Background), normalTextContrast)
101104 }
102105
103106 func TestLegacyAliasesPaletteOffAndForcedThemesRemainExact(t *testing.T) {
@@ -94,10 +94,13 @@ func TestPickerSeparatorUsesSecondaryTextContrast(t *testing.T) {
94 theme.PaletteKnown = 1<<2 | 1<<1094 theme.PaletteKnown = 1<<2 | 1<<10
95 resolved := Resolve(theme, domain.ThemeAccent{Mode: domain.ThemeAccentAuto})95 resolved := Resolve(theme, domain.ThemeAccent{Mode: domain.ThemeAccentAuto})
96 96
97- want, ok := secondaryText(resolved.Styles.PickerBase.ForegroundRGB, resolved.Styles.PickerBase.BackgroundRGB)97+ want, ok := secondaryText(theme.Foreground, theme.Background)
98 require.True(t, ok)98 require.True(t, ok)
99 require.Equal(t, want, resolved.Styles.PickerSeparator.ForegroundRGB)99 require.Equal(t, want, resolved.Styles.PickerSeparator.ForegroundRGB)
100- require.GreaterOrEqual(t, ContrastRatio(resolved.Styles.PickerSeparator.ForegroundRGB, resolved.Styles.PickerBase.BackgroundRGB), normalTextContrast)100+ require.False(t, resolved.Styles.PickerSeparator.HasBackgroundRGB)
101+ require.Equal(t, want, resolved.Styles.PickerDescription.ForegroundRGB)
102+ require.False(t, resolved.Styles.PickerDescription.HasBackgroundRGB)
103+ require.GreaterOrEqual(t, ContrastRatio(resolved.Styles.PickerSeparator.ForegroundRGB, theme.Background), normalTextContrast)
101 }104 }
102 105
103 func TestLegacyAliasesPaletteOffAndForcedThemesRemainExact(t *testing.T) {106 func TestLegacyAliasesPaletteOffAndForcedThemesRemainExact(t *testing.T) {
M internal/usecase/theme/ramp_test.go +4 -1
@@ -145,7 +145,10 @@ func TestResolveBuildsCompleteStylesFromOneAccent(t *testing.T) {
145145 require.Equal(t, foregroundStyle(Blend(theme.Foreground, theme.Background, 0.40)), resolved.Styles.NeutralBorder)
146146 require.True(t, resolved.Styles.TabActive.Bold)
147147 require.Equal(t, resolved.Styles.SurfaceRecent, resolved.Styles.MRURecent)
148- require.Equal(t, resolved.Styles.SurfaceBar, resolved.Styles.PickerBase)
148+ require.Equal(t, renderer.DefaultStyle(), resolved.Styles.PickerBase)
149+ require.Equal(t, renderer.DefaultStyle(), resolved.Styles.PromptBase)
150+ require.False(t, resolved.Styles.PickerDescription.HasBackgroundRGB)
151+ require.False(t, resolved.Styles.PickerSeparator.HasBackgroundRGB)
149152 require.Equal(t, resolved.Styles.SurfaceActive.BackgroundRGB, resolved.Styles.PickerSelection.BackgroundRGB)
150153 require.True(t, resolved.Styles.PickerSelection.Bold)
151154 }
@@ -145,7 +145,10 @@ func TestResolveBuildsCompleteStylesFromOneAccent(t *testing.T) {
145 require.Equal(t, foregroundStyle(Blend(theme.Foreground, theme.Background, 0.40)), resolved.Styles.NeutralBorder)145 require.Equal(t, foregroundStyle(Blend(theme.Foreground, theme.Background, 0.40)), resolved.Styles.NeutralBorder)
146 require.True(t, resolved.Styles.TabActive.Bold)146 require.True(t, resolved.Styles.TabActive.Bold)
147 require.Equal(t, resolved.Styles.SurfaceRecent, resolved.Styles.MRURecent)147 require.Equal(t, resolved.Styles.SurfaceRecent, resolved.Styles.MRURecent)
148- require.Equal(t, resolved.Styles.SurfaceBar, resolved.Styles.PickerBase)148+ require.Equal(t, renderer.DefaultStyle(), resolved.Styles.PickerBase)
149+ require.Equal(t, renderer.DefaultStyle(), resolved.Styles.PromptBase)
150+ require.False(t, resolved.Styles.PickerDescription.HasBackgroundRGB)
151+ require.False(t, resolved.Styles.PickerSeparator.HasBackgroundRGB)
149 require.Equal(t, resolved.Styles.SurfaceActive.BackgroundRGB, resolved.Styles.PickerSelection.BackgroundRGB)152 require.Equal(t, resolved.Styles.SurfaceActive.BackgroundRGB, resolved.Styles.PickerSelection.BackgroundRGB)
150 require.True(t, resolved.Styles.PickerSelection.Bold)153 require.True(t, resolved.Styles.PickerSelection.Bold)
151 }154 }
M internal/usecase/theme/resolved_styles.go +23 -19
@@ -36,8 +36,7 @@ func stylesFromRamp(t Theme, accent Accent, ramp Ramp) Styles {
3636
3737 inactiveTitle := secondarySurface(ramp.SurfaceInactive)
3838 activeTitle := secondarySurface(ramp.SurfaceActive)
39- inactiveDescription := secondarySurface(ramp.SurfaceInactive)
40- barDescription := secondarySurface(ramp.SurfaceBar)
39+ overlayDescription := overlaySecondaryText(t)
4140 styles := Styles{
4241 SurfaceBar: ramp.SurfaceBar,
4342 SurfaceInactive: ramp.SurfaceInactive,
@@ -54,19 +53,30 @@ func stylesFromRamp(t Theme, accent Accent, ramp Ramp) Styles {
5453 TabActiveTitle: activeTitle,
5554 MRURecent: ramp.SurfaceRecent,
5655
57- PickerBase: ramp.SurfaceBar,
56+ PickerBase: renderer.DefaultStyle(),
5857 PickerSelection: EmphasisStyle(ramp.SurfaceActive, t),
59- PickerDescription: inactiveDescription,
60- // Separators are secondary text on the picker base surface, not
58+ PickerDescription: overlayDescription,
59+ // Separators are secondary text on the terminal background, not
6160 // non-text borders; they therefore require the normal 4.5:1 contrast.
62- PickerSeparator: barDescription,
63- PromptBase: ramp.SurfaceBar,
61+ PickerSeparator: overlayDescription,
62+ PromptBase: renderer.DefaultStyle(),
6463 CopyStatus: ramp.SurfaceBar,
6564 SearchSelection: EmphasisStyle(ramp.SurfaceActive, t),
6665 }
6766 return withMRUStyles(withLegacyAliases(styles, t), ramp)
6867 }
6968
69+func overlaySecondaryText(t Theme) renderer.Style {
70+ if !usable(t) {
71+ return renderer.DefaultStyle()
72+ }
73+ foreground, ok := secondaryText(t.Foreground, t.Background)
74+ if !ok {
75+ return foregroundStyle(t.Foreground)
76+ }
77+ return foregroundStyle(foreground)
78+}
79+
7080 func secondarySurface(base renderer.Style) renderer.Style {
7181 if !base.HasForegroundRGB || !base.HasBackgroundRGB {
7282 return base
@@ -113,18 +123,12 @@ func neutralStyles(t Theme) Styles {
113123 status := renderer.DefaultStyle()
114124 accent := inverseStyle()
115125 border := renderer.DefaultStyle()
116- inactiveDescription := status
117- barDescription := status
126+ overlayDescription := renderer.DefaultStyle()
118127 if usable(t) {
119128 status = rgbSurface(t.Foreground, Blend(t.Background, t.Foreground, 0.12))
120129 accent = rgbSurface(t.Foreground, Blend(t.Background, t.Foreground, neutralAccentBlend))
121130 border = neutralBorder(t)
122- // Preserve the legacy neutral muted foreground while retaining the
123- // semantic row/bar background beneath description and separator cells.
124- inactiveDescription = status
125- inactiveDescription.ForegroundRGB = Blend(t.Foreground, t.Background, 0.45)
126- barDescription = status
127- barDescription.ForegroundRGB = Blend(t.Foreground, t.Background, 0.45)
131+ overlayDescription = overlaySecondaryText(t)
128132 }
129133 styles := Styles{
130134 SurfaceBar: status,
@@ -142,11 +146,11 @@ func neutralStyles(t Theme) Styles {
142146 TabActiveTitle: MutedVariantStyle(accent, t),
143147 MRURecent: status,
144148
145- PickerBase: status,
149+ PickerBase: renderer.DefaultStyle(),
146150 PickerSelection: EmphasisStyle(accent, t),
147- PickerDescription: inactiveDescription,
148- PickerSeparator: barDescription,
149- PromptBase: status,
151+ PickerDescription: overlayDescription,
152+ PickerSeparator: overlayDescription,
153+ PromptBase: renderer.DefaultStyle(),
150154 CopyStatus: accent,
151155 SearchSelection: accent,
152156 }
@@ -36,8 +36,7 @@ func stylesFromRamp(t Theme, accent Accent, ramp Ramp) Styles {
36 36
37 inactiveTitle := secondarySurface(ramp.SurfaceInactive)37 inactiveTitle := secondarySurface(ramp.SurfaceInactive)
38 activeTitle := secondarySurface(ramp.SurfaceActive)38 activeTitle := secondarySurface(ramp.SurfaceActive)
39- inactiveDescription := secondarySurface(ramp.SurfaceInactive)39+ overlayDescription := overlaySecondaryText(t)
40- barDescription := secondarySurface(ramp.SurfaceBar)
41 styles := Styles{40 styles := Styles{
42 SurfaceBar: ramp.SurfaceBar,41 SurfaceBar: ramp.SurfaceBar,
43 SurfaceInactive: ramp.SurfaceInactive,42 SurfaceInactive: ramp.SurfaceInactive,
@@ -54,19 +53,30 @@ func stylesFromRamp(t Theme, accent Accent, ramp Ramp) Styles {
54 TabActiveTitle: activeTitle,53 TabActiveTitle: activeTitle,
55 MRURecent: ramp.SurfaceRecent,54 MRURecent: ramp.SurfaceRecent,
56 55
57- PickerBase: ramp.SurfaceBar,56+ PickerBase: renderer.DefaultStyle(),
58 PickerSelection: EmphasisStyle(ramp.SurfaceActive, t),57 PickerSelection: EmphasisStyle(ramp.SurfaceActive, t),
59- PickerDescription: inactiveDescription,58+ PickerDescription: overlayDescription,
60- // Separators are secondary text on the picker base surface, not59+ // Separators are secondary text on the terminal background, not
61 // non-text borders; they therefore require the normal 4.5:1 contrast.60 // non-text borders; they therefore require the normal 4.5:1 contrast.
62- PickerSeparator: barDescription,61+ PickerSeparator: overlayDescription,
63- PromptBase: ramp.SurfaceBar,62+ PromptBase: renderer.DefaultStyle(),
64 CopyStatus: ramp.SurfaceBar,63 CopyStatus: ramp.SurfaceBar,
65 SearchSelection: EmphasisStyle(ramp.SurfaceActive, t),64 SearchSelection: EmphasisStyle(ramp.SurfaceActive, t),
66 }65 }
67 return withMRUStyles(withLegacyAliases(styles, t), ramp)66 return withMRUStyles(withLegacyAliases(styles, t), ramp)
68 }67 }
69 68
69+func overlaySecondaryText(t Theme) renderer.Style {
70+ if !usable(t) {
71+ return renderer.DefaultStyle()
72+ }
73+ foreground, ok := secondaryText(t.Foreground, t.Background)
74+ if !ok {
75+ return foregroundStyle(t.Foreground)
76+ }
77+ return foregroundStyle(foreground)
78+}
79+
70 func secondarySurface(base renderer.Style) renderer.Style {80 func secondarySurface(base renderer.Style) renderer.Style {
71 if !base.HasForegroundRGB || !base.HasBackgroundRGB {81 if !base.HasForegroundRGB || !base.HasBackgroundRGB {
72 return base82 return base
@@ -113,18 +123,12 @@ func neutralStyles(t Theme) Styles {
113 status := renderer.DefaultStyle()123 status := renderer.DefaultStyle()
114 accent := inverseStyle()124 accent := inverseStyle()
115 border := renderer.DefaultStyle()125 border := renderer.DefaultStyle()
116- inactiveDescription := status126+ overlayDescription := renderer.DefaultStyle()
117- barDescription := status
118 if usable(t) {127 if usable(t) {
119 status = rgbSurface(t.Foreground, Blend(t.Background, t.Foreground, 0.12))128 status = rgbSurface(t.Foreground, Blend(t.Background, t.Foreground, 0.12))
120 accent = rgbSurface(t.Foreground, Blend(t.Background, t.Foreground, neutralAccentBlend))129 accent = rgbSurface(t.Foreground, Blend(t.Background, t.Foreground, neutralAccentBlend))
121 border = neutralBorder(t)130 border = neutralBorder(t)
122- // Preserve the legacy neutral muted foreground while retaining the131+ overlayDescription = overlaySecondaryText(t)
123- // semantic row/bar background beneath description and separator cells.
124- inactiveDescription = status
125- inactiveDescription.ForegroundRGB = Blend(t.Foreground, t.Background, 0.45)
126- barDescription = status
127- barDescription.ForegroundRGB = Blend(t.Foreground, t.Background, 0.45)
128 }132 }
129 styles := Styles{133 styles := Styles{
130 SurfaceBar: status,134 SurfaceBar: status,
@@ -142,11 +146,11 @@ func neutralStyles(t Theme) Styles {
142 TabActiveTitle: MutedVariantStyle(accent, t),146 TabActiveTitle: MutedVariantStyle(accent, t),
143 MRURecent: status,147 MRURecent: status,
144 148
145- PickerBase: status,149+ PickerBase: renderer.DefaultStyle(),
146 PickerSelection: EmphasisStyle(accent, t),150 PickerSelection: EmphasisStyle(accent, t),
147- PickerDescription: inactiveDescription,151+ PickerDescription: overlayDescription,
148- PickerSeparator: barDescription,152+ PickerSeparator: overlayDescription,
149- PromptBase: status,153+ PromptBase: renderer.DefaultStyle(),
150 CopyStatus: accent,154 CopyStatus: accent,
151 SearchSelection: accent,155 SearchSelection: accent,
152 }156 }
M internal/usecase/theme/theme.go +1 -1
@@ -367,7 +367,7 @@ func BorderStyle(t Theme) renderer.Style {
367367 }
368368
369369 func MutedTextStyle(t Theme) renderer.Style {
370- return neutralStyles(t).PickerDescription
370+ return legacyMutedText(t)
371371 }
372372
373373 // mutedVariantBlend is the fraction MutedVariantStyle blends a style's own
@@ -367,7 +367,7 @@ func BorderStyle(t Theme) renderer.Style {
367 }367 }
368 368
369 func MutedTextStyle(t Theme) renderer.Style {369 func MutedTextStyle(t Theme) renderer.Style {
370- return neutralStyles(t).PickerDescription370+ return legacyMutedText(t)
371 }371 }
372 372
373 // mutedVariantBlend is the fraction MutedVariantStyle blends a style's own373 // mutedVariantBlend is the fraction MutedVariantStyle blends a style's own

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 2

Tags 13

↑↓ moveenter openesc close