detached 3389f59

fix(theme): validate accent review boundaries

commit
3389f596fce81d339324734067dcc49950c442db
author
brice · · 2026-07-18 12:55 UTC
parent
aae2680
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +28 -2
M internal/adapters/config/theme_accent_test.go +6 -1
@@ -146,5 +146,10 @@ func TestWatchThemeAccentInvalidReloadFallsBackToAuto(t *testing.T) {
146146 }
147147
148148 cancel()
149- require.True(t, errors.Is(<-done, context.Canceled))
149+ select {
150+ case err := <-done:
151+ require.True(t, errors.Is(err, context.Canceled))
152+ case <-time.After(2 * time.Second):
153+ t.Fatal("timed out waiting for config watcher shutdown")
154+ }
150155 }
@@ -146,5 +146,10 @@ func TestWatchThemeAccentInvalidReloadFallsBackToAuto(t *testing.T) {
146 }146 }
147 147
148 cancel()148 cancel()
149- require.True(t, errors.Is(<-done, context.Canceled))149+ select {
150+ case err := <-done:
151+ require.True(t, errors.Is(err, context.Canceled))
152+ case <-time.After(2 * time.Second):
153+ t.Fatal("timed out waiting for config watcher shutdown")
154+ }
150 }155 }
M internal/usecase/theme/accent_resolve.go +4 -1
@@ -55,7 +55,10 @@ func ResolveAccent(t Theme, policy domain.ThemeAccent) Accent {
5555 }
5656
5757 func resolveExplicitAccent(t Theme, slot uint8) Accent {
58- if slot >= uint8(len(t.Palette)) || t.PaletteKnown&(uint16(1)<<slot) == 0 {
58+ if slot >= uint8(len(t.Palette)) {
59+ return Accent{}
60+ }
61+ if t.PaletteKnown&(uint16(1)<<slot) == 0 {
5962 // An explicit policy is a strict override. Even without OSC RGB for its
6063 // slot, it may still safely decorate foregrounds and borders by index.
6164 return indexedAccent(slot)
@@ -55,7 +55,10 @@ func ResolveAccent(t Theme, policy domain.ThemeAccent) Accent {
55 }55 }
56 56
57 func resolveExplicitAccent(t Theme, slot uint8) Accent {57 func resolveExplicitAccent(t Theme, slot uint8) Accent {
58- if slot >= uint8(len(t.Palette)) || t.PaletteKnown&(uint16(1)<<slot) == 0 {58+ if slot >= uint8(len(t.Palette)) {
59+ return Accent{}
60+ }
61+ if t.PaletteKnown&(uint16(1)<<slot) == 0 {
59 // An explicit policy is a strict override. Even without OSC RGB for its62 // An explicit policy is a strict override. Even without OSC RGB for its
60 // slot, it may still safely decorate foregrounds and borders by index.63 // slot, it may still safely decorate foregrounds and borders by index.
61 return indexedAccent(slot)64 return indexedAccent(slot)
M internal/usecase/theme/accent_resolve_test.go +18 -0
@@ -158,6 +158,24 @@ func TestResolveAccentExplicitSlot(t *testing.T) {
158158 policy: domain.ThemeAccent{Mode: domain.ThemeAccentSlot, Slot: 5},
159159 want: Accent{Slot: 5, IndexedOnly: true},
160160 },
161+ {
162+ name: "unknown final palette slot keeps its indexed decoration",
163+ theme: paletteTheme(map[int]renderer.RGB{2: teal, 10: teal, 4: blue}),
164+ policy: domain.ThemeAccent{Mode: domain.ThemeAccentSlot, Slot: 15},
165+ want: Accent{Slot: 15, IndexedOnly: true},
166+ },
167+ {
168+ name: "first slot outside palette is rejected",
169+ theme: paletteTheme(map[int]renderer.RGB{2: teal, 10: teal, 4: blue}),
170+ policy: domain.ThemeAccent{Mode: domain.ThemeAccentSlot, Slot: 16},
171+ want: Accent{},
172+ },
173+ {
174+ name: "maximum uint8 slot outside palette is rejected",
175+ theme: paletteTheme(map[int]renderer.RGB{2: teal, 10: teal, 4: blue}),
176+ policy: domain.ThemeAccent{Mode: domain.ThemeAccentSlot, Slot: 255},
177+ want: Accent{},
178+ },
161179 {
162180 name: "explicit known slot is indexed only without truecolor prerequisites",
163181 theme: func() Theme { t := paletteTheme(map[int]renderer.RGB{3: teal}); t.TrueColor = false; return t }(),
@@ -158,6 +158,24 @@ func TestResolveAccentExplicitSlot(t *testing.T) {
158 policy: domain.ThemeAccent{Mode: domain.ThemeAccentSlot, Slot: 5},158 policy: domain.ThemeAccent{Mode: domain.ThemeAccentSlot, Slot: 5},
159 want: Accent{Slot: 5, IndexedOnly: true},159 want: Accent{Slot: 5, IndexedOnly: true},
160 },160 },
161+ {
162+ name: "unknown final palette slot keeps its indexed decoration",
163+ theme: paletteTheme(map[int]renderer.RGB{2: teal, 10: teal, 4: blue}),
164+ policy: domain.ThemeAccent{Mode: domain.ThemeAccentSlot, Slot: 15},
165+ want: Accent{Slot: 15, IndexedOnly: true},
166+ },
167+ {
168+ name: "first slot outside palette is rejected",
169+ theme: paletteTheme(map[int]renderer.RGB{2: teal, 10: teal, 4: blue}),
170+ policy: domain.ThemeAccent{Mode: domain.ThemeAccentSlot, Slot: 16},
171+ want: Accent{},
172+ },
173+ {
174+ name: "maximum uint8 slot outside palette is rejected",
175+ theme: paletteTheme(map[int]renderer.RGB{2: teal, 10: teal, 4: blue}),
176+ policy: domain.ThemeAccent{Mode: domain.ThemeAccentSlot, Slot: 255},
177+ want: Accent{},
178+ },
161 {179 {
162 name: "explicit known slot is indexed only without truecolor prerequisites",180 name: "explicit known slot is indexed only without truecolor prerequisites",
163 theme: func() Theme { t := paletteTheme(map[int]renderer.RGB{3: teal}); t.TrueColor = false; return t }(),181 theme: func() Theme { t := paletteTheme(map[int]renderer.RGB{3: teal}); t.TrueColor = false; return t }(),

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