detached 737b447

feat(browser): add persistence config and appearance injection

- Pass DataDir/CacheDir to webkit.Config in buildWebkitConfig
- Add exposeAppearanceConfig to inject color palettes and fonts
- Add appearance-config.ts module for TypeScript GUI integration
commit
737b447db67baee27918da05298fa7a60b88ebc4
author
bamen · · 2025-10-08 16:10 UTC
committer
bamen · · 2025-10-08 16:01 UTC
parent
8d33e03
signature
unsigned
2 files · +198 -4
A gui/src/injected/modules/appearance-config.ts +133 -0
@@ -0,0 +1,133 @@
1+/**
2+ * Appearance Configuration Module
3+ *
4+ * Handles application appearance configuration including color palettes and fonts.
5+ * This config is injected by the native layer at startup and made available to all
6+ * injected modules and components.
7+ */
8+
9+export interface ColorPalette {
10+ background: string;
11+ surface: string;
12+ surfaceVariant: string;
13+ text: string;
14+ muted: string;
15+ accent: string;
16+ border: string;
17+}
18+
19+export interface AppearanceConfig {
20+ theme: "light" | "dark";
21+ palettes: {
22+ light: ColorPalette;
23+ dark: ColorPalette;
24+ };
25+ fonts: {
26+ sans: string;
27+ serif: string;
28+ monospace: string;
29+ defaultSize: number;
30+ };
31+}
32+
33+declare global {
34+ interface Window {
35+ __dumber_appearance_config?: AppearanceConfig;
36+ __dumber_applyPalette?: (theme: "light" | "dark") => void;
37+ }
38+}
39+
40+/**
41+ * Applies the appearance configuration to the document
42+ * Sets CSS custom properties for colors and fonts
43+ */
44+export function applyAppearanceConfig(): void {
45+ const config = window.__dumber_appearance_config;
46+ if (!config) {
47+ console.warn("[dumber] Appearance config not available");
48+ return;
49+ }
50+
51+ const theme = config.theme || "light";
52+ const palette = config.palettes[theme];
53+ const root = document.documentElement;
54+
55+ // Apply color palette as CSS custom properties
56+ root.style.setProperty("--color-background", palette.background);
57+ root.style.setProperty("--color-surface", palette.surface);
58+ root.style.setProperty("--color-surface-variant", palette.surfaceVariant);
59+ root.style.setProperty("--color-text", palette.text);
60+ root.style.setProperty("--color-muted", palette.muted);
61+ root.style.setProperty("--color-accent", palette.accent);
62+ root.style.setProperty("--color-border", palette.border);
63+
64+ // Apply font families
65+ root.style.setProperty("--font-sans", config.fonts.sans);
66+ root.style.setProperty("--font-serif", config.fonts.serif);
67+ root.style.setProperty("--font-mono", config.fonts.monospace);
68+
69+ console.log(`[dumber] Applied ${theme} appearance config`);
70+
71+ // Dispatch event for components that need to react to appearance changes
72+ document.dispatchEvent(
73+ new CustomEvent("dumber:appearance-applied", {
74+ detail: { theme, palette, fonts: config.fonts },
75+ }),
76+ );
77+}
78+
79+/**
80+ * Gets the current theme from appearance config
81+ */
82+export function getCurrentTheme(): "light" | "dark" {
83+ return window.__dumber_appearance_config?.theme || "light";
84+}
85+
86+/**
87+ * Gets the current color palette for the active theme
88+ */
89+export function getCurrentPalette(): ColorPalette | null {
90+ const config = window.__dumber_appearance_config;
91+ if (!config) return null;
92+
93+ return config.palettes[config.theme];
94+}
95+
96+/**
97+ * Expose palette application function globally for integration with color-scheme module
98+ */
99+window.__dumber_applyPalette = (theme: "light" | "dark") => {
100+ const config = window.__dumber_appearance_config;
101+ if (!config) return;
102+
103+ const palette = config.palettes[theme];
104+ const root = document.documentElement;
105+
106+ root.style.setProperty("--color-background", palette.background);
107+ root.style.setProperty("--color-surface", palette.surface);
108+ root.style.setProperty("--color-surface-variant", palette.surfaceVariant);
109+ root.style.setProperty("--color-text", palette.text);
110+ root.style.setProperty("--color-muted", palette.muted);
111+ root.style.setProperty("--color-accent", palette.accent);
112+ root.style.setProperty("--color-border", palette.border);
113+
114+ console.log(`[dumber] Switched to ${theme} palette`);
115+};
116+
117+// Auto-initialize if config is already available
118+if (window.__dumber_appearance_config) {
119+ applyAppearanceConfig();
120+}
121+
122+// Listen for config injection and apply
123+document.addEventListener("DOMContentLoaded", () => {
124+ if (window.__dumber_appearance_config) {
125+ applyAppearanceConfig();
126+ }
127+});
128+
129+export default {
130+ applyAppearanceConfig,
131+ getCurrentTheme,
132+ getCurrentPalette,
133+};
@@ -0,0 +1,133 @@
1+/**
2+ * Appearance Configuration Module
3+ *
4+ * Handles application appearance configuration including color palettes and fonts.
5+ * This config is injected by the native layer at startup and made available to all
6+ * injected modules and components.
7+ */
8+
9+export interface ColorPalette {
10+ background: string;
11+ surface: string;
12+ surfaceVariant: string;
13+ text: string;
14+ muted: string;
15+ accent: string;
16+ border: string;
17+}
18+
19+export interface AppearanceConfig {
20+ theme: "light" | "dark";
21+ palettes: {
22+ light: ColorPalette;
23+ dark: ColorPalette;
24+ };
25+ fonts: {
26+ sans: string;
27+ serif: string;
28+ monospace: string;
29+ defaultSize: number;
30+ };
31+}
32+
33+declare global {
34+ interface Window {
35+ __dumber_appearance_config?: AppearanceConfig;
36+ __dumber_applyPalette?: (theme: "light" | "dark") => void;
37+ }
38+}
39+
40+/**
41+ * Applies the appearance configuration to the document
42+ * Sets CSS custom properties for colors and fonts
43+ */
44+export function applyAppearanceConfig(): void {
45+ const config = window.__dumber_appearance_config;
46+ if (!config) {
47+ console.warn("[dumber] Appearance config not available");
48+ return;
49+ }
50+
51+ const theme = config.theme || "light";
52+ const palette = config.palettes[theme];
53+ const root = document.documentElement;
54+
55+ // Apply color palette as CSS custom properties
56+ root.style.setProperty("--color-background", palette.background);
57+ root.style.setProperty("--color-surface", palette.surface);
58+ root.style.setProperty("--color-surface-variant", palette.surfaceVariant);
59+ root.style.setProperty("--color-text", palette.text);
60+ root.style.setProperty("--color-muted", palette.muted);
61+ root.style.setProperty("--color-accent", palette.accent);
62+ root.style.setProperty("--color-border", palette.border);
63+
64+ // Apply font families
65+ root.style.setProperty("--font-sans", config.fonts.sans);
66+ root.style.setProperty("--font-serif", config.fonts.serif);
67+ root.style.setProperty("--font-mono", config.fonts.monospace);
68+
69+ console.log(`[dumber] Applied ${theme} appearance config`);
70+
71+ // Dispatch event for components that need to react to appearance changes
72+ document.dispatchEvent(
73+ new CustomEvent("dumber:appearance-applied", {
74+ detail: { theme, palette, fonts: config.fonts },
75+ }),
76+ );
77+}
78+
79+/**
80+ * Gets the current theme from appearance config
81+ */
82+export function getCurrentTheme(): "light" | "dark" {
83+ return window.__dumber_appearance_config?.theme || "light";
84+}
85+
86+/**
87+ * Gets the current color palette for the active theme
88+ */
89+export function getCurrentPalette(): ColorPalette | null {
90+ const config = window.__dumber_appearance_config;
91+ if (!config) return null;
92+
93+ return config.palettes[config.theme];
94+}
95+
96+/**
97+ * Expose palette application function globally for integration with color-scheme module
98+ */
99+window.__dumber_applyPalette = (theme: "light" | "dark") => {
100+ const config = window.__dumber_appearance_config;
101+ if (!config) return;
102+
103+ const palette = config.palettes[theme];
104+ const root = document.documentElement;
105+
106+ root.style.setProperty("--color-background", palette.background);
107+ root.style.setProperty("--color-surface", palette.surface);
108+ root.style.setProperty("--color-surface-variant", palette.surfaceVariant);
109+ root.style.setProperty("--color-text", palette.text);
110+ root.style.setProperty("--color-muted", palette.muted);
111+ root.style.setProperty("--color-accent", palette.accent);
112+ root.style.setProperty("--color-border", palette.border);
113+
114+ console.log(`[dumber] Switched to ${theme} palette`);
115+};
116+
117+// Auto-initialize if config is already available
118+if (window.__dumber_appearance_config) {
119+ applyAppearanceConfig();
120+}
121+
122+// Listen for config injection and apply
123+document.addEventListener("DOMContentLoaded", () => {
124+ if (window.__dumber_appearance_config) {
125+ applyAppearanceConfig();
126+ }
127+});
128+
129+export default {
130+ applyAppearanceConfig,
131+ getCurrentTheme,
132+ getCurrentPalette,
133+};
M internal/app/browser/webview.go +65 -4
@@ -2,6 +2,7 @@ package browser
22
33 import (
44 "context"
5+ "encoding/json"
56 "fmt"
67 "log"
78 "os"
@@ -39,10 +40,7 @@ func (app *BrowserApp) buildWebkitConfig() (*webkit.Config, error) {
3940 return nil, err
4041 }
4142
42- // Note: WebKit data/cache directories are now handled by WebContext
43- // Application-level config (colors, memory, video acceleration) is handled separately
44-
45- // Build basic WebKit configuration
43+ // Build WebKit configuration with data directories for persistence
4644 cfg := &webkit.Config{
4745 UserAgent: buildUserAgent(app.config),
4846 EnableJavaScript: true,
@@ -51,6 +49,8 @@ func (app *BrowserApp) buildWebkitConfig() (*webkit.Config, error) {
5149 HardwareAcceleration: true,
5250 DefaultFontSize: app.config.Appearance.DefaultFontSize,
5351 MinimumFontSize: 8,
52+ DataDir: webkitData,
53+ CacheDir: webkitCache,
5454 }
5555
5656 return cfg, nil
@@ -246,6 +246,67 @@ func (app *BrowserApp) setupWebViewIntegration() {
246246 if win := app.webView.Window(); win != nil {
247247 app.browserService.SetWindowTitleUpdater(win)
248248 }
249+
250+ // Expose appearance configuration to the injected GUI modules
251+ app.exposeAppearanceConfig(app.webView)
252+}
253+
254+// exposeAppearanceConfig injects appearance configuration as a typed global
255+// This makes color palettes and font preferences available to TypeScript modules
256+func (app *BrowserApp) exposeAppearanceConfig(view *webkit.WebView) {
257+ if view == nil || app.config == nil {
258+ return
259+ }
260+
261+ // Build appearance config matching the TypeScript AppearanceConfig interface
262+ // Note: Theme preference comes from GTK system settings (via color-scheme.ts),
263+ // not from config file. We default to "light" here.
264+ appearanceConfig := map[string]interface{}{
265+ "theme": "light", // Default theme - color-scheme.ts will detect actual GTK preference
266+ "palettes": map[string]interface{}{
267+ "light": map[string]string{
268+ "background": app.config.Appearance.LightPalette.Background,
269+ "surface": app.config.Appearance.LightPalette.Surface,
270+ "surfaceVariant": app.config.Appearance.LightPalette.SurfaceVariant,
271+ "text": app.config.Appearance.LightPalette.Text,
272+ "muted": app.config.Appearance.LightPalette.Muted,
273+ "accent": app.config.Appearance.LightPalette.Accent,
274+ "border": app.config.Appearance.LightPalette.Border,
275+ },
276+ "dark": map[string]string{
277+ "background": app.config.Appearance.DarkPalette.Background,
278+ "surface": app.config.Appearance.DarkPalette.Surface,
279+ "surfaceVariant": app.config.Appearance.DarkPalette.SurfaceVariant,
280+ "text": app.config.Appearance.DarkPalette.Text,
281+ "muted": app.config.Appearance.DarkPalette.Muted,
282+ "accent": app.config.Appearance.DarkPalette.Accent,
283+ "border": app.config.Appearance.DarkPalette.Border,
284+ },
285+ },
286+ "fonts": map[string]interface{}{
287+ "sans": app.config.Appearance.SansFont,
288+ "serif": app.config.Appearance.SerifFont,
289+ "monospace": app.config.Appearance.MonospaceFont,
290+ "defaultSize": app.config.Appearance.DefaultFontSize,
291+ },
292+ }
293+
294+ // Convert to JSON
295+ payload, err := json.Marshal(appearanceConfig)
296+ if err != nil {
297+ log.Printf("[webview] Failed to marshal appearance config: %v", err)
298+ return
299+ }
300+
301+ // Inject as global variable before any modules load
302+ // The appearance-config.ts module will detect and apply this
303+ script := fmt.Sprintf(`window.__dumber_appearance_config = %s;`, string(payload))
304+
305+ if err := view.InjectScript(script); err != nil {
306+ log.Printf("[webview] Failed to expose appearance config: %v", err)
307+ } else {
308+ log.Printf("[webview] Appearance config exposed with default theme=light")
309+ }
249310 }
250311
251312 // setupContentBlocking initializes the content blocking system with proper timing
@@ -2,6 +2,7 @@ package browser
2 2
3 import (3 import (
4 "context"4 "context"
5+ "encoding/json"
5 "fmt"6 "fmt"
6 "log"7 "log"
7 "os"8 "os"
@@ -39,10 +40,7 @@ func (app *BrowserApp) buildWebkitConfig() (*webkit.Config, error) {
39 return nil, err40 return nil, err
40 }41 }
41 42
42- // Note: WebKit data/cache directories are now handled by WebContext43+ // Build WebKit configuration with data directories for persistence
43- // Application-level config (colors, memory, video acceleration) is handled separately
44-
45- // Build basic WebKit configuration
46 cfg := &webkit.Config{44 cfg := &webkit.Config{
47 UserAgent: buildUserAgent(app.config),45 UserAgent: buildUserAgent(app.config),
48 EnableJavaScript: true,46 EnableJavaScript: true,
@@ -51,6 +49,8 @@ func (app *BrowserApp) buildWebkitConfig() (*webkit.Config, error) {
51 HardwareAcceleration: true,49 HardwareAcceleration: true,
52 DefaultFontSize: app.config.Appearance.DefaultFontSize,50 DefaultFontSize: app.config.Appearance.DefaultFontSize,
53 MinimumFontSize: 8,51 MinimumFontSize: 8,
52+ DataDir: webkitData,
53+ CacheDir: webkitCache,
54 }54 }
55 55
56 return cfg, nil56 return cfg, nil
@@ -246,6 +246,67 @@ func (app *BrowserApp) setupWebViewIntegration() {
246 if win := app.webView.Window(); win != nil {246 if win := app.webView.Window(); win != nil {
247 app.browserService.SetWindowTitleUpdater(win)247 app.browserService.SetWindowTitleUpdater(win)
248 }248 }
249+
250+ // Expose appearance configuration to the injected GUI modules
251+ app.exposeAppearanceConfig(app.webView)
252+}
253+
254+// exposeAppearanceConfig injects appearance configuration as a typed global
255+// This makes color palettes and font preferences available to TypeScript modules
256+func (app *BrowserApp) exposeAppearanceConfig(view *webkit.WebView) {
257+ if view == nil || app.config == nil {
258+ return
259+ }
260+
261+ // Build appearance config matching the TypeScript AppearanceConfig interface
262+ // Note: Theme preference comes from GTK system settings (via color-scheme.ts),
263+ // not from config file. We default to "light" here.
264+ appearanceConfig := map[string]interface{}{
265+ "theme": "light", // Default theme - color-scheme.ts will detect actual GTK preference
266+ "palettes": map[string]interface{}{
267+ "light": map[string]string{
268+ "background": app.config.Appearance.LightPalette.Background,
269+ "surface": app.config.Appearance.LightPalette.Surface,
270+ "surfaceVariant": app.config.Appearance.LightPalette.SurfaceVariant,
271+ "text": app.config.Appearance.LightPalette.Text,
272+ "muted": app.config.Appearance.LightPalette.Muted,
273+ "accent": app.config.Appearance.LightPalette.Accent,
274+ "border": app.config.Appearance.LightPalette.Border,
275+ },
276+ "dark": map[string]string{
277+ "background": app.config.Appearance.DarkPalette.Background,
278+ "surface": app.config.Appearance.DarkPalette.Surface,
279+ "surfaceVariant": app.config.Appearance.DarkPalette.SurfaceVariant,
280+ "text": app.config.Appearance.DarkPalette.Text,
281+ "muted": app.config.Appearance.DarkPalette.Muted,
282+ "accent": app.config.Appearance.DarkPalette.Accent,
283+ "border": app.config.Appearance.DarkPalette.Border,
284+ },
285+ },
286+ "fonts": map[string]interface{}{
287+ "sans": app.config.Appearance.SansFont,
288+ "serif": app.config.Appearance.SerifFont,
289+ "monospace": app.config.Appearance.MonospaceFont,
290+ "defaultSize": app.config.Appearance.DefaultFontSize,
291+ },
292+ }
293+
294+ // Convert to JSON
295+ payload, err := json.Marshal(appearanceConfig)
296+ if err != nil {
297+ log.Printf("[webview] Failed to marshal appearance config: %v", err)
298+ return
299+ }
300+
301+ // Inject as global variable before any modules load
302+ // The appearance-config.ts module will detect and apply this
303+ script := fmt.Sprintf(`window.__dumber_appearance_config = %s;`, string(payload))
304+
305+ if err := view.InjectScript(script); err != nil {
306+ log.Printf("[webview] Failed to expose appearance config: %v", err)
307+ } else {
308+ log.Printf("[webview] Appearance config exposed with default theme=light")
309+ }
249 }310 }
250 311
251 // setupContentBlocking initializes the content blocking system with proper timing312 // setupContentBlocking initializes the content blocking system with proper timing

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