detached 6f77c42

fix: address copilot review (logger context, immutable chains)

commit
6f77c423aa4a6c1e82898fc4b3d161dec9923962
author
brice · · 2026-01-01 14:43 UTC
parent
4614c6c
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +45 -21
M internal/infrastructure/config/loader.go +8 -4
@@ -11,6 +11,7 @@ import (
1111
1212 "github.com/bnema/dumber/internal/application/port"
1313 "github.com/bnema/dumber/internal/infrastructure/fonts"
14+ "github.com/bnema/dumber/internal/logging"
1415 "github.com/spf13/viper"
1516 )
1617
@@ -322,7 +323,10 @@ func (m *Manager) createDefaultConfig() error {
322323 // detectAndSetFonts detects available system fonts and sets the best available
323324 // fonts from the fallback chains. This runs only during first-run config creation.
324325 func (m *Manager) detectAndSetFonts() {
325- ctx := context.Background()
326+ // Create context with logger for debugging first-run font detection.
327+ logger := logging.NewFromEnv()
328+ ctx := logging.WithContext(context.Background(), logger)
329+
326330 detector := fonts.NewDetector()
327331
328332 if !detector.IsAvailable(ctx) {
@@ -330,9 +334,9 @@ func (m *Manager) detectAndSetFonts() {
330334 return
331335 }
332336
333- sansFont := detector.SelectBestFont(ctx, port.FontCategorySansSerif, fonts.SansSerifFallbackChain)
334- serifFont := detector.SelectBestFont(ctx, port.FontCategorySerif, fonts.SerifFallbackChain)
335- monoFont := detector.SelectBestFont(ctx, port.FontCategoryMonospace, fonts.MonospaceFallbackChain)
337+ sansFont := detector.SelectBestFont(ctx, port.FontCategorySansSerif, fonts.SansSerifFallbackChain())
338+ serifFont := detector.SelectBestFont(ctx, port.FontCategorySerif, fonts.SerifFallbackChain())
339+ monoFont := detector.SelectBestFont(ctx, port.FontCategoryMonospace, fonts.MonospaceFallbackChain())
336340
337341 m.viper.Set("appearance.sans_font", sansFont)
338342 m.viper.Set("appearance.serif_font", serifFont)
@@ -11,6 +11,7 @@ import (
11 11
12 "github.com/bnema/dumber/internal/application/port"12 "github.com/bnema/dumber/internal/application/port"
13 "github.com/bnema/dumber/internal/infrastructure/fonts"13 "github.com/bnema/dumber/internal/infrastructure/fonts"
14+ "github.com/bnema/dumber/internal/logging"
14 "github.com/spf13/viper"15 "github.com/spf13/viper"
15 )16 )
16 17
@@ -322,7 +323,10 @@ func (m *Manager) createDefaultConfig() error {
322 // detectAndSetFonts detects available system fonts and sets the best available323 // detectAndSetFonts detects available system fonts and sets the best available
323 // fonts from the fallback chains. This runs only during first-run config creation.324 // fonts from the fallback chains. This runs only during first-run config creation.
324 func (m *Manager) detectAndSetFonts() {325 func (m *Manager) detectAndSetFonts() {
325- ctx := context.Background()326+ // Create context with logger for debugging first-run font detection.
327+ logger := logging.NewFromEnv()
328+ ctx := logging.WithContext(context.Background(), logger)
329+
326 detector := fonts.NewDetector()330 detector := fonts.NewDetector()
327 331
328 if !detector.IsAvailable(ctx) {332 if !detector.IsAvailable(ctx) {
@@ -330,9 +334,9 @@ func (m *Manager) detectAndSetFonts() {
330 return334 return
331 }335 }
332 336
333- sansFont := detector.SelectBestFont(ctx, port.FontCategorySansSerif, fonts.SansSerifFallbackChain)337+ sansFont := detector.SelectBestFont(ctx, port.FontCategorySansSerif, fonts.SansSerifFallbackChain())
334- serifFont := detector.SelectBestFont(ctx, port.FontCategorySerif, fonts.SerifFallbackChain)338+ serifFont := detector.SelectBestFont(ctx, port.FontCategorySerif, fonts.SerifFallbackChain())
335- monoFont := detector.SelectBestFont(ctx, port.FontCategoryMonospace, fonts.MonospaceFallbackChain)339+ monoFont := detector.SelectBestFont(ctx, port.FontCategoryMonospace, fonts.MonospaceFallbackChain())
336 340
337 m.viper.Set("appearance.sans_font", sansFont)341 m.viper.Set("appearance.sans_font", sansFont)
338 m.viper.Set("appearance.serif_font", serifFont)342 m.viper.Set("appearance.serif_font", serifFont)
M internal/infrastructure/fonts/detector.go +25 -5
@@ -11,10 +11,9 @@ import (
1111 "github.com/bnema/dumber/internal/logging"
1212 )
1313
14-// Fallback chains for each font category.
15-// The first available font in the chain will be selected.
14+// Fallback chains for each font category (unexported to prevent modification).
1615 var (
17- SansSerifFallbackChain = []string{
16+ sansSerifFallbackChain = []string{
1817 "Fira Sans",
1918 "Noto Sans",
2019 "DejaVu Sans",
@@ -22,14 +21,14 @@ var (
2221 "FreeSans",
2322 }
2423
25- SerifFallbackChain = []string{
24+ serifFallbackChain = []string{
2625 "Noto Serif",
2726 "DejaVu Serif",
2827 "Liberation Serif",
2928 "FreeSerif",
3029 }
3130
32- MonospaceFallbackChain = []string{
31+ monospaceFallbackChain = []string{
3332 "Fira Code",
3433 "JetBrains Mono",
3534 "Noto Sans Mono",
@@ -39,6 +38,27 @@ var (
3938 }
4039 )
4140
41+// SansSerifFallbackChain returns the fallback chain for sans-serif fonts.
42+func SansSerifFallbackChain() []string {
43+ result := make([]string, len(sansSerifFallbackChain))
44+ copy(result, sansSerifFallbackChain)
45+ return result
46+}
47+
48+// SerifFallbackChain returns the fallback chain for serif fonts.
49+func SerifFallbackChain() []string {
50+ result := make([]string, len(serifFallbackChain))
51+ copy(result, serifFallbackChain)
52+ return result
53+}
54+
55+// MonospaceFallbackChain returns the fallback chain for monospace fonts.
56+func MonospaceFallbackChain() []string {
57+ result := make([]string, len(monospaceFallbackChain))
58+ copy(result, monospaceFallbackChain)
59+ return result
60+}
61+
4262 // Generic CSS fallbacks when no fonts from the chain are found.
4363 const (
4464 genericSansSerif = "sans-serif"
@@ -11,10 +11,9 @@ import (
11 "github.com/bnema/dumber/internal/logging"11 "github.com/bnema/dumber/internal/logging"
12 )12 )
13 13
14-// Fallback chains for each font category.14+// Fallback chains for each font category (unexported to prevent modification).
15-// The first available font in the chain will be selected.
16 var (15 var (
17- SansSerifFallbackChain = []string{16+ sansSerifFallbackChain = []string{
18 "Fira Sans",17 "Fira Sans",
19 "Noto Sans",18 "Noto Sans",
20 "DejaVu Sans",19 "DejaVu Sans",
@@ -22,14 +21,14 @@ var (
22 "FreeSans",21 "FreeSans",
23 }22 }
24 23
25- SerifFallbackChain = []string{24+ serifFallbackChain = []string{
26 "Noto Serif",25 "Noto Serif",
27 "DejaVu Serif",26 "DejaVu Serif",
28 "Liberation Serif",27 "Liberation Serif",
29 "FreeSerif",28 "FreeSerif",
30 }29 }
31 30
32- MonospaceFallbackChain = []string{31+ monospaceFallbackChain = []string{
33 "Fira Code",32 "Fira Code",
34 "JetBrains Mono",33 "JetBrains Mono",
35 "Noto Sans Mono",34 "Noto Sans Mono",
@@ -39,6 +38,27 @@ var (
39 }38 }
40 )39 )
41 40
41+// SansSerifFallbackChain returns the fallback chain for sans-serif fonts.
42+func SansSerifFallbackChain() []string {
43+ result := make([]string, len(sansSerifFallbackChain))
44+ copy(result, sansSerifFallbackChain)
45+ return result
46+}
47+
48+// SerifFallbackChain returns the fallback chain for serif fonts.
49+func SerifFallbackChain() []string {
50+ result := make([]string, len(serifFallbackChain))
51+ copy(result, serifFallbackChain)
52+ return result
53+}
54+
55+// MonospaceFallbackChain returns the fallback chain for monospace fonts.
56+func MonospaceFallbackChain() []string {
57+ result := make([]string, len(monospaceFallbackChain))
58+ copy(result, monospaceFallbackChain)
59+ return result
60+}
61+
42 // Generic CSS fallbacks when no fonts from the chain are found.62 // Generic CSS fallbacks when no fonts from the chain are found.
43 const (63 const (
44 genericSansSerif = "sans-serif"64 genericSansSerif = "sans-serif"
M internal/infrastructure/fonts/detector_test.go +12 -12
@@ -67,12 +67,12 @@ func TestDetector_SelectBestFont_SansSerif(t *testing.T) {
6767 t.Skip("fc-list not available on this system")
6868 }
6969
70- font := detector.SelectBestFont(ctx, port.FontCategorySansSerif, SansSerifFallbackChain)
70+ font := detector.SelectBestFont(ctx, port.FontCategorySansSerif, SansSerifFallbackChain())
7171 assert.NotEmpty(t, font)
7272
7373 // Should either be a font from the chain or the generic fallback
7474 isFromChain := false
75- for _, f := range SansSerifFallbackChain {
75+ for _, f := range SansSerifFallbackChain() {
7676 if font == f {
7777 isFromChain = true
7878 break
@@ -93,11 +93,11 @@ func TestDetector_SelectBestFont_Serif(t *testing.T) {
9393 t.Skip("fc-list not available on this system")
9494 }
9595
96- font := detector.SelectBestFont(ctx, port.FontCategorySerif, SerifFallbackChain)
96+ font := detector.SelectBestFont(ctx, port.FontCategorySerif, SerifFallbackChain())
9797 assert.NotEmpty(t, font)
9898
9999 isFromChain := false
100- for _, f := range SerifFallbackChain {
100+ for _, f := range SerifFallbackChain() {
101101 if font == f {
102102 isFromChain = true
103103 break
@@ -118,11 +118,11 @@ func TestDetector_SelectBestFont_Monospace(t *testing.T) {
118118 t.Skip("fc-list not available on this system")
119119 }
120120
121- font := detector.SelectBestFont(ctx, port.FontCategoryMonospace, MonospaceFallbackChain)
121+ font := detector.SelectBestFont(ctx, port.FontCategoryMonospace, MonospaceFallbackChain())
122122 assert.NotEmpty(t, font)
123123
124124 isFromChain := false
125- for _, f := range MonospaceFallbackChain {
125+ for _, f := range MonospaceFallbackChain() {
126126 if font == f {
127127 isFromChain = true
128128 break
@@ -194,14 +194,14 @@ func TestDetector_genericFallback(t *testing.T) {
194194 }
195195
196196 func TestFallbackChains_NotEmpty(t *testing.T) {
197- assert.NotEmpty(t, SansSerifFallbackChain, "sans-serif fallback chain should not be empty")
198- assert.NotEmpty(t, SerifFallbackChain, "serif fallback chain should not be empty")
199- assert.NotEmpty(t, MonospaceFallbackChain, "monospace fallback chain should not be empty")
197+ assert.NotEmpty(t, SansSerifFallbackChain(), "sans-serif fallback chain should not be empty")
198+ assert.NotEmpty(t, SerifFallbackChain(), "serif fallback chain should not be empty")
199+ assert.NotEmpty(t, MonospaceFallbackChain(), "monospace fallback chain should not be empty")
200200 }
201201
202202 func TestFallbackChains_PreferredFontsFirst(t *testing.T) {
203203 // Verify preferred fonts are first in chain
204- assert.Equal(t, "Fira Sans", SansSerifFallbackChain[0], "Fira Sans should be first in sans-serif chain")
205- assert.Equal(t, "Noto Serif", SerifFallbackChain[0], "Noto Serif should be first in serif chain")
206- assert.Equal(t, "Fira Code", MonospaceFallbackChain[0], "Fira Code should be first in monospace chain")
204+ assert.Equal(t, "Fira Sans", SansSerifFallbackChain()[0], "Fira Sans should be first in sans-serif chain")
205+ assert.Equal(t, "Noto Serif", SerifFallbackChain()[0], "Noto Serif should be first in serif chain")
206+ assert.Equal(t, "Fira Code", MonospaceFallbackChain()[0], "Fira Code should be first in monospace chain")
207207 }
@@ -67,12 +67,12 @@ func TestDetector_SelectBestFont_SansSerif(t *testing.T) {
67 t.Skip("fc-list not available on this system")67 t.Skip("fc-list not available on this system")
68 }68 }
69 69
70- font := detector.SelectBestFont(ctx, port.FontCategorySansSerif, SansSerifFallbackChain)70+ font := detector.SelectBestFont(ctx, port.FontCategorySansSerif, SansSerifFallbackChain())
71 assert.NotEmpty(t, font)71 assert.NotEmpty(t, font)
72 72
73 // Should either be a font from the chain or the generic fallback73 // Should either be a font from the chain or the generic fallback
74 isFromChain := false74 isFromChain := false
75- for _, f := range SansSerifFallbackChain {75+ for _, f := range SansSerifFallbackChain() {
76 if font == f {76 if font == f {
77 isFromChain = true77 isFromChain = true
78 break78 break
@@ -93,11 +93,11 @@ func TestDetector_SelectBestFont_Serif(t *testing.T) {
93 t.Skip("fc-list not available on this system")93 t.Skip("fc-list not available on this system")
94 }94 }
95 95
96- font := detector.SelectBestFont(ctx, port.FontCategorySerif, SerifFallbackChain)96+ font := detector.SelectBestFont(ctx, port.FontCategorySerif, SerifFallbackChain())
97 assert.NotEmpty(t, font)97 assert.NotEmpty(t, font)
98 98
99 isFromChain := false99 isFromChain := false
100- for _, f := range SerifFallbackChain {100+ for _, f := range SerifFallbackChain() {
101 if font == f {101 if font == f {
102 isFromChain = true102 isFromChain = true
103 break103 break
@@ -118,11 +118,11 @@ func TestDetector_SelectBestFont_Monospace(t *testing.T) {
118 t.Skip("fc-list not available on this system")118 t.Skip("fc-list not available on this system")
119 }119 }
120 120
121- font := detector.SelectBestFont(ctx, port.FontCategoryMonospace, MonospaceFallbackChain)121+ font := detector.SelectBestFont(ctx, port.FontCategoryMonospace, MonospaceFallbackChain())
122 assert.NotEmpty(t, font)122 assert.NotEmpty(t, font)
123 123
124 isFromChain := false124 isFromChain := false
125- for _, f := range MonospaceFallbackChain {125+ for _, f := range MonospaceFallbackChain() {
126 if font == f {126 if font == f {
127 isFromChain = true127 isFromChain = true
128 break128 break
@@ -194,14 +194,14 @@ func TestDetector_genericFallback(t *testing.T) {
194 }194 }
195 195
196 func TestFallbackChains_NotEmpty(t *testing.T) {196 func TestFallbackChains_NotEmpty(t *testing.T) {
197- assert.NotEmpty(t, SansSerifFallbackChain, "sans-serif fallback chain should not be empty")197+ assert.NotEmpty(t, SansSerifFallbackChain(), "sans-serif fallback chain should not be empty")
198- assert.NotEmpty(t, SerifFallbackChain, "serif fallback chain should not be empty")198+ assert.NotEmpty(t, SerifFallbackChain(), "serif fallback chain should not be empty")
199- assert.NotEmpty(t, MonospaceFallbackChain, "monospace fallback chain should not be empty")199+ assert.NotEmpty(t, MonospaceFallbackChain(), "monospace fallback chain should not be empty")
200 }200 }
201 201
202 func TestFallbackChains_PreferredFontsFirst(t *testing.T) {202 func TestFallbackChains_PreferredFontsFirst(t *testing.T) {
203 // Verify preferred fonts are first in chain203 // Verify preferred fonts are first in chain
204- assert.Equal(t, "Fira Sans", SansSerifFallbackChain[0], "Fira Sans should be first in sans-serif chain")204+ assert.Equal(t, "Fira Sans", SansSerifFallbackChain()[0], "Fira Sans should be first in sans-serif chain")
205- assert.Equal(t, "Noto Serif", SerifFallbackChain[0], "Noto Serif should be first in serif chain")205+ assert.Equal(t, "Noto Serif", SerifFallbackChain()[0], "Noto Serif should be first in serif chain")
206- assert.Equal(t, "Fira Code", MonospaceFallbackChain[0], "Fira Code should be first in monospace chain")206+ assert.Equal(t, "Fira Code", MonospaceFallbackChain()[0], "Fira Code should be first in monospace chain")
207 }207 }

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