detached 00609e3

fix(theme): preserve overlay contrast and unify active tab surfaces

commit
00609e34a8b330e82cbb52d7b5e9930f902eca4e
author
brice · · 2026-10-05 12:01 UTC
parent
cfdaba6
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
4 files · +12 -3
M internal/usecase/daemon/render_pipeline.go +1 -1
@@ -335,7 +335,7 @@ func captureOverlayLayers(state *capturedRenderState, snap *overlayRenderSnapsho
335335 modal := promptModalFor(snap.promptModel.Title())
336336 presentation := modal.Resolve(size)
337337 o.prompt = capturedModal{active: true, title: modal.Title, presentation: presentation, focused: true}
338- o.prompt.inner = snap.promptModel.RenderStyled(rectSize(presentation.Inner), prompt.RenderStyles{Base: styles.PromptBase, Selection: styles.SurfaceActive})
338+ o.prompt.inner = snap.promptModel.RenderStyled(rectSize(presentation.Inner), prompt.RenderStyles{Base: styles.PromptBase, Selection: styles.Selection})
339339 }
340340 state.cursor.hiddenByOverlay = o.active()
341341 }
@@ -335,7 +335,7 @@ func captureOverlayLayers(state *capturedRenderState, snap *overlayRenderSnapsho
335 modal := promptModalFor(snap.promptModel.Title())335 modal := promptModalFor(snap.promptModel.Title())
336 presentation := modal.Resolve(size)336 presentation := modal.Resolve(size)
337 o.prompt = capturedModal{active: true, title: modal.Title, presentation: presentation, focused: true}337 o.prompt = capturedModal{active: true, title: modal.Title, presentation: presentation, focused: true}
338- o.prompt.inner = snap.promptModel.RenderStyled(rectSize(presentation.Inner), prompt.RenderStyles{Base: styles.PromptBase, Selection: styles.SurfaceActive})338+ o.prompt.inner = snap.promptModel.RenderStyled(rectSize(presentation.Inner), prompt.RenderStyles{Base: styles.PromptBase, Selection: styles.Selection})
339 }339 }
340 state.cursor.hiddenByOverlay = o.active()340 state.cursor.hiddenByOverlay = o.active()
341 }341 }
M internal/usecase/theme/neutral_session_test.go +6 -0
@@ -23,6 +23,12 @@ func TestNeutralSessionSurfaceHierarchy(t *testing.T) {
2323 require.Equal(t, tt.foreground, styles.SurfaceActive.BackgroundRGB)
2424 require.Equal(t, tt.background, styles.SurfaceActive.ForegroundRGB)
2525 require.Equal(t, styles.TabActive.BackgroundRGB, styles.SurfaceActive.BackgroundRGB)
26+ require.Equal(t, styles.SurfaceActive.BackgroundRGB, styles.TabActiveTitle.BackgroundRGB)
27+ soft := Blend(tt.background, tt.foreground, neutralAccentBlend)
28+ for _, selection := range []renderer.Style{styles.PickerSelection, styles.Selection, styles.SearchSelection} {
29+ require.Equal(t, soft, selection.BackgroundRGB)
30+ }
31+ require.Equal(t, Blend(styles.SurfaceBar.BackgroundRGB, tt.foreground, 0.5), styles.MRUStyle(0, 3).BackgroundRGB)
2632 for count := 1; count <= 9; count++ {
2733 for index := range count {
2834 want := Blend(styles.SurfaceActive.BackgroundRGB, styles.SurfaceBar.BackgroundRGB, 1-mruWeight(index, count))
@@ -23,6 +23,12 @@ func TestNeutralSessionSurfaceHierarchy(t *testing.T) {
23 require.Equal(t, tt.foreground, styles.SurfaceActive.BackgroundRGB)23 require.Equal(t, tt.foreground, styles.SurfaceActive.BackgroundRGB)
24 require.Equal(t, tt.background, styles.SurfaceActive.ForegroundRGB)24 require.Equal(t, tt.background, styles.SurfaceActive.ForegroundRGB)
25 require.Equal(t, styles.TabActive.BackgroundRGB, styles.SurfaceActive.BackgroundRGB)25 require.Equal(t, styles.TabActive.BackgroundRGB, styles.SurfaceActive.BackgroundRGB)
26+ require.Equal(t, styles.SurfaceActive.BackgroundRGB, styles.TabActiveTitle.BackgroundRGB)
27+ soft := Blend(tt.background, tt.foreground, neutralAccentBlend)
28+ for _, selection := range []renderer.Style{styles.PickerSelection, styles.Selection, styles.SearchSelection} {
29+ require.Equal(t, soft, selection.BackgroundRGB)
30+ }
31+ require.Equal(t, Blend(styles.SurfaceBar.BackgroundRGB, tt.foreground, 0.5), styles.MRUStyle(0, 3).BackgroundRGB)
26 for count := 1; count <= 9; count++ {32 for count := 1; count <= 9; count++ {
27 for index := range count {33 for index := range count {
28 want := Blend(styles.SurfaceActive.BackgroundRGB, styles.SurfaceBar.BackgroundRGB, 1-mruWeight(index, count))34 want := Blend(styles.SurfaceActive.BackgroundRGB, styles.SurfaceBar.BackgroundRGB, 1-mruWeight(index, count))
M internal/usecase/theme/phase3_review_test.go +1 -0
@@ -123,6 +123,7 @@ func TestLegacyAliasesPaletteOffAndForcedThemesRemainExact(t *testing.T) {
123123 // Active neutral sessions and tab names share the history anchor.
124124 want["Accent"] = rgbSurface(tt.theme.Background, tt.theme.Foreground)
125125 want["TabNameActive"] = EmphasisStyle(rgbSurface(tt.theme.Background, tt.theme.Foreground), tt.theme)
126+ want["TabTitleActive"] = MutedVariantStyle(rgbSurface(tt.theme.Background, tt.theme.Foreground), tt.theme)
126127 for name, style := range want {
127128 require.Equalf(t, style, legacyAndSemanticStyles(got)[name], "%s", name)
128129 }
@@ -123,6 +123,7 @@ func TestLegacyAliasesPaletteOffAndForcedThemesRemainExact(t *testing.T) {
123 // Active neutral sessions and tab names share the history anchor.123 // Active neutral sessions and tab names share the history anchor.
124 want["Accent"] = rgbSurface(tt.theme.Background, tt.theme.Foreground)124 want["Accent"] = rgbSurface(tt.theme.Background, tt.theme.Foreground)
125 want["TabNameActive"] = EmphasisStyle(rgbSurface(tt.theme.Background, tt.theme.Foreground), tt.theme)125 want["TabNameActive"] = EmphasisStyle(rgbSurface(tt.theme.Background, tt.theme.Foreground), tt.theme)
126+ want["TabTitleActive"] = MutedVariantStyle(rgbSurface(tt.theme.Background, tt.theme.Foreground), tt.theme)
126 for name, style := range want {127 for name, style := range want {
127 require.Equalf(t, style, legacyAndSemanticStyles(got)[name], "%s", name)128 require.Equalf(t, style, legacyAndSemanticStyles(got)[name], "%s", name)
128 }129 }
M internal/usecase/theme/resolved_styles.go +4 -2
@@ -117,8 +117,8 @@ func indexedForeground(style renderer.Style, slot uint8) renderer.Style {
117117 return style
118118 }
119119
120-// neutralStyles deliberately preserves the pre-accent neutral hierarchy. It
121-// is used for palette-off, forced themes, incomplete defaults, and RGB-less
120+// neutralStyles uses full-strength session surfaces and softer overlay selections.
121+// It is used for palette-off, forced themes, incomplete defaults, and RGB-less
122122 // indexed fallbacks; it never performs a palette slot lookup.
123123 func neutralStyles(t Theme) Styles {
124124 status := renderer.DefaultStyle()
@@ -168,6 +168,8 @@ func neutralStyles(t Theme) Styles {
168168 styles.Accent = styles.SurfaceActive
169169 styles.TabActive = EmphasisStyle(styles.SurfaceActive, t)
170170 styles.TabNameActive = styles.TabActive
171+ styles.TabActiveTitle = MutedVariantStyle(styles.SurfaceActive, t)
172+ styles.TabTitleActive = styles.TabActiveTitle
171173 // Without a palette accent the history uses the same mruWeight
172174 // placement as MRUStyle, between the active tab and the bar.
173175 for count := 1; count <= len(styles.mruStyles); count++ {
@@ -117,8 +117,8 @@ func indexedForeground(style renderer.Style, slot uint8) renderer.Style {
117 return style117 return style
118 }118 }
119 119
120-// neutralStyles deliberately preserves the pre-accent neutral hierarchy. It120+// neutralStyles uses full-strength session surfaces and softer overlay selections.
121-// is used for palette-off, forced themes, incomplete defaults, and RGB-less121+// It is used for palette-off, forced themes, incomplete defaults, and RGB-less
122 // indexed fallbacks; it never performs a palette slot lookup.122 // indexed fallbacks; it never performs a palette slot lookup.
123 func neutralStyles(t Theme) Styles {123 func neutralStyles(t Theme) Styles {
124 status := renderer.DefaultStyle()124 status := renderer.DefaultStyle()
@@ -168,6 +168,8 @@ func neutralStyles(t Theme) Styles {
168 styles.Accent = styles.SurfaceActive168 styles.Accent = styles.SurfaceActive
169 styles.TabActive = EmphasisStyle(styles.SurfaceActive, t)169 styles.TabActive = EmphasisStyle(styles.SurfaceActive, t)
170 styles.TabNameActive = styles.TabActive170 styles.TabNameActive = styles.TabActive
171+ styles.TabActiveTitle = MutedVariantStyle(styles.SurfaceActive, t)
172+ styles.TabTitleActive = styles.TabActiveTitle
171 // Without a palette accent the history uses the same mruWeight173 // Without a palette accent the history uses the same mruWeight
172 // placement as MRUStyle, between the active tab and the bar.174 // placement as MRUStyle, between the active tab and the bar.
173 for count := 1; count <= len(styles.mruStyles); count++ {175 for count := 1; count <= len(styles.mruStyles); count++ {

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