detached c927059

Merge pull request #254 from bnema/add-gtk-font-config

Add separate GTK UI font setting
commit
c927059650e31537fe80d9a67f205ae30ae383c7
author
bnema · · 2026-05-17 06:04 UTC
committer
GitHub · · 2026-05-17 06:04 UTC
merge of
7bebba6 + 08a0a9d · diff against the first parent
signature
GPG UnverifiedB5690EEEBB952194 The key is unknown
18 files · +364 -296
M internal/application/dto/webui_config.go +1 -0
@@ -70,6 +70,7 @@ type WebUIAppearanceConfig struct {
7070 SansFont string `json:"sans_font"`
7171 SerifFont string `json:"serif_font"`
7272 MonospaceFont string `json:"monospace_font"`
73+ GtkFont string `json:"gtk_font"`
7374 DefaultFontSize int `json:"default_font_size"`
7475 ColorScheme string `json:"color_scheme"`
7576 LightPalette ColorPalette `json:"light_palette"`
@@ -70,6 +70,7 @@ type WebUIAppearanceConfig struct {
70 SansFont string `json:"sans_font"`70 SansFont string `json:"sans_font"`
71 SerifFont string `json:"serif_font"`71 SerifFont string `json:"serif_font"`
72 MonospaceFont string `json:"monospace_font"`72 MonospaceFont string `json:"monospace_font"`
73+ GtkFont string `json:"gtk_font"`
73 DefaultFontSize int `json:"default_font_size"`74 DefaultFontSize int `json:"default_font_size"`
74 ColorScheme string `json:"color_scheme"`75 ColorScheme string `json:"color_scheme"`
75 LightPalette ColorPalette `json:"light_palette"`76 LightPalette ColorPalette `json:"light_palette"`
M internal/application/usecase/save_webui_config.go +2 -0
@@ -55,6 +55,7 @@ func normalizeWebUIConfig(cfg dto.WebUIConfig) dto.WebUIConfig {
5555 cfg.Appearance.SansFont = strings.TrimSpace(cfg.Appearance.SansFont)
5656 cfg.Appearance.SerifFont = strings.TrimSpace(cfg.Appearance.SerifFont)
5757 cfg.Appearance.MonospaceFont = strings.TrimSpace(cfg.Appearance.MonospaceFont)
58+ cfg.Appearance.GtkFont = strings.TrimSpace(cfg.Appearance.GtkFont)
5859 cfg.Appearance.ColorScheme = strings.TrimSpace(cfg.Appearance.ColorScheme)
5960 cfg.DefaultSearchEngine = strings.TrimSpace(cfg.DefaultSearchEngine)
6061 if len(cfg.SearchShortcuts) > 0 {
@@ -86,6 +87,7 @@ func validateWebUIConfig(cfg dto.WebUIConfig) error {
8687 errs = append(errs, validation.ValidateFontFamily("appearance.sans_font", cfg.Appearance.SansFont)...)
8788 errs = append(errs, validation.ValidateFontFamily("appearance.serif_font", cfg.Appearance.SerifFont)...)
8889 errs = append(errs, validation.ValidateFontFamily("appearance.monospace_font", cfg.Appearance.MonospaceFont)...)
90+ errs = append(errs, validation.ValidateFontFamily("appearance.gtk_font", cfg.Appearance.GtkFont)...)
8991
9092 for _, err := range validation.ValidateShortcutURL(cfg.DefaultSearchEngine) {
9193 errs = append(errs, fmt.Sprintf("default_search_engine: %s", err))
@@ -55,6 +55,7 @@ func normalizeWebUIConfig(cfg dto.WebUIConfig) dto.WebUIConfig {
55 cfg.Appearance.SansFont = strings.TrimSpace(cfg.Appearance.SansFont)55 cfg.Appearance.SansFont = strings.TrimSpace(cfg.Appearance.SansFont)
56 cfg.Appearance.SerifFont = strings.TrimSpace(cfg.Appearance.SerifFont)56 cfg.Appearance.SerifFont = strings.TrimSpace(cfg.Appearance.SerifFont)
57 cfg.Appearance.MonospaceFont = strings.TrimSpace(cfg.Appearance.MonospaceFont)57 cfg.Appearance.MonospaceFont = strings.TrimSpace(cfg.Appearance.MonospaceFont)
58+ cfg.Appearance.GtkFont = strings.TrimSpace(cfg.Appearance.GtkFont)
58 cfg.Appearance.ColorScheme = strings.TrimSpace(cfg.Appearance.ColorScheme)59 cfg.Appearance.ColorScheme = strings.TrimSpace(cfg.Appearance.ColorScheme)
59 cfg.DefaultSearchEngine = strings.TrimSpace(cfg.DefaultSearchEngine)60 cfg.DefaultSearchEngine = strings.TrimSpace(cfg.DefaultSearchEngine)
60 if len(cfg.SearchShortcuts) > 0 {61 if len(cfg.SearchShortcuts) > 0 {
@@ -86,6 +87,7 @@ func validateWebUIConfig(cfg dto.WebUIConfig) error {
86 errs = append(errs, validation.ValidateFontFamily("appearance.sans_font", cfg.Appearance.SansFont)...)87 errs = append(errs, validation.ValidateFontFamily("appearance.sans_font", cfg.Appearance.SansFont)...)
87 errs = append(errs, validation.ValidateFontFamily("appearance.serif_font", cfg.Appearance.SerifFont)...)88 errs = append(errs, validation.ValidateFontFamily("appearance.serif_font", cfg.Appearance.SerifFont)...)
88 errs = append(errs, validation.ValidateFontFamily("appearance.monospace_font", cfg.Appearance.MonospaceFont)...)89 errs = append(errs, validation.ValidateFontFamily("appearance.monospace_font", cfg.Appearance.MonospaceFont)...)
90+ errs = append(errs, validation.ValidateFontFamily("appearance.gtk_font", cfg.Appearance.GtkFont)...)
89 91
90 for _, err := range validation.ValidateShortcutURL(cfg.DefaultSearchEngine) {92 for _, err := range validation.ValidateShortcutURL(cfg.DefaultSearchEngine) {
91 errs = append(errs, fmt.Sprintf("default_search_engine: %s", err))93 errs = append(errs, fmt.Sprintf("default_search_engine: %s", err))
M internal/application/usecase/save_webui_config_test.go +1 -0
@@ -112,6 +112,7 @@ func validWebUIConfig() dto.WebUIConfig {
112112 SansFont: "Inter",
113113 SerifFont: "Georgia",
114114 MonospaceFont: "JetBrains Mono",
115+ GtkFont: "Adwaita Sans",
115116 DefaultFontSize: 16,
116117 ColorScheme: "prefer-dark",
117118 LightPalette: palette,
@@ -112,6 +112,7 @@ func validWebUIConfig() dto.WebUIConfig {
112 SansFont: "Inter",112 SansFont: "Inter",
113 SerifFont: "Georgia",113 SerifFont: "Georgia",
114 MonospaceFont: "JetBrains Mono",114 MonospaceFont: "JetBrains Mono",
115+ GtkFont: "Adwaita Sans",
115 DefaultFontSize: 16,116 DefaultFontSize: 16,
116 ColorScheme: "prefer-dark",117 ColorScheme: "prefer-dark",
117 LightPalette: palette,118 LightPalette: palette,
M internal/domain/entity/config_types.go +1 -0
@@ -9,6 +9,7 @@ type AppearanceConfig struct {
99 SansFont string `mapstructure:"sans_font" yaml:"sans_font" toml:"sans_font" json:"sans_font"`
1010 SerifFont string `mapstructure:"serif_font" yaml:"serif_font" toml:"serif_font" json:"serif_font"`
1111 MonospaceFont string `mapstructure:"monospace_font" yaml:"monospace_font" toml:"monospace_font" json:"monospace_font"`
12+ GtkFont string `mapstructure:"gtk_font" yaml:"gtk_font" toml:"gtk_font" json:"gtk_font"`
1213 DefaultFontSize int `mapstructure:"default_font_size" yaml:"default_font_size" toml:"default_font_size" json:"default_font_size"`
1314 LightPalette ColorPalette `mapstructure:"light_palette" yaml:"light_palette" toml:"light_palette" json:"light_palette"`
1415 DarkPalette ColorPalette `mapstructure:"dark_palette" yaml:"dark_palette" toml:"dark_palette" json:"dark_palette"`
@@ -9,6 +9,7 @@ type AppearanceConfig struct {
9 SansFont string `mapstructure:"sans_font" yaml:"sans_font" toml:"sans_font" json:"sans_font"`9 SansFont string `mapstructure:"sans_font" yaml:"sans_font" toml:"sans_font" json:"sans_font"`
10 SerifFont string `mapstructure:"serif_font" yaml:"serif_font" toml:"serif_font" json:"serif_font"`10 SerifFont string `mapstructure:"serif_font" yaml:"serif_font" toml:"serif_font" json:"serif_font"`
11 MonospaceFont string `mapstructure:"monospace_font" yaml:"monospace_font" toml:"monospace_font" json:"monospace_font"`11 MonospaceFont string `mapstructure:"monospace_font" yaml:"monospace_font" toml:"monospace_font" json:"monospace_font"`
12+ GtkFont string `mapstructure:"gtk_font" yaml:"gtk_font" toml:"gtk_font" json:"gtk_font"`
12 DefaultFontSize int `mapstructure:"default_font_size" yaml:"default_font_size" toml:"default_font_size" json:"default_font_size"`13 DefaultFontSize int `mapstructure:"default_font_size" yaml:"default_font_size" toml:"default_font_size" json:"default_font_size"`
13 LightPalette ColorPalette `mapstructure:"light_palette" yaml:"light_palette" toml:"light_palette" json:"light_palette"`14 LightPalette ColorPalette `mapstructure:"light_palette" yaml:"light_palette" toml:"light_palette" json:"light_palette"`
14 DarkPalette ColorPalette `mapstructure:"dark_palette" yaml:"dark_palette" toml:"dark_palette" json:"dark_palette"`15 DarkPalette ColorPalette `mapstructure:"dark_palette" yaml:"dark_palette" toml:"dark_palette" json:"dark_palette"`
M internal/infrastructure/config/defaults.go +1 -0
@@ -136,6 +136,7 @@ func DefaultConfig() *Config {
136136 SansFont: "Fira Sans",
137137 SerifFont: "Fira Sans",
138138 MonospaceFont: "Fira Code",
139+ GtkFont: "Adwaita Sans",
139140 DefaultFontSize: defaultFontSize,
140141 LightPalette: ColorPalette{
141142 Background: "#fafafa",
@@ -136,6 +136,7 @@ func DefaultConfig() *Config {
136 SansFont: "Fira Sans",136 SansFont: "Fira Sans",
137 SerifFont: "Fira Sans",137 SerifFont: "Fira Sans",
138 MonospaceFont: "Fira Code",138 MonospaceFont: "Fira Code",
139+ GtkFont: "Adwaita Sans",
139 DefaultFontSize: defaultFontSize,140 DefaultFontSize: defaultFontSize,
140 LightPalette: ColorPalette{141 LightPalette: ColorPalette{
141 Background: "#fafafa",142 Background: "#fafafa",
M internal/infrastructure/config/loader.go +1 -0
@@ -584,6 +584,7 @@ func (m *Manager) setAppearanceDefaults(defaults *Config) {
584584 m.viper.SetDefault("appearance.sans_font", defaults.Appearance.SansFont)
585585 m.viper.SetDefault("appearance.serif_font", defaults.Appearance.SerifFont)
586586 m.viper.SetDefault("appearance.monospace_font", defaults.Appearance.MonospaceFont)
587+ m.viper.SetDefault("appearance.gtk_font", defaults.Appearance.GtkFont)
587588 m.viper.SetDefault("appearance.default_font_size", defaults.Appearance.DefaultFontSize)
588589 m.viper.SetDefault("appearance.light_palette", defaults.Appearance.LightPalette)
589590 m.viper.SetDefault("appearance.dark_palette", defaults.Appearance.DarkPalette)
@@ -584,6 +584,7 @@ func (m *Manager) setAppearanceDefaults(defaults *Config) {
584 m.viper.SetDefault("appearance.sans_font", defaults.Appearance.SansFont)584 m.viper.SetDefault("appearance.sans_font", defaults.Appearance.SansFont)
585 m.viper.SetDefault("appearance.serif_font", defaults.Appearance.SerifFont)585 m.viper.SetDefault("appearance.serif_font", defaults.Appearance.SerifFont)
586 m.viper.SetDefault("appearance.monospace_font", defaults.Appearance.MonospaceFont)586 m.viper.SetDefault("appearance.monospace_font", defaults.Appearance.MonospaceFont)
587+ m.viper.SetDefault("appearance.gtk_font", defaults.Appearance.GtkFont)
587 m.viper.SetDefault("appearance.default_font_size", defaults.Appearance.DefaultFontSize)588 m.viper.SetDefault("appearance.default_font_size", defaults.Appearance.DefaultFontSize)
588 m.viper.SetDefault("appearance.light_palette", defaults.Appearance.LightPalette)589 m.viper.SetDefault("appearance.light_palette", defaults.Appearance.LightPalette)
589 m.viper.SetDefault("appearance.dark_palette", defaults.Appearance.DarkPalette)590 m.viper.SetDefault("appearance.dark_palette", defaults.Appearance.DarkPalette)
M internal/infrastructure/config/schema_provider.go +7 -0
@@ -138,6 +138,13 @@ func (*SchemaProvider) getAppearanceKeys(defaults *Config) []entity.ConfigKeyInf
138138 Description: "Monospace font family for code",
139139 Section: SectionAppearance,
140140 },
141+ {
142+ Key: "appearance.gtk_font",
143+ Type: "string",
144+ Default: defaults.Appearance.GtkFont,
145+ Description: "Native GTK UI font family for app chrome",
146+ Section: SectionAppearance,
147+ },
141148 {
142149 Key: "default_webpage_zoom",
143150 Type: "float64",
@@ -138,6 +138,13 @@ func (*SchemaProvider) getAppearanceKeys(defaults *Config) []entity.ConfigKeyInf
138 Description: "Monospace font family for code",138 Description: "Monospace font family for code",
139 Section: SectionAppearance,139 Section: SectionAppearance,
140 },140 },
141+ {
142+ Key: "appearance.gtk_font",
143+ Type: "string",
144+ Default: defaults.Appearance.GtkFont,
145+ Description: "Native GTK UI font family for app chrome",
146+ Section: SectionAppearance,
147+ },
141 {148 {
142 Key: "default_webpage_zoom",149 Key: "default_webpage_zoom",
143 Type: "float64",150 Type: "float64",
M internal/infrastructure/config/validation.go +1 -0
@@ -72,6 +72,7 @@ func validateAppearance(config *Config) []string {
7272 }
7373 validationErrors = append(validationErrors, domainvalidation.ValidateFontFamily("appearance.sans_font", config.Appearance.SansFont)...)
7474 validationErrors = append(validationErrors, domainvalidation.ValidateFontFamily("appearance.serif_font", config.Appearance.SerifFont)...)
75+ validationErrors = append(validationErrors, domainvalidation.ValidateFontFamily("appearance.gtk_font", config.Appearance.GtkFont)...)
7576 validationErrors = append(
7677 validationErrors,
7778 domainvalidation.ValidateFontFamily("appearance.monospace_font", config.Appearance.MonospaceFont)...,
@@ -72,6 +72,7 @@ func validateAppearance(config *Config) []string {
72 }72 }
73 validationErrors = append(validationErrors, domainvalidation.ValidateFontFamily("appearance.sans_font", config.Appearance.SansFont)...)73 validationErrors = append(validationErrors, domainvalidation.ValidateFontFamily("appearance.sans_font", config.Appearance.SansFont)...)
74 validationErrors = append(validationErrors, domainvalidation.ValidateFontFamily("appearance.serif_font", config.Appearance.SerifFont)...)74 validationErrors = append(validationErrors, domainvalidation.ValidateFontFamily("appearance.serif_font", config.Appearance.SerifFont)...)
75+ validationErrors = append(validationErrors, domainvalidation.ValidateFontFamily("appearance.gtk_font", config.Appearance.GtkFont)...)
75 validationErrors = append(76 validationErrors = append(
76 validationErrors,77 validationErrors,
77 domainvalidation.ValidateFontFamily("appearance.monospace_font", config.Appearance.MonospaceFont)...,78 domainvalidation.ValidateFontFamily("appearance.monospace_font", config.Appearance.MonospaceFont)...,
M internal/infrastructure/config/webui_gateway.go +1 -0
@@ -43,6 +43,7 @@ func (g *WebUIConfigGateway) SaveWebUIConfig(ctx context.Context, cfg dto.WebUIC
4343 current.Appearance.SansFont = cfg.Appearance.SansFont
4444 current.Appearance.SerifFont = cfg.Appearance.SerifFont
4545 current.Appearance.MonospaceFont = cfg.Appearance.MonospaceFont
46+ current.Appearance.GtkFont = cfg.Appearance.GtkFont
4647 current.Appearance.DefaultFontSize = cfg.Appearance.DefaultFontSize
4748 current.Appearance.ColorScheme = cfg.Appearance.ColorScheme
4849 current.Appearance.LightPalette = ColorPalette{
@@ -43,6 +43,7 @@ func (g *WebUIConfigGateway) SaveWebUIConfig(ctx context.Context, cfg dto.WebUIC
43 current.Appearance.SansFont = cfg.Appearance.SansFont43 current.Appearance.SansFont = cfg.Appearance.SansFont
44 current.Appearance.SerifFont = cfg.Appearance.SerifFont44 current.Appearance.SerifFont = cfg.Appearance.SerifFont
45 current.Appearance.MonospaceFont = cfg.Appearance.MonospaceFont45 current.Appearance.MonospaceFont = cfg.Appearance.MonospaceFont
46+ current.Appearance.GtkFont = cfg.Appearance.GtkFont
46 current.Appearance.DefaultFontSize = cfg.Appearance.DefaultFontSize47 current.Appearance.DefaultFontSize = cfg.Appearance.DefaultFontSize
47 current.Appearance.ColorScheme = cfg.Appearance.ColorScheme48 current.Appearance.ColorScheme = cfg.Appearance.ColorScheme
48 current.Appearance.LightPalette = ColorPalette{49 current.Appearance.LightPalette = ColorPalette{
M internal/infrastructure/config/webui_payload.go +1 -0
@@ -53,6 +53,7 @@ func buildSystemviewAppearancePayload(appearance AppearanceConfig) dto.WebUIAppe
5353 SansFont: appearance.SansFont,
5454 SerifFont: appearance.SerifFont,
5555 MonospaceFont: appearance.MonospaceFont,
56+ GtkFont: appearance.GtkFont,
5657 DefaultFontSize: appearance.DefaultFontSize,
5758 ColorScheme: appearance.ColorScheme,
5859 LightPalette: buildSystemviewPalettePayload(appearance.LightPalette, defaults.LightPalette),
@@ -53,6 +53,7 @@ func buildSystemviewAppearancePayload(appearance AppearanceConfig) dto.WebUIAppe
53 SansFont: appearance.SansFont,53 SansFont: appearance.SansFont,
54 SerifFont: appearance.SerifFont,54 SerifFont: appearance.SerifFont,
55 MonospaceFont: appearance.MonospaceFont,55 MonospaceFont: appearance.MonospaceFont,
56+ GtkFont: appearance.GtkFont,
56 DefaultFontSize: appearance.DefaultFontSize,57 DefaultFontSize: appearance.DefaultFontSize,
57 ColorScheme: appearance.ColorScheme,58 ColorScheme: appearance.ColorScheme,
58 LightPalette: buildSystemviewPalettePayload(appearance.LightPalette, defaults.LightPalette),59 LightPalette: buildSystemviewPalettePayload(appearance.LightPalette, defaults.LightPalette),
M internal/ui/systemviews/config.templ +1 -0
@@ -31,6 +31,7 @@ templ AppearanceForm(config dto.SystemviewConfigPayload) {
3131 <label><span>Sans font</span><input name="sans_font" type="text" value={ config.Appearance.SansFont } required/></label>
3232 <label><span>Serif font</span><input name="serif_font" type="text" value={ config.Appearance.SerifFont } required/></label>
3333 <label><span>Monospace font</span><input name="monospace_font" type="text" value={ config.Appearance.MonospaceFont } required/></label>
34+ <label><span>GTK UI font</span><input name="gtk_font" type="text" value={ config.Appearance.GtkFont } required/></label>
3435 <label><span>Default font size</span><input name="default_font_size" type="number" min="1" max="72" value={ formatConfigInt(config.Appearance.DefaultFontSize) } required/></label>
3536 <label><span>UI scale</span><input name="default_ui_scale" type="number" min="0.5" max="3" step="0.05" value={ formatConfigFloat(config.DefaultUIScale) } required/></label>
3637 <label><span>Color scheme</span><select name="color_scheme">
@@ -31,6 +31,7 @@ templ AppearanceForm(config dto.SystemviewConfigPayload) {
31 <label><span>Sans font</span><input name="sans_font" type="text" value={ config.Appearance.SansFont } required/></label>31 <label><span>Sans font</span><input name="sans_font" type="text" value={ config.Appearance.SansFont } required/></label>
32 <label><span>Serif font</span><input name="serif_font" type="text" value={ config.Appearance.SerifFont } required/></label>32 <label><span>Serif font</span><input name="serif_font" type="text" value={ config.Appearance.SerifFont } required/></label>
33 <label><span>Monospace font</span><input name="monospace_font" type="text" value={ config.Appearance.MonospaceFont } required/></label>33 <label><span>Monospace font</span><input name="monospace_font" type="text" value={ config.Appearance.MonospaceFont } required/></label>
34+ <label><span>GTK UI font</span><input name="gtk_font" type="text" value={ config.Appearance.GtkFont } required/></label>
34 <label><span>Default font size</span><input name="default_font_size" type="number" min="1" max="72" value={ formatConfigInt(config.Appearance.DefaultFontSize) } required/></label>35 <label><span>Default font size</span><input name="default_font_size" type="number" min="1" max="72" value={ formatConfigInt(config.Appearance.DefaultFontSize) } required/></label>
35 <label><span>UI scale</span><input name="default_ui_scale" type="number" min="0.5" max="3" step="0.05" value={ formatConfigFloat(config.DefaultUIScale) } required/></label>36 <label><span>UI scale</span><input name="default_ui_scale" type="number" min="0.5" max="3" step="0.05" value={ formatConfigFloat(config.DefaultUIScale) } required/></label>
36 <label><span>Color scheme</span><select name="color_scheme">37 <label><span>Color scheme</span><select name="color_scheme">
M internal/ui/systemviews/config_actions.go +1 -0
@@ -99,6 +99,7 @@ func (a *App) saveAppearanceConfig(ctx context.Context, data map[string]string)
9999 cfg.Appearance.SansFont = strings.TrimSpace(data["sans_font"])
100100 cfg.Appearance.SerifFont = strings.TrimSpace(data["serif_font"])
101101 cfg.Appearance.MonospaceFont = strings.TrimSpace(data["monospace_font"])
102+ cfg.Appearance.GtkFont = strings.TrimSpace(data["gtk_font"])
102103 cfg.Appearance.DefaultFontSize = fontSize
103104 cfg.Appearance.ColorScheme = strings.TrimSpace(data["color_scheme"])
104105 cfg.Appearance.LightPalette = paletteFromForm(data, "light")
@@ -99,6 +99,7 @@ func (a *App) saveAppearanceConfig(ctx context.Context, data map[string]string)
99 cfg.Appearance.SansFont = strings.TrimSpace(data["sans_font"])99 cfg.Appearance.SansFont = strings.TrimSpace(data["sans_font"])
100 cfg.Appearance.SerifFont = strings.TrimSpace(data["serif_font"])100 cfg.Appearance.SerifFont = strings.TrimSpace(data["serif_font"])
101 cfg.Appearance.MonospaceFont = strings.TrimSpace(data["monospace_font"])101 cfg.Appearance.MonospaceFont = strings.TrimSpace(data["monospace_font"])
102+ cfg.Appearance.GtkFont = strings.TrimSpace(data["gtk_font"])
102 cfg.Appearance.DefaultFontSize = fontSize103 cfg.Appearance.DefaultFontSize = fontSize
103 cfg.Appearance.ColorScheme = strings.TrimSpace(data["color_scheme"])104 cfg.Appearance.ColorScheme = strings.TrimSpace(data["color_scheme"])
104 cfg.Appearance.LightPalette = paletteFromForm(data, "light")105 cfg.Appearance.LightPalette = paletteFromForm(data, "light")
M internal/ui/systemviews/config_templ.go +304 -291 generated file: collapsed
M internal/ui/systemviews/theme.go +1 -0
@@ -184,6 +184,7 @@ func isZeroAppearance(appearance dto.WebUIAppearanceConfig) bool {
184184 appearance.SansFont == "" &&
185185 appearance.SerifFont == "" &&
186186 appearance.MonospaceFont == "" &&
187+ appearance.GtkFont == "" &&
187188 appearance.DefaultFontSize == 0 &&
188189 appearance.LightPalette == (dto.ColorPalette{}) &&
189190 appearance.DarkPalette == (dto.ColorPalette{})
@@ -184,6 +184,7 @@ func isZeroAppearance(appearance dto.WebUIAppearanceConfig) bool {
184 appearance.SansFont == "" &&184 appearance.SansFont == "" &&
185 appearance.SerifFont == "" &&185 appearance.SerifFont == "" &&
186 appearance.MonospaceFont == "" &&186 appearance.MonospaceFont == "" &&
187+ appearance.GtkFont == "" &&
187 appearance.DefaultFontSize == 0 &&188 appearance.DefaultFontSize == 0 &&
188 appearance.LightPalette == (dto.ColorPalette{}) &&189 appearance.LightPalette == (dto.ColorPalette{}) &&
189 appearance.DarkPalette == (dto.ColorPalette{})190 appearance.DarkPalette == (dto.ColorPalette{})
M internal/ui/theme/css.go +5 -1
@@ -21,6 +21,7 @@ func GenerateCSSWithScale(p Palette, scale float64) string {
2121 type FontConfig struct {
2222 SansFont string
2323 MonospaceFont string
24+ GtkFont string
2425 }
2526
2627 // DefaultFontConfig returns safe font fallbacks.
@@ -28,6 +29,7 @@ func DefaultFontConfig() FontConfig {
2829 return FontConfig{
2930 SansFont: "sans-serif",
3031 MonospaceFont: "monospace",
32+ GtkFont: DefaultGTKFont(),
3133 }
3234 }
3335
@@ -37,13 +39,14 @@ func FontCSSVars(fonts FontConfig) string {
3739 // Quote the configured family; include generic fallback.
3840 fmt.Fprintf(&sb, " --font-sans: %q, sans-serif;\n", fonts.SansFont)
3941 fmt.Fprintf(&sb, " --font-mono: %q, monospace;\n", fonts.MonospaceFont)
42+ fmt.Fprintf(&sb, " --font-gtk: %q, sans-serif;\n", fonts.GtkFont)
4043 return sb.String()
4144 }
4245
4346 func generateFontCSS() string {
4447 return `/* Font styling */
4548 window, tooltip, popover {
46- font-family: var(--font-sans);
49+ font-family: var(--font-gtk);
4750 }
4851 `
4952 }
@@ -62,6 +65,7 @@ func GenerateCSSFull(p Palette, _ float64, fonts FontConfig, modeColors ModeColo
6265 fonts = FontConfig{
6366 SansFont: Coalesce(fonts.SansFont, defaults.SansFont),
6467 MonospaceFont: Coalesce(fonts.MonospaceFont, defaults.MonospaceFont),
68+ GtkFont: Coalesce(fonts.GtkFont, defaults.GtkFont),
6569 }
6670
6771 var sb strings.Builder
@@ -21,6 +21,7 @@ func GenerateCSSWithScale(p Palette, scale float64) string {
21 type FontConfig struct {21 type FontConfig struct {
22 SansFont string22 SansFont string
23 MonospaceFont string23 MonospaceFont string
24+ GtkFont string
24 }25 }
25 26
26 // DefaultFontConfig returns safe font fallbacks.27 // DefaultFontConfig returns safe font fallbacks.
@@ -28,6 +29,7 @@ func DefaultFontConfig() FontConfig {
28 return FontConfig{29 return FontConfig{
29 SansFont: "sans-serif",30 SansFont: "sans-serif",
30 MonospaceFont: "monospace",31 MonospaceFont: "monospace",
32+ GtkFont: DefaultGTKFont(),
31 }33 }
32 }34 }
33 35
@@ -37,13 +39,14 @@ func FontCSSVars(fonts FontConfig) string {
37 // Quote the configured family; include generic fallback.39 // Quote the configured family; include generic fallback.
38 fmt.Fprintf(&sb, " --font-sans: %q, sans-serif;\n", fonts.SansFont)40 fmt.Fprintf(&sb, " --font-sans: %q, sans-serif;\n", fonts.SansFont)
39 fmt.Fprintf(&sb, " --font-mono: %q, monospace;\n", fonts.MonospaceFont)41 fmt.Fprintf(&sb, " --font-mono: %q, monospace;\n", fonts.MonospaceFont)
42+ fmt.Fprintf(&sb, " --font-gtk: %q, sans-serif;\n", fonts.GtkFont)
40 return sb.String()43 return sb.String()
41 }44 }
42 45
43 func generateFontCSS() string {46 func generateFontCSS() string {
44 return `/* Font styling */47 return `/* Font styling */
45 window, tooltip, popover {48 window, tooltip, popover {
46- font-family: var(--font-sans);49+ font-family: var(--font-gtk);
47 }50 }
48 `51 `
49 }52 }
@@ -62,6 +65,7 @@ func GenerateCSSFull(p Palette, _ float64, fonts FontConfig, modeColors ModeColo
62 fonts = FontConfig{65 fonts = FontConfig{
63 SansFont: Coalesce(fonts.SansFont, defaults.SansFont),66 SansFont: Coalesce(fonts.SansFont, defaults.SansFont),
64 MonospaceFont: Coalesce(fonts.MonospaceFont, defaults.MonospaceFont),67 MonospaceFont: Coalesce(fonts.MonospaceFont, defaults.MonospaceFont),
68+ GtkFont: Coalesce(fonts.GtkFont, defaults.GtkFont),
65 }69 }
66 70
67 var sb strings.Builder71 var sb strings.Builder
M internal/ui/theme/css_test.go +14 -0
@@ -8,6 +8,20 @@ import (
88 "github.com/stretchr/testify/assert"
99 )
1010
11+func TestGenerateCSS_UsesGTKFontForNativeWidgets(t *testing.T) {
12+ css := GenerateCSSWithScaleAndFonts(DefaultDarkPalette(), 1.0, FontConfig{
13+ SansFont: "Fira Sans",
14+ MonospaceFont: "Fira Code",
15+ GtkFont: "Adwaita Sans",
16+ })
17+
18+ assert.Contains(t, css, `--font-sans: "Fira Sans", sans-serif;`)
19+ assert.Contains(t, css, `--font-mono: "Fira Code", monospace;`)
20+ assert.Contains(t, css, `--font-gtk: "Adwaita Sans", sans-serif;`)
21+ assert.Contains(t, css, "window, tooltip, popover {\n\tfont-family: var(--font-gtk);\n}")
22+ assert.NotContains(t, css, "window, tooltip, popover {\n\tfont-family: var(--font-sans);\n}")
23+}
24+
1125 func TestGenerateCSS_StandaloneOmniboxWindowUsesTransparentBackground(t *testing.T) {
1226 css := GenerateCSS(DefaultDarkPalette())
1327
@@ -8,6 +8,20 @@ import (
8 "github.com/stretchr/testify/assert"8 "github.com/stretchr/testify/assert"
9 )9 )
10 10
11+func TestGenerateCSS_UsesGTKFontForNativeWidgets(t *testing.T) {
12+ css := GenerateCSSWithScaleAndFonts(DefaultDarkPalette(), 1.0, FontConfig{
13+ SansFont: "Fira Sans",
14+ MonospaceFont: "Fira Code",
15+ GtkFont: "Adwaita Sans",
16+ })
17+
18+ assert.Contains(t, css, `--font-sans: "Fira Sans", sans-serif;`)
19+ assert.Contains(t, css, `--font-mono: "Fira Code", monospace;`)
20+ assert.Contains(t, css, `--font-gtk: "Adwaita Sans", sans-serif;`)
21+ assert.Contains(t, css, "window, tooltip, popover {\n\tfont-family: var(--font-gtk);\n}")
22+ assert.NotContains(t, css, "window, tooltip, popover {\n\tfont-family: var(--font-sans);\n}")
23+}
24+
11 func TestGenerateCSS_StandaloneOmniboxWindowUsesTransparentBackground(t *testing.T) {25 func TestGenerateCSS_StandaloneOmniboxWindowUsesTransparentBackground(t *testing.T) {
12 css := GenerateCSS(DefaultDarkPalette())26 css := GenerateCSS(DefaultDarkPalette())
13 27
M internal/ui/theme/manager.go +13 -2
@@ -21,6 +21,7 @@ type Manager struct {
2121 darkPalette Palette // Dark theme colors
2222 uiScale float64 // UI scaling factor (1.0 = 100%)
2323 fonts FontConfig
24+ gtkFont string
2425 modeColors ModeColors // Modal mode indicator colors
2526 cssProvider *gtk.CssProvider
2627 appliedFont string
@@ -68,9 +69,12 @@ func NewManager(
6869 }
6970
7071 fonts := DefaultFontConfig()
72+ gtkFont := DefaultGTKFont()
7173 if appearance != nil {
7274 fonts.SansFont = Coalesce(appearance.SansFont, fonts.SansFont)
7375 fonts.MonospaceFont = Coalesce(appearance.MonospaceFont, fonts.MonospaceFont)
76+ gtkFont = Coalesce(appearance.GtkFont, gtkFont)
77+ fonts.GtkFont = gtkFont
7478 }
7579
7680 // Build mode colors from config
@@ -82,6 +86,7 @@ func NewManager(
8286 Float64("ui_scale", uiScale).
8387 Str("sans_font", fonts.SansFont).
8488 Str("monospace_font", fonts.MonospaceFont).
89+ Str("gtk_font", gtkFont).
8590 Msg("theme manager initialized")
8691
8792 return &Manager{
@@ -91,6 +96,7 @@ func NewManager(
9196 darkPalette: darkPalette,
9297 uiScale: uiScale,
9398 fonts: fonts,
99+ gtkFont: gtkFont,
94100 modeColors: modeColors,
95101 colorResolver: resolver,
96102 }
@@ -155,10 +161,12 @@ func (m *Manager) GetWebUIThemeCSS() string {
155161
156162 const gtkDefaultFontPointSize = 11
157163
164+func DefaultGTKFont() string { return "Adwaita Sans" }
165+
158166 func formatGTKFontName(family string, uiScale float64) string {
159167 family = strings.TrimSpace(family)
160168 if family == "" {
161- family = DefaultFontConfig().SansFont
169+ family = DefaultGTKFont()
162170 }
163171 if uiScale <= 0 {
164172 uiScale = 1.0
@@ -186,7 +194,7 @@ func (m *Manager) ApplyToDisplay(ctx context.Context, display *gdk.Display) {
186194 // Generate CSS with current palette, UI scale, fonts, and mode colors
187195 palette := m.GetCurrentPalette()
188196 css := GenerateCSSFull(palette, m.uiScale, m.fonts, m.modeColors)
189- fontName := formatGTKFontName(m.fonts.SansFont, m.uiScale)
197+ fontName := formatGTKFontName(m.gtkFont, m.uiScale)
190198
191199 settings := gtk.SettingsGetForDisplay(display)
192200 if settings == nil {
@@ -279,9 +287,11 @@ func (m *Manager) UpdateFromConfig(
279287 }
280288
281289 defaults := DefaultFontConfig()
290+ m.gtkFont = Coalesce(appearance.GtkFont, DefaultGTKFont())
282291 m.fonts = FontConfig{
283292 SansFont: Coalesce(appearance.SansFont, defaults.SansFont),
284293 MonospaceFont: Coalesce(appearance.MonospaceFont, defaults.MonospaceFont),
294+ GtkFont: m.gtkFont,
285295 }
286296
287297 // Update mode colors
@@ -295,6 +305,7 @@ func (m *Manager) UpdateFromConfig(
295305 Float64("ui_scale", m.uiScale).
296306 Str("sans_font", m.fonts.SansFont).
297307 Str("monospace_font", m.fonts.MonospaceFont).
308+ Str("gtk_font", m.gtkFont).
298309 Msg("theme manager updated from config")
299310
300311 // Re-apply CSS if display is available
@@ -21,6 +21,7 @@ type Manager struct {
21 darkPalette Palette // Dark theme colors21 darkPalette Palette // Dark theme colors
22 uiScale float64 // UI scaling factor (1.0 = 100%)22 uiScale float64 // UI scaling factor (1.0 = 100%)
23 fonts FontConfig23 fonts FontConfig
24+ gtkFont string
24 modeColors ModeColors // Modal mode indicator colors25 modeColors ModeColors // Modal mode indicator colors
25 cssProvider *gtk.CssProvider26 cssProvider *gtk.CssProvider
26 appliedFont string27 appliedFont string
@@ -68,9 +69,12 @@ func NewManager(
68 }69 }
69 70
70 fonts := DefaultFontConfig()71 fonts := DefaultFontConfig()
72+ gtkFont := DefaultGTKFont()
71 if appearance != nil {73 if appearance != nil {
72 fonts.SansFont = Coalesce(appearance.SansFont, fonts.SansFont)74 fonts.SansFont = Coalesce(appearance.SansFont, fonts.SansFont)
73 fonts.MonospaceFont = Coalesce(appearance.MonospaceFont, fonts.MonospaceFont)75 fonts.MonospaceFont = Coalesce(appearance.MonospaceFont, fonts.MonospaceFont)
76+ gtkFont = Coalesce(appearance.GtkFont, gtkFont)
77+ fonts.GtkFont = gtkFont
74 }78 }
75 79
76 // Build mode colors from config80 // Build mode colors from config
@@ -82,6 +86,7 @@ func NewManager(
82 Float64("ui_scale", uiScale).86 Float64("ui_scale", uiScale).
83 Str("sans_font", fonts.SansFont).87 Str("sans_font", fonts.SansFont).
84 Str("monospace_font", fonts.MonospaceFont).88 Str("monospace_font", fonts.MonospaceFont).
89+ Str("gtk_font", gtkFont).
85 Msg("theme manager initialized")90 Msg("theme manager initialized")
86 91
87 return &Manager{92 return &Manager{
@@ -91,6 +96,7 @@ func NewManager(
91 darkPalette: darkPalette,96 darkPalette: darkPalette,
92 uiScale: uiScale,97 uiScale: uiScale,
93 fonts: fonts,98 fonts: fonts,
99+ gtkFont: gtkFont,
94 modeColors: modeColors,100 modeColors: modeColors,
95 colorResolver: resolver,101 colorResolver: resolver,
96 }102 }
@@ -155,10 +161,12 @@ func (m *Manager) GetWebUIThemeCSS() string {
155 161
156 const gtkDefaultFontPointSize = 11162 const gtkDefaultFontPointSize = 11
157 163
164+func DefaultGTKFont() string { return "Adwaita Sans" }
165+
158 func formatGTKFontName(family string, uiScale float64) string {166 func formatGTKFontName(family string, uiScale float64) string {
159 family = strings.TrimSpace(family)167 family = strings.TrimSpace(family)
160 if family == "" {168 if family == "" {
161- family = DefaultFontConfig().SansFont169+ family = DefaultGTKFont()
162 }170 }
163 if uiScale <= 0 {171 if uiScale <= 0 {
164 uiScale = 1.0172 uiScale = 1.0
@@ -186,7 +194,7 @@ func (m *Manager) ApplyToDisplay(ctx context.Context, display *gdk.Display) {
186 // Generate CSS with current palette, UI scale, fonts, and mode colors194 // Generate CSS with current palette, UI scale, fonts, and mode colors
187 palette := m.GetCurrentPalette()195 palette := m.GetCurrentPalette()
188 css := GenerateCSSFull(palette, m.uiScale, m.fonts, m.modeColors)196 css := GenerateCSSFull(palette, m.uiScale, m.fonts, m.modeColors)
189- fontName := formatGTKFontName(m.fonts.SansFont, m.uiScale)197+ fontName := formatGTKFontName(m.gtkFont, m.uiScale)
190 198
191 settings := gtk.SettingsGetForDisplay(display)199 settings := gtk.SettingsGetForDisplay(display)
192 if settings == nil {200 if settings == nil {
@@ -279,9 +287,11 @@ func (m *Manager) UpdateFromConfig(
279 }287 }
280 288
281 defaults := DefaultFontConfig()289 defaults := DefaultFontConfig()
290+ m.gtkFont = Coalesce(appearance.GtkFont, DefaultGTKFont())
282 m.fonts = FontConfig{291 m.fonts = FontConfig{
283 SansFont: Coalesce(appearance.SansFont, defaults.SansFont),292 SansFont: Coalesce(appearance.SansFont, defaults.SansFont),
284 MonospaceFont: Coalesce(appearance.MonospaceFont, defaults.MonospaceFont),293 MonospaceFont: Coalesce(appearance.MonospaceFont, defaults.MonospaceFont),
294+ GtkFont: m.gtkFont,
285 }295 }
286 296
287 // Update mode colors297 // Update mode colors
@@ -295,6 +305,7 @@ func (m *Manager) UpdateFromConfig(
295 Float64("ui_scale", m.uiScale).305 Float64("ui_scale", m.uiScale).
296 Str("sans_font", m.fonts.SansFont).306 Str("sans_font", m.fonts.SansFont).
297 Str("monospace_font", m.fonts.MonospaceFont).307 Str("monospace_font", m.fonts.MonospaceFont).
308+ Str("gtk_font", m.gtkFont).
298 Msg("theme manager updated from config")309 Msg("theme manager updated from config")
299 310
300 // Re-apply CSS if display is available311 // Re-apply CSS if display is available
M internal/ui/theme/manager_test.go +8 -2
@@ -276,6 +276,7 @@ func TestManager_Fonts(t *testing.T) {
276276 appearance := &entity.AppearanceConfig{
277277 SansFont: "Inter",
278278 MonospaceFont: "JetBrains Mono",
279+ GtkFont: "Adwaita Sans",
279280 }
280281
281282 mockResolver := mocks.NewMockColorSchemeResolver(t)
@@ -314,13 +315,18 @@ func TestManager_ModeColors(t *testing.T) {
314315 assert.Equal(t, "#ffff00", modeColors.ResizeMode)
315316 }
316317
317-func TestFormatGTKFontName_UsesScaledConfiguredSansFont(t *testing.T) {
318- assert.Equal(t, "Fira Sans 14", formatGTKFontName("Fira Sans", 1.3))
318+func TestDefaultGTKFont(t *testing.T) {
319+ assert.Equal(t, "Adwaita Sans", DefaultGTKFont())
320+}
321+
322+func TestFormatGTKFontName_UsesScaledConfiguredGTKFont(t *testing.T) {
323+ assert.Equal(t, "Adwaita Sans 14", formatGTKFontName("Adwaita Sans", 1.3))
319324 }
320325
321326 func TestFormatGTKFontName_DefaultsScaleAndRoundsToNearestPoint(t *testing.T) {
322327 assert.Equal(t, "Inter 11", formatGTKFontName("Inter", 0))
323328 assert.Equal(t, "Inter 12", formatGTKFontName("Inter", 1.05))
329+ assert.Equal(t, "Adwaita Sans 11", formatGTKFontName("", 1.0))
324330 }
325331
326332 func TestShouldApplyGTKFontName(t *testing.T) {
@@ -276,6 +276,7 @@ func TestManager_Fonts(t *testing.T) {
276 appearance := &entity.AppearanceConfig{276 appearance := &entity.AppearanceConfig{
277 SansFont: "Inter",277 SansFont: "Inter",
278 MonospaceFont: "JetBrains Mono",278 MonospaceFont: "JetBrains Mono",
279+ GtkFont: "Adwaita Sans",
279 }280 }
280 281
281 mockResolver := mocks.NewMockColorSchemeResolver(t)282 mockResolver := mocks.NewMockColorSchemeResolver(t)
@@ -314,13 +315,18 @@ func TestManager_ModeColors(t *testing.T) {
314 assert.Equal(t, "#ffff00", modeColors.ResizeMode)315 assert.Equal(t, "#ffff00", modeColors.ResizeMode)
315 }316 }
316 317
317-func TestFormatGTKFontName_UsesScaledConfiguredSansFont(t *testing.T) {318+func TestDefaultGTKFont(t *testing.T) {
318- assert.Equal(t, "Fira Sans 14", formatGTKFontName("Fira Sans", 1.3))319+ assert.Equal(t, "Adwaita Sans", DefaultGTKFont())
320+}
321+
322+func TestFormatGTKFontName_UsesScaledConfiguredGTKFont(t *testing.T) {
323+ assert.Equal(t, "Adwaita Sans 14", formatGTKFontName("Adwaita Sans", 1.3))
319 }324 }
320 325
321 func TestFormatGTKFontName_DefaultsScaleAndRoundsToNearestPoint(t *testing.T) {326 func TestFormatGTKFontName_DefaultsScaleAndRoundsToNearestPoint(t *testing.T) {
322 assert.Equal(t, "Inter 11", formatGTKFontName("Inter", 0))327 assert.Equal(t, "Inter 11", formatGTKFontName("Inter", 0))
323 assert.Equal(t, "Inter 12", formatGTKFontName("Inter", 1.05))328 assert.Equal(t, "Inter 12", formatGTKFontName("Inter", 1.05))
329+ assert.Equal(t, "Adwaita Sans 11", formatGTKFontName("", 1.0))
324 }330 }
325 331
326 func TestShouldApplyGTKFontName(t *testing.T) {332 func TestShouldApplyGTKFontName(t *testing.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 8

Tags 42

↑↓ moveenter openesc close