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 | |||
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | 4 | "context" | |
| 5 | + | "encoding/json" | |
| 5 | 6 | "fmt" | |
| 6 | 7 | "log" | |
| 7 | 8 | "os" | |
| @@ -39,10 +40,7 @@ func (app *BrowserApp) buildWebkitConfig() (*webkit.Config, error) { | |||
| 39 | 40 | return nil, err | |
| 40 | 41 | } | |
| 41 | 42 | ||
| 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 | |
| 46 | 44 | cfg := &webkit.Config{ | |
| 47 | 45 | UserAgent: buildUserAgent(app.config), | |
| 48 | 46 | EnableJavaScript: true, | |
| @@ -51,6 +49,8 @@ func (app *BrowserApp) buildWebkitConfig() (*webkit.Config, error) { | |||
| 51 | 49 | HardwareAcceleration: true, | |
| 52 | 50 | DefaultFontSize: app.config.Appearance.DefaultFontSize, | |
| 53 | 51 | MinimumFontSize: 8, | |
| 52 | + | DataDir: webkitData, | |
| 53 | + | CacheDir: webkitCache, | |
| 54 | 54 | } | |
| 55 | 55 | ||
| 56 | 56 | return cfg, nil | |
| @@ -246,6 +246,67 @@ func (app *BrowserApp) setupWebViewIntegration() { | |||
| 246 | 246 | if win := app.webView.Window(); win != nil { | |
| 247 | 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 | 312 | // 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, err | 40 | return nil, err | ||
| 40 | } | 41 | } | ||
| 41 | 42 | ||||
| 42 | - | // Note: WebKit data/cache directories are now handled by WebContext | 43 | + | // 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, nil | 56 | 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 timing | 312 | // setupContentBlocking initializes the content blocking system with proper timing | ||