detached 05565b9

fix(theme): keep stacked pane titles and symbolic icons readable

commit
05565b913e55f74a54e3fe105a6e762557b51eb7
author
bnema · · 2026-09-04 21:43 UTC
parent
28a2549
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +45 -13
M internal/ui/theme/css.go +6 -13
@@ -745,28 +745,21 @@ func generateStackedPaneCSS(p Palette) string {
745745 /* The title bar is now a Box with GestureClick, not wrapped in a button */
746746 .stacked-pane-titlebar {
747747 background-color: var(--surface-variant);
748+ color: var(--control-text);
748749 border-bottom: 0.0625em solid var(--border);
749750 padding: 0.25em 0.5em;
750751 min-height: 1.5em;
751752 }
752753
753-/* Clickable title bar - hover styling */
754-.stacked-pane-titlebar.stacked-pane-title-clickable {
755- transition: background-color 150ms ease-in-out;
756-}
757-
758-.stacked-pane-titlebar.stacked-pane-title-clickable:hover {
759- background-color: shade(var(--surface-variant), 1.15);
760-}
761-
762-/* Title bar content box */
754+/* Keep the background stable so --control-text retains its contrast.
755+ * Indicate hover with an underline and the active pane with an accent border. */
763756 .stacked-pane-titlebar.active {
764- background-color: shade(var(--surface-variant), 1.2);
765757 border-left: 0.1875em solid var(--accent);
766758 }
767759
768760 /* Favicon image in title bar */
769761 .stacked-pane-titlebar image {
762+ color: var(--control-text);
770763 min-width: 1em;
771764 min-height: 1em;
772765 margin-right: 0.375em;
@@ -774,14 +767,14 @@ func generateStackedPaneCSS(p Palette) string {
774767
775768 /* Title text in title bar */
776769 .stacked-pane-titlebar label {
777- color: var(--text);
770+ color: var(--control-text);
778771 font-size: 0.75em;
779772 font-weight: 400;
780773 }
781774
782775 /* Hover effect on title bar label */
783776 .stacked-pane-titlebar.stacked-pane-title-clickable:hover label {
784- color: var(--accent);
777+ text-decoration-line: underline;
785778 }
786779
787780 /* Close button in stacked pane title bar */
@@ -745,28 +745,21 @@ func generateStackedPaneCSS(p Palette) string {
745 /* The title bar is now a Box with GestureClick, not wrapped in a button */745 /* The title bar is now a Box with GestureClick, not wrapped in a button */
746 .stacked-pane-titlebar {746 .stacked-pane-titlebar {
747 background-color: var(--surface-variant);747 background-color: var(--surface-variant);
748+ color: var(--control-text);
748 border-bottom: 0.0625em solid var(--border);749 border-bottom: 0.0625em solid var(--border);
749 padding: 0.25em 0.5em;750 padding: 0.25em 0.5em;
750 min-height: 1.5em;751 min-height: 1.5em;
751 }752 }
752 753
753-/* Clickable title bar - hover styling */754+/* Keep the background stable so --control-text retains its contrast.
754-.stacked-pane-titlebar.stacked-pane-title-clickable {755+ * Indicate hover with an underline and the active pane with an accent border. */
755- transition: background-color 150ms ease-in-out;
756-}
757-
758-.stacked-pane-titlebar.stacked-pane-title-clickable:hover {
759- background-color: shade(var(--surface-variant), 1.15);
760-}
761-
762-/* Title bar content box */
763 .stacked-pane-titlebar.active {756 .stacked-pane-titlebar.active {
764- background-color: shade(var(--surface-variant), 1.2);
765 border-left: 0.1875em solid var(--accent);757 border-left: 0.1875em solid var(--accent);
766 }758 }
767 759
768 /* Favicon image in title bar */760 /* Favicon image in title bar */
769 .stacked-pane-titlebar image {761 .stacked-pane-titlebar image {
762+ color: var(--control-text);
770 min-width: 1em;763 min-width: 1em;
771 min-height: 1em;764 min-height: 1em;
772 margin-right: 0.375em;765 margin-right: 0.375em;
@@ -774,14 +767,14 @@ func generateStackedPaneCSS(p Palette) string {
774 767
775 /* Title text in title bar */768 /* Title text in title bar */
776 .stacked-pane-titlebar label {769 .stacked-pane-titlebar label {
777- color: var(--text);770+ color: var(--control-text);
778 font-size: 0.75em;771 font-size: 0.75em;
779 font-weight: 400;772 font-weight: 400;
780 }773 }
781 774
782 /* Hover effect on title bar label */775 /* Hover effect on title bar label */
783 .stacked-pane-titlebar.stacked-pane-title-clickable:hover label {776 .stacked-pane-titlebar.stacked-pane-title-clickable:hover label {
784- color: var(--accent);777+ text-decoration-line: underline;
785 }778 }
786 779
787 /* Close button in stacked pane title bar */780 /* Close button in stacked pane title bar */
M internal/ui/theme/css_test.go +39 -0
@@ -8,6 +8,45 @@ import (
88 "github.com/stretchr/testify/assert"
99 )
1010
11+func TestGenerateCSS_StackedTitlebarUsesReadableControlText(t *testing.T) {
12+ tests := []struct {
13+ name string
14+ background string
15+ text string
16+ want string
17+ }{
18+ {"dark background", "#775f79", "#3d303f", "#ffffff"},
19+ {"light background", "#f0f0f0", "#dddddd", "#000000"},
20+ {"readable configured text", "#282828", "#eeeeee", "#eeeeee"},
21+ }
22+ for _, tt := range tests {
23+ t.Run(tt.name, func(t *testing.T) {
24+ palette := DefaultLightPalette()
25+ palette.SurfaceVariant = tt.background
26+ palette.Text = tt.text
27+ css := GenerateCSS(palette)
28+
29+ assert.Contains(t, css, "--control-text: "+tt.want+";")
30+ for _, selector := range []string{
31+ ".stacked-pane-titlebar",
32+ ".stacked-pane-titlebar label",
33+ ".stacked-pane-titlebar image",
34+ } {
35+ block := regexp.MustCompile(regexp.QuoteMeta(selector) + `\s*\{[^}]*\}`).FindString(css)
36+ assert.Contains(t, block, "color: var(--control-text);", selector)
37+ }
38+
39+ // Hover and active states must not invalidate the computed contrast.
40+ stackedCSS := generateStackedPaneCSS(palette)
41+ assert.NotContains(t, stackedCSS, "shade(var(--surface-variant)")
42+ hover := regexp.MustCompile(`\.stacked-pane-titlebar\.stacked-pane-title-clickable:hover label\s*\{[^}]*\}`).FindString(css)
43+ assert.Contains(t, hover, "text-decoration-line: underline;")
44+ assert.NotContains(t, hover, "color:")
45+ assert.Contains(t, stackedCSS, "border-left: 0.1875em solid var(--accent);")
46+ })
47+ }
48+}
49+
1150 func TestGenerateCSS_UsesGTKFontForNativeWidgets(t *testing.T) {
1251 css := GenerateCSSWithScaleAndFonts(DefaultDarkPalette(), 1.0, FontConfig{
1352 SansFont: "Fira Sans",
@@ -8,6 +8,45 @@ import (
8 "github.com/stretchr/testify/assert"8 "github.com/stretchr/testify/assert"
9 )9 )
10 10
11+func TestGenerateCSS_StackedTitlebarUsesReadableControlText(t *testing.T) {
12+ tests := []struct {
13+ name string
14+ background string
15+ text string
16+ want string
17+ }{
18+ {"dark background", "#775f79", "#3d303f", "#ffffff"},
19+ {"light background", "#f0f0f0", "#dddddd", "#000000"},
20+ {"readable configured text", "#282828", "#eeeeee", "#eeeeee"},
21+ }
22+ for _, tt := range tests {
23+ t.Run(tt.name, func(t *testing.T) {
24+ palette := DefaultLightPalette()
25+ palette.SurfaceVariant = tt.background
26+ palette.Text = tt.text
27+ css := GenerateCSS(palette)
28+
29+ assert.Contains(t, css, "--control-text: "+tt.want+";")
30+ for _, selector := range []string{
31+ ".stacked-pane-titlebar",
32+ ".stacked-pane-titlebar label",
33+ ".stacked-pane-titlebar image",
34+ } {
35+ block := regexp.MustCompile(regexp.QuoteMeta(selector) + `\s*\{[^}]*\}`).FindString(css)
36+ assert.Contains(t, block, "color: var(--control-text);", selector)
37+ }
38+
39+ // Hover and active states must not invalidate the computed contrast.
40+ stackedCSS := generateStackedPaneCSS(palette)
41+ assert.NotContains(t, stackedCSS, "shade(var(--surface-variant)")
42+ hover := regexp.MustCompile(`\.stacked-pane-titlebar\.stacked-pane-title-clickable:hover label\s*\{[^}]*\}`).FindString(css)
43+ assert.Contains(t, hover, "text-decoration-line: underline;")
44+ assert.NotContains(t, hover, "color:")
45+ assert.Contains(t, stackedCSS, "border-left: 0.1875em solid var(--accent);")
46+ })
47+ }
48+}
49+
11 func TestGenerateCSS_UsesGTKFontForNativeWidgets(t *testing.T) {50 func TestGenerateCSS_UsesGTKFontForNativeWidgets(t *testing.T) {
12 css := GenerateCSSWithScaleAndFonts(DefaultDarkPalette(), 1.0, FontConfig{51 css := GenerateCSSWithScaleAndFonts(DefaultDarkPalette(), 1.0, FontConfig{
13 SansFont: "Fira Sans",52 SansFont: "Fira Sans",

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 8

Tags 42

↑↓ moveenter openesc close