detached 826661c

refactor(logging): migrate log.Printf to logging package

commit
826661c824505953081811775cf89b587fc1e071
author
bnema · · 2025-11-29 12:04 UTC
parent
5e0ca2d
signature
GPG Unverified2B82207D370127A0 The key is unknown
50 files · +867 -843
M internal/app/api/scheme.go +11 -11
@@ -3,7 +3,6 @@ package api
3 import (3 import (
4 "embed"4 "embed"
5 "fmt"5 "fmt"
6- "log"
7 "mime"6 "mime"
8 neturl "net/url"7 neturl "net/url"
9 "os"8 "os"
@@ -12,6 +11,7 @@ import (
12 11
13 "github.com/bnema/dumber/internal/app/constants"12 "github.com/bnema/dumber/internal/app/constants"
14 "github.com/bnema/dumber/internal/config"13 "github.com/bnema/dumber/internal/config"
14+ "github.com/bnema/dumber/internal/logging"
15 "github.com/bnema/dumber/internal/services"15 "github.com/bnema/dumber/internal/services"
16 webkit "github.com/diamondburned/gotk4-webkitgtk/pkg/webkit/v6"16 webkit "github.com/diamondburned/gotk4-webkitgtk/pkg/webkit/v6"
17 "github.com/diamondburned/gotk4/pkg/gio/v2"17 "github.com/diamondburned/gotk4/pkg/gio/v2"
@@ -47,7 +47,7 @@ func (s *SchemeHandler) SetConfig(cfg *config.Config) {
47 // Handle processes dumb:// scheme requests using the new URISchemeRequest API47 // Handle processes dumb:// scheme requests using the new URISchemeRequest API
48 func (s *SchemeHandler) Handle(req *webkit.URISchemeRequest) {48 func (s *SchemeHandler) Handle(req *webkit.URISchemeRequest) {
49 uri := req.URI()49 uri := req.URI()
50- log.Printf("[scheme] request: %s", uri)50+ logging.Debug(fmt.Sprintf("[scheme] request: %s", uri))
51 51
52 // Known forms:52 // Known forms:
53 // - dumb://homepage or dumb:homepage → index.html53 // - dumb://homepage or dumb:homepage → index.html
@@ -96,7 +96,7 @@ func (s *SchemeHandler) handleAsset(req *webkit.URISchemeRequest, u *neturl.URL)
96 96
97 // Special-case homepage favicon: map .ico request to embedded PNG file97 // Special-case homepage favicon: map .ico request to embedded PNG file
98 if (u.Host == constants.HomepagePath || u.Opaque == constants.HomepagePath) && strings.EqualFold(rel, "favicon.ico") {98 if (u.Host == constants.HomepagePath || u.Opaque == constants.HomepagePath) && strings.EqualFold(rel, "favicon.ico") {
99- log.Printf("[scheme] asset: rel=%s (host=%s path=%s) → mapping to favicon.png", rel, u.Host, u.Path)99+ logging.Debug(fmt.Sprintf("[scheme] asset: rel=%s (host=%s path=%s) → mapping to favicon.png", rel, u.Host, u.Path))
100 data, rerr := s.assets.ReadFile(filepath.ToSlash(filepath.Join("assets", "gui", "favicon.png")))100 data, rerr := s.assets.ReadFile(filepath.ToSlash(filepath.Join("assets", "gui", "favicon.png")))
101 if rerr == nil {101 if rerr == nil {
102 s.finishRequest(req, "image/png", data, "favicon.png")102 s.finishRequest(req, "image/png", data, "favicon.png")
@@ -104,12 +104,12 @@ func (s *SchemeHandler) handleAsset(req *webkit.URISchemeRequest, u *neturl.URL)
104 }104 }
105 }105 }
106 106
107- log.Printf("[scheme] asset: rel=%s (host=%s path=%s)", rel, u.Host, u.Path)107+ logging.Debug(fmt.Sprintf("[scheme] asset: rel=%s (host=%s path=%s)", rel, u.Host, u.Path))
108 108
109 // Try to read the requested asset109 // Try to read the requested asset
110 data, rerr := s.assets.ReadFile(filepath.ToSlash(filepath.Join("assets", "gui", rel)))110 data, rerr := s.assets.ReadFile(filepath.ToSlash(filepath.Join("assets", "gui", rel)))
111 if rerr != nil {111 if rerr != nil {
112- log.Printf("[scheme] not found: %s", rel)112+ logging.Debug(fmt.Sprintf("[scheme] not found: %s", rel))
113 req.FinishError(fmt.Errorf("asset not found: %s", rel))113 req.FinishError(fmt.Errorf("asset not found: %s", rel))
114 return114 return
115 }115 }
@@ -118,7 +118,7 @@ func (s *SchemeHandler) handleAsset(req *webkit.URISchemeRequest, u *neturl.URL)
118 mt := s.getMimeType(rel)118 mt := s.getMimeType(rel)
119 119
120 // Finish the request with the data120 // Finish the request with the data
121- log.Printf("[scheme] serving %s with mime-type: %s", rel, mt)121+ logging.Debug(fmt.Sprintf("[scheme] serving %s with mime-type: %s", rel, mt))
122 s.finishRequest(req, mt, data, rel)122 s.finishRequest(req, mt, data, rel)
123 }123 }
124 124
@@ -131,7 +131,7 @@ func (s *SchemeHandler) finishRequest(req *webkit.URISchemeRequest, mimeType str
131 stream := gio.NewMemoryInputStreamFromBytes(gbytes)131 stream := gio.NewMemoryInputStreamFromBytes(gbytes)
132 if stream == nil {132 if stream == nil {
133 req.FinishError(fmt.Errorf("failed to create input stream for: %s", filename))133 req.FinishError(fmt.Errorf("failed to create input stream for: %s", filename))
134- log.Printf("[scheme] failed to create stream for: %s", filename)134+ logging.Error(fmt.Sprintf("[scheme] failed to create stream for: %s", filename))
135 return135 return
136 }136 }
137 137
@@ -204,7 +204,7 @@ func (s *SchemeHandler) handleFavicon(req *webkit.URISchemeRequest, u *neturl.UR
204 }204 }
205 205
206 if filename == "" {206 if filename == "" {
207- log.Printf("[scheme] favicon: empty filename")207+ logging.Debug(fmt.Sprintf("[scheme] favicon: empty filename"))
208 req.FinishError(fmt.Errorf("invalid favicon path"))208 req.FinishError(fmt.Errorf("invalid favicon path"))
209 return209 return
210 }210 }
@@ -212,7 +212,7 @@ func (s *SchemeHandler) handleFavicon(req *webkit.URISchemeRequest, u *neturl.UR
212 // Get the favicon cache directory path212 // Get the favicon cache directory path
213 dataDir, err := config.GetDataDir()213 dataDir, err := config.GetDataDir()
214 if err != nil {214 if err != nil {
215- log.Printf("[scheme] favicon: failed to get data directory: %v", err)215+ logging.Error(fmt.Sprintf("[scheme] favicon: failed to get data directory: %v", err))
216 req.FinishError(fmt.Errorf("failed to get data directory"))216 req.FinishError(fmt.Errorf("failed to get data directory"))
217 return217 return
218 }218 }
@@ -222,11 +222,11 @@ func (s *SchemeHandler) handleFavicon(req *webkit.URISchemeRequest, u *neturl.UR
222 // Read the favicon file222 // Read the favicon file
223 data, err := os.ReadFile(faviconPath)223 data, err := os.ReadFile(faviconPath)
224 if err != nil {224 if err != nil {
225- log.Printf("[scheme] favicon: file not found: %s", faviconPath)225+ logging.Debug(fmt.Sprintf("[scheme] favicon: file not found: %s", faviconPath))
226 req.FinishError(fmt.Errorf("favicon not found: %s", filename))226 req.FinishError(fmt.Errorf("favicon not found: %s", filename))
227 return227 return
228 }228 }
229 229
230- log.Printf("[scheme] favicon: serving %s (%d bytes)", filename, len(data))230+ logging.Debug(fmt.Sprintf("[scheme] favicon: serving %s (%d bytes)", filename, len(data)))
231 s.finishRequest(req, "image/png", data, filename)231 s.finishRequest(req, "image/png", data, filename)
232 }232 }
M internal/app/browser/browser.go +42 -43
@@ -5,7 +5,6 @@ import (
5 "database/sql"5 "database/sql"
6 "embed"6 "embed"
7 "fmt"7 "fmt"
8- "log"
9 "os"8 "os"
10 "os/signal"9 "os/signal"
11 "runtime"10 "runtime"
@@ -58,8 +57,8 @@ type BrowserApp struct {
58 workspace *WorkspaceManager57 workspace *WorkspaceManager
59 58
60 // Content filtering59 // Content filtering
61- filterManager *filtering.FilterManager60+ filterManager *filtering.FilterManager
62- contentBlockingService *filtering.ContentBlockingService61+ contentBlockingService *filtering.ContentBlockingService
63 62
64 // Handlers63 // Handlers
65 schemeHandler *api.SchemeHandler64 schemeHandler *api.SchemeHandler
@@ -71,7 +70,7 @@ type BrowserApp struct {
71 // Run starts the browser application70 // Run starts the browser application
72 func Run(assets embed.FS, version, commit, buildDate string) {71 func Run(assets embed.FS, version, commit, buildDate string) {
73 startupStart := time.Now()72 startupStart := time.Now()
74- log.Printf("Starting GUI mode (webkit_cgo=%v)", webkit.IsNativeAvailable())73+ logging.Info(fmt.Sprintf("Starting GUI mode (webkit_cgo=%v)", webkit.IsNativeAvailable()))
75 74
76 app := &BrowserApp{75 app := &BrowserApp{
77 version: version,76 version: version,
@@ -81,7 +80,7 @@ func Run(assets embed.FS, version, commit, buildDate string) {
81 }80 }
82 81
83 if err := app.Initialize(); err != nil {82 if err := app.Initialize(); err != nil {
84- log.Printf("Failed to initialize browser: %v", err)83+ logging.Error(fmt.Sprintf("Failed to initialize browser: %v", err))
85 if webkit.IsNativeAvailable() {84 if webkit.IsNativeAvailable() {
86 runtime.UnlockOSThread()85 runtime.UnlockOSThread()
87 }86 }
@@ -89,9 +88,9 @@ func Run(assets embed.FS, version, commit, buildDate string) {
89 }88 }
90 89
91 startupElapsed := time.Since(startupStart)90 startupElapsed := time.Since(startupStart)
92- log.Printf("[startup] Application initialized in %v", startupElapsed)91+ logging.Info(fmt.Sprintf("[startup] Application initialized in %v", startupElapsed))
93 if startupElapsed > 500*time.Millisecond {92 if startupElapsed > 500*time.Millisecond {
94- log.Printf("[startup] WARNING: Startup took %v (target: <500ms)", startupElapsed)93+ logging.Warn(fmt.Sprintf("[startup] WARNING: Startup took %v (target: <500ms)", startupElapsed))
95 }94 }
96 95
97 app.Run()96 app.Run()
@@ -116,20 +115,20 @@ func (app *BrowserApp) Initialize() error {
116 115
117 // Setup output capture if configured116 // Setup output capture if configured
118 if err := app.setupOutputCapture(); err != nil {117 if err := app.setupOutputCapture(); err != nil {
119- log.Printf("Warning: failed to setup output capture: %v", err)118+ logging.Warn(fmt.Sprintf("Warning: failed to setup output capture: %v", err))
120 }119 }
121 120
122 // Initialize WebKit log capture if configured121 // Initialize WebKit log capture if configured
123 if app.config.Logging.CaptureCOutput {122 if app.config.Logging.CaptureCOutput {
124 if err := webkit.InitWebKitLogCapture(); err != nil {123 if err := webkit.InitWebKitLogCapture(); err != nil {
125- log.Printf("Warning: failed to initialize WebKit log capture: %v", err)124+ logging.Warn(fmt.Sprintf("Warning: failed to initialize WebKit log capture: %v", err))
126 } else {125 } else {
127 defer webkit.StopWebKitLogCapture()126 defer webkit.StopWebKitLogCapture()
128 webkit.StartWebKitOutputCapture()127 webkit.StartWebKitOutputCapture()
129 }128 }
130 }129 }
131 130
132- log.Printf("Config initialized")131+ logging.Info(fmt.Sprintf("Config initialized"))
133 132
134 // Detect keyboard layout133 // Detect keyboard layout
135 environment.DetectAndSetKeyboardLocale()134 environment.DetectAndSetKeyboardLocale()
@@ -141,11 +140,11 @@ func (app *BrowserApp) Initialize() error {
141 }140 }
142 app.database = database141 app.database = database
143 app.queries = db.New(database)142 app.queries = db.New(database)
144- log.Printf("Database opened at %s", app.config.Database.Path)143+ logging.Info(fmt.Sprintf("Database opened at %s", app.config.Database.Path))
145 144
146 // Cleanup expired certificate validations145 // Cleanup expired certificate validations
147 if err := webkit.CleanupExpiredCertificateValidations(); err != nil {146 if err := webkit.CleanupExpiredCertificateValidations(); err != nil {
148- log.Printf("Warning: failed to cleanup expired certificate validations: %v", err)147+ logging.Warn(fmt.Sprintf("Warning: failed to cleanup expired certificate validations: %v", err))
149 }148 }
150 149
151 // Initialize services150 // Initialize services
@@ -154,7 +153,7 @@ func (app *BrowserApp) Initialize() error {
154 153
155 // Load all caches in parallel for fast startup (target: <100ms)154 // Load all caches in parallel for fast startup (target: <100ms)
156 if err := app.loadCachesParallel(context.Background()); err != nil {155 if err := app.loadCachesParallel(context.Background()); err != nil {
157- log.Printf("Warning: failed to load caches: %v", err)156+ logging.Warn(fmt.Sprintf("Warning: failed to load caches: %v", err))
158 // Non-fatal - caches will fall back to defaults or DB queries157 // Non-fatal - caches will fall back to defaults or DB queries
159 }158 }
160 159
@@ -176,7 +175,7 @@ func (app *BrowserApp) loadCachesParallel(ctx context.Context) error {
176 // Load zoom cache in parallel175 // Load zoom cache in parallel
177 g.Go(func() error {176 g.Go(func() error {
178 if err := app.browserService.LoadZoomCacheFromDB(ctx); err != nil {177 if err := app.browserService.LoadZoomCacheFromDB(ctx); err != nil {
179- log.Printf("[cache] Failed to load zoom cache: %v", err)178+ logging.Error(fmt.Sprintf("[cache] Failed to load zoom cache: %v", err))
180 return err179 return err
181 }180 }
182 return nil181 return nil
@@ -185,7 +184,7 @@ func (app *BrowserApp) loadCachesParallel(ctx context.Context) error {
185 // Load certificate validation cache in parallel184 // Load certificate validation cache in parallel
186 g.Go(func() error {185 g.Go(func() error {
187 if err := app.browserService.LoadCertCacheFromDB(ctx); err != nil {186 if err := app.browserService.LoadCertCacheFromDB(ctx); err != nil {
188- log.Printf("[cache] Failed to load cert cache: %v", err)187+ logging.Error(fmt.Sprintf("[cache] Failed to load cert cache: %v", err))
189 return err188 return err
190 }189 }
191 return nil190 return nil
@@ -194,7 +193,7 @@ func (app *BrowserApp) loadCachesParallel(ctx context.Context) error {
194 // Load favorites cache in parallel193 // Load favorites cache in parallel
195 g.Go(func() error {194 g.Go(func() error {
196 if err := app.browserService.LoadFavoritesCacheFromDB(ctx); err != nil {195 if err := app.browserService.LoadFavoritesCacheFromDB(ctx); err != nil {
197- log.Printf("[cache] Failed to load favorites cache: %v", err)196+ logging.Error(fmt.Sprintf("[cache] Failed to load favorites cache: %v", err))
198 return err197 return err
199 }198 }
200 return nil199 return nil
@@ -203,7 +202,7 @@ func (app *BrowserApp) loadCachesParallel(ctx context.Context) error {
203 // Load fuzzy search cache in parallel for instant dmenu access202 // Load fuzzy search cache in parallel for instant dmenu access
204 g.Go(func() error {203 g.Go(func() error {
205 if err := app.browserService.LoadFuzzyCacheFromDB(ctx); err != nil {204 if err := app.browserService.LoadFuzzyCacheFromDB(ctx); err != nil {
206- log.Printf("[cache] Failed to load fuzzy cache: %v", err)205+ logging.Error(fmt.Sprintf("[cache] Failed to load fuzzy cache: %v", err))
207 return err206 return err
208 }207 }
209 return nil208 return nil
@@ -215,11 +214,11 @@ func (app *BrowserApp) loadCachesParallel(ctx context.Context) error {
215 }214 }
216 215
217 elapsed := time.Since(startTime)216 elapsed := time.Since(startTime)
218- log.Printf("[cache] All caches loaded in %v", elapsed)217+ logging.Info(fmt.Sprintf("[cache] All caches loaded in %v", elapsed))
219 218
220 // Warn if startup is slower than target219 // Warn if startup is slower than target
221 if elapsed > 100*time.Millisecond {220 if elapsed > 100*time.Millisecond {
222- log.Printf("[cache] Warning: Cache loading took %v (target: <100ms)", elapsed)221+ logging.Warn(fmt.Sprintf("[cache] Warning: Cache loading took %v (target: <100ms)", elapsed))
223 }222 }
224 223
225 return nil224 return nil
@@ -239,19 +238,19 @@ func (app *BrowserApp) Run() {
239 // Initialize content blocking BEFORE creating WebViews so the service is ready238 // Initialize content blocking BEFORE creating WebViews so the service is ready
240 // to register WebViews as they are created (tabs, workspaces, popups)239 // to register WebViews as they are created (tabs, workspaces, popups)
241 if err := app.setupContentBlocking(); err != nil {240 if err := app.setupContentBlocking(); err != nil {
242- log.Printf("Warning: failed to setup content blocking: %v", err)241+ logging.Warn(fmt.Sprintf("Warning: failed to setup content blocking: %v", err))
243 // Continue without content blocking242 // Continue without content blocking
244 }243 }
245 244
246 // Create and setup WebView (this also creates TabManager which creates more WebViews)245 // Create and setup WebView (this also creates TabManager which creates more WebViews)
247 if err := app.createWebView(); err != nil {246 if err := app.createWebView(); err != nil {
248- log.Printf("Warning: failed to create WebView: %v", err)247+ logging.Warn(fmt.Sprintf("Warning: failed to create WebView: %v", err))
249 return248 return
250 }249 }
251 250
252 // Apply URI scheme handlers after WebView creation251 // Apply URI scheme handlers after WebView creation
253 if err := webkit.ApplyURISchemeHandlers(app.webView.GetWebView()); err != nil {252 if err := webkit.ApplyURISchemeHandlers(app.webView.GetWebView()); err != nil {
254- log.Printf("Warning: failed to register URI scheme handlers: %v", err)253+ logging.Warn(fmt.Sprintf("Warning: failed to register URI scheme handlers: %v", err))
255 }254 }
256 255
257 // Handle browse command if present (must use active tab's navigation controller)256 // Handle browse command if present (must use active tab's navigation controller)
@@ -277,52 +276,52 @@ func (app *BrowserApp) Run() {
277 276
278 // cleanup handles cleanup on shutdown277 // cleanup handles cleanup on shutdown
279 func (app *BrowserApp) cleanup() {278 func (app *BrowserApp) cleanup() {
280- log.Printf("Starting browser cleanup...")279+ logging.Info(fmt.Sprintf("Starting browser cleanup..."))
281 280
282 // Cleanup window shortcuts first281 // Cleanup window shortcuts first
283 if app.windowShortcutHandler != nil {282 if app.windowShortcutHandler != nil {
284- log.Printf("Cleaning up window shortcuts")283+ logging.Info(fmt.Sprintf("Cleaning up window shortcuts"))
285 app.windowShortcutHandler.Cleanup()284 app.windowShortcutHandler.Cleanup()
286 app.windowShortcutHandler = nil285 app.windowShortcutHandler = nil
287 }286 }
288 287
289 // Cleanup tab manager (which cleans up all tabs and their workspaces)288 // Cleanup tab manager (which cleans up all tabs and their workspaces)
290 if app.tabManager != nil {289 if app.tabManager != nil {
291- log.Printf("Cleaning up tab manager")290+ logging.Info(fmt.Sprintf("Cleaning up tab manager"))
292 app.tabManager.Cleanup()291 app.tabManager.Cleanup()
293 app.tabManager = nil292 app.tabManager = nil
294 }293 }
295 294
296 // Flush all caches to ensure pending writes complete before database closes295 // Flush all caches to ensure pending writes complete before database closes
297 if app.browserService != nil {296 if app.browserService != nil {
298- log.Printf("Flushing all caches...")297+ logging.Info(fmt.Sprintf("Flushing all caches..."))
299 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)298 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
300 defer cancel()299 defer cancel()
301 if err := app.browserService.FlushAllCaches(ctx); err != nil {300 if err := app.browserService.FlushAllCaches(ctx); err != nil {
302- log.Printf("Warning: failed to flush caches: %v", err)301+ logging.Warn(fmt.Sprintf("Warning: failed to flush caches: %v", err))
303 }302 }
304 }303 }
305 304
306 // Close database with WAL checkpoint305 // Close database with WAL checkpoint
307 if app.database != nil {306 if app.database != nil {
308- log.Printf("Performing WAL checkpoint and closing database...")307+ logging.Info(fmt.Sprintf("Performing WAL checkpoint and closing database..."))
309 308
310 // Run WAL checkpoint to commit all pending writes and truncate WAL file309 // Run WAL checkpoint to commit all pending writes and truncate WAL file
311 if _, err := app.database.Exec("PRAGMA wal_checkpoint(TRUNCATE)"); err != nil {310 if _, err := app.database.Exec("PRAGMA wal_checkpoint(TRUNCATE)"); err != nil {
312- log.Printf("Warning: WAL checkpoint failed: %v", err)311+ logging.Warn(fmt.Sprintf("Warning: WAL checkpoint failed: %v", err))
313 } else {312 } else {
314- log.Printf("WAL checkpoint completed successfully")313+ logging.Info(fmt.Sprintf("WAL checkpoint completed successfully"))
315 }314 }
316 315
317 // Close database connection316 // Close database connection
318 if closeErr := app.database.Close(); closeErr != nil {317 if closeErr := app.database.Close(); closeErr != nil {
319- log.Printf("Warning: failed to close database: %v", closeErr)318+ logging.Warn(fmt.Sprintf("Warning: failed to close database: %v", closeErr))
320 } else {319 } else {
321- log.Printf("Database closed successfully")320+ logging.Info(fmt.Sprintf("Database closed successfully"))
322 }321 }
323 }322 }
324 323
325- log.Printf("Browser cleanup completed")324+ logging.Info(fmt.Sprintf("Browser cleanup completed"))
326 }325 }
327 326
328 // setupOutputCapture initializes stdout/stderr capture if configured327 // setupOutputCapture initializes stdout/stderr capture if configured
@@ -331,11 +330,11 @@ func (app *BrowserApp) setupOutputCapture() error {
331 // Capturing stdout/stderr works for non-GTK builds, but in native GTK mode330 // Capturing stdout/stderr works for non-GTK builds, but in native GTK mode
332 // it interferes with WebKit's own pipe management and crashes immediately.331 // it interferes with WebKit's own pipe management and crashes immediately.
333 if webkit.IsNativeAvailable() {332 if webkit.IsNativeAvailable() {
334- log.Printf("Warning: stdout/stderr capture is not supported in native GTK mode; skipping")333+ logging.Warn(fmt.Sprintf("Warning: stdout/stderr capture is not supported in native GTK mode; skipping"))
335 return nil334 return nil
336 }335 }
337 336
338- log.Printf("Warning: stdout/stderr capture is experimental and may interfere with normal operations")337+ logging.Warn(fmt.Sprintf("Warning: stdout/stderr capture is experimental and may interfere with normal operations"))
339 if logger := logging.GetLogger(); logger != nil {338 if logger := logging.GetLogger(); logger != nil {
340 outputCapture := logging.NewOutputCapture(logger)339 outputCapture := logging.NewOutputCapture(logger)
341 if err := outputCapture.Start(); err != nil {340 if err := outputCapture.Start(); err != nil {
@@ -355,7 +354,7 @@ func (app *BrowserApp) setupSignalHandling() {
355 // Handle signals in a goroutine to quit the main loop354 // Handle signals in a goroutine to quit the main loop
356 go func() {355 go func() {
357 sig := <-sigChan356 sig := <-sigChan
358- log.Printf("Received signal %v - shutting down gracefully", sig)357+ logging.Info(fmt.Sprintf("Received signal %v - shutting down gracefully", sig))
359 webkit.QuitMainLoop()358 webkit.QuitMainLoop()
360 }()359 }()
361 }360 }
@@ -363,29 +362,29 @@ func (app *BrowserApp) setupSignalHandling() {
363 // runMainLoop starts the appropriate main loop based on WebKit availability362 // runMainLoop starts the appropriate main loop based on WebKit availability
364 func (app *BrowserApp) runMainLoop() {363 func (app *BrowserApp) runMainLoop() {
365 if webkit.IsNativeAvailable() {364 if webkit.IsNativeAvailable() {
366- log.Printf("Entering GTK main loop…")365+ logging.Info(fmt.Sprintf("Entering GTK main loop…"))
367 webkit.RunMainLoop()366 webkit.RunMainLoop()
368- log.Printf("GTK main loop exited")367+ logging.Info(fmt.Sprintf("GTK main loop exited"))
369 368
370 // Flush pending history writes immediately after main loop exit369 // Flush pending history writes immediately after main loop exit
371 // This ensures database operations complete while GTK is still in a valid state370 // This ensures database operations complete while GTK is still in a valid state
372 // MUST happen before cleanup() deferred call, which happens after this function returns371 // MUST happen before cleanup() deferred call, which happens after this function returns
373 if app.browserService != nil {372 if app.browserService != nil {
374- log.Printf("Flushing pending history writes...")373+ logging.Info(fmt.Sprintf("Flushing pending history writes..."))
375 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)374 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
376 defer cancel()375 defer cancel()
377 if err := app.browserService.FlushHistoryQueue(ctx); err != nil {376 if err := app.browserService.FlushHistoryQueue(ctx); err != nil {
378- log.Printf("Warning: history queue flush incomplete: %v", err)377+ logging.Warn(fmt.Sprintf("Warning: history queue flush incomplete: %v", err))
379 } else {378 } else {
380- log.Printf("History queue flushed successfully")379+ logging.Info(fmt.Sprintf("History queue flushed successfully"))
381 }380 }
382 }381 }
383 } else {382 } else {
384- log.Printf("Not entering GUI loop (non-CGO build)")383+ logging.Info(fmt.Sprintf("Not entering GUI loop (non-CGO build)"))
385 // In non-CGO mode, just wait for signals384 // In non-CGO mode, just wait for signals
386 sigChan := make(chan os.Signal, 1)385 sigChan := make(chan os.Signal, 1)
387 signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)386 signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
388 sig := <-sigChan387 sig := <-sigChan
389- log.Printf("Received signal %v - exiting", sig)388+ logging.Info(fmt.Sprintf("Received signal %v - exiting", sig))
390 }389 }
391 }390 }
M internal/app/browser/focus_state_machine.go +40 -40
@@ -4,11 +4,11 @@ package browser
4 import (4 import (
5 "container/heap"5 "container/heap"
6 "fmt"6 "fmt"
7- "log"
8 "runtime/debug"7 "runtime/debug"
9 "sync"8 "sync"
10 "time"9 "time"
11 10
11+ "github.com/bnema/dumber/internal/logging"
12 "github.com/bnema/dumber/pkg/webkit"12 "github.com/bnema/dumber/pkg/webkit"
13 "github.com/diamondburned/gotk4/pkg/gtk/v4"13 "github.com/diamondburned/gotk4/pkg/gtk/v4"
14 )14 )
@@ -274,10 +274,10 @@ func (fsm *FocusStateMachine) ConfigureDebug(focusDebug, _ bool, metricsEnabled
274 fsm.metricsEnabled = metricsEnabled274 fsm.metricsEnabled = metricsEnabled
275 275
276 if focusDebug {276 if focusDebug {
277- log.Printf("[FSM] Focus debug logging enabled")277+ logging.Info(fmt.Sprintf("[FSM] Focus debug logging enabled"))
278 }278 }
279 if metricsEnabled {279 if metricsEnabled {
280- log.Printf("[FSM] Focus metrics tracking enabled")280+ logging.Info(fmt.Sprintf("[FSM] Focus metrics tracking enabled"))
281 }281 }
282 }282 }
283 283
@@ -286,27 +286,27 @@ func (fsm *FocusStateMachine) Initialize() error {
286 fsm.mu.Lock()286 fsm.mu.Lock()
287 defer fsm.mu.Unlock()287 defer fsm.mu.Unlock()
288 288
289- log.Printf("[FSM] Initializing focus state machine")289+ logging.Info(fmt.Sprintf("[FSM] Initializing focus state machine"))
290 fsm.currentState = StateInitializing290 fsm.currentState = StateInitializing
291 291
292 // Determine initial focus292 // Determine initial focus
293 leaves := fsm.wm.collectLeaves()293 leaves := fsm.wm.collectLeaves()
294 if len(leaves) == 0 {294 if len(leaves) == 0 {
295- log.Printf("[FSM] No panes found during initialization")295+ logging.Debug(fmt.Sprintf("[FSM] No panes found during initialization"))
296 fsm.currentState = StateIdle296 fsm.currentState = StateIdle
297 return nil297 return nil
298 }298 }
299 299
300 initialPane := fsm.determineInitialFocus(leaves)300 initialPane := fsm.determineInitialFocus(leaves)
301 if initialPane == nil {301 if initialPane == nil {
302- log.Printf("[FSM] Could not determine initial focus target")302+ logging.Warn(fmt.Sprintf("[FSM] Could not determine initial focus target"))
303 fsm.currentState = StateIdle303 fsm.currentState = StateIdle
304 return nil304 return nil
305 }305 }
306 306
307 // Phase 3: Apply initial focus and CSS307 // Phase 3: Apply initial focus and CSS
308 if err := fsm.applyInitialFocus(initialPane); err != nil {308 if err := fsm.applyInitialFocus(initialPane); err != nil {
309- log.Printf("[FSM] Failed to apply initial focus: %v", err)309+ logging.Error(fmt.Sprintf("[FSM] Failed to apply initial focus: %v", err))
310 fsm.currentState = StateIdle310 fsm.currentState = StateIdle
311 return err311 return err
312 }312 }
@@ -324,7 +324,7 @@ func (fsm *FocusStateMachine) Initialize() error {
324 // Phase 7: Transition to idle state324 // Phase 7: Transition to idle state
325 fsm.currentState = StateIdle325 fsm.currentState = StateIdle
326 326
327- log.Printf("[FSM] Focus state machine initialized successfully with pane %p", initialPane)327+ logging.Info(fmt.Sprintf("[FSM] Focus state machine initialized successfully with pane %p", initialPane))
328 return nil328 return nil
329 }329 }
330 330
@@ -333,7 +333,7 @@ func (fsm *FocusStateMachine) Shutdown() {
333 fsm.mu.Lock()333 fsm.mu.Lock()
334 defer fsm.mu.Unlock()334 defer fsm.mu.Unlock()
335 335
336- log.Printf("[FSM] Shutting down focus state machine")336+ logging.Info(fmt.Sprintf("[FSM] Shutting down focus state machine"))
337 337
338 // Signal shutdown338 // Signal shutdown
339 close(fsm.shutdownChan)339 close(fsm.shutdownChan)
@@ -346,7 +346,7 @@ func (fsm *FocusStateMachine) Shutdown() {
346 fsm.settlingTimer.Stop()346 fsm.settlingTimer.Stop()
347 }347 }
348 348
349- log.Printf("[FSM] Focus state machine shutdown complete")349+ logging.Info(fmt.Sprintf("[FSM] Focus state machine shutdown complete"))
350 }350 }
351 351
352 // RequestFocus queues a focus change request with priority and deduplication352 // RequestFocus queues a focus change request with priority and deduplication
@@ -382,7 +382,7 @@ func (fsm *FocusStateMachine) RequestFocus(node *paneNode, source FocusSource) e
382 fsm.mu.Unlock()382 fsm.mu.Unlock()
383 }383 }
384 if fsm.debugEnabled {384 if fsm.debugEnabled {
385- log.Printf("[FSM] Duplicate request ignored: %s -> %p", request.Source, request.TargetNode)385+ logging.Debug(fmt.Sprintf("[FSM] Duplicate request ignored: %s -> %p", request.Source, request.TargetNode))
386 }386 }
387 return nil387 return nil
388 }388 }
@@ -395,7 +395,7 @@ func (fsm *FocusStateMachine) RequestFocus(node *paneNode, source FocusSource) e
395 fsm.mu.Unlock()395 fsm.mu.Unlock()
396 }396 }
397 if fsm.debugEnabled {397 if fsm.debugEnabled {
398- log.Printf("[FSM] Request coalesced: %s -> %p", request.Source, request.TargetNode)398+ logging.Debug(fmt.Sprintf("[FSM] Request coalesced: %s -> %p", request.Source, request.TargetNode))
399 }399 }
400 return nil400 return nil
401 }401 }
@@ -443,7 +443,7 @@ func (fsm *FocusStateMachine) InvalidateActivePane(node *paneNode) {
443 return443 return
444 }444 }
445 445
446- log.Printf("[FSM] Active pane invalidated: %p", node)446+ logging.Debug(fmt.Sprintf("[FSM] Active pane invalidated: %p", node))
447 fsm.activePane = nil447 fsm.activePane = nil
448 448
449 if fsm.wm != nil && fsm.wm.app != nil {449 if fsm.wm != nil && fsm.wm.app != nil {
@@ -541,7 +541,7 @@ func (fsm *FocusStateMachine) determineInitialFocus(leaves []*paneNode) *paneNod
541 if leaf.pane != nil && leaf.pane.webView != nil {541 if leaf.pane != nil && leaf.pane.webView != nil {
542 url := leaf.pane.webView.GetCurrentURL()542 url := leaf.pane.webView.GetCurrentURL()
543 if url != "" && url != "about:blank" {543 if url != "" && url != "about:blank" {
544- log.Printf("[FSM] Selected pane with content as initial focus: %s", url)544+ logging.Debug(fmt.Sprintf("[FSM] Selected pane with content as initial focus: %s", url))
545 return leaf545 return leaf
546 }546 }
547 }547 }
@@ -549,12 +549,12 @@ func (fsm *FocusStateMachine) determineInitialFocus(leaves []*paneNode) *paneNod
549 549
550 // Priority 2: Top-left pane (geometrically)550 // Priority 2: Top-left pane (geometrically)
551 if topLeft := fsm.findTopLeftPane(leaves); topLeft != nil {551 if topLeft := fsm.findTopLeftPane(leaves); topLeft != nil {
552- log.Printf("[FSM] Selected top-left pane as initial focus")552+ logging.Debug(fmt.Sprintf("[FSM] Selected top-left pane as initial focus"))
553 return topLeft553 return topLeft
554 }554 }
555 555
556 // Priority 3: First pane in tree order556 // Priority 3: First pane in tree order
557- log.Printf("[FSM] Selected first pane as initial focus")557+ logging.Debug(fmt.Sprintf("[FSM] Selected first pane as initial focus"))
558 return leaves[0]558 return leaves[0]
559 }559 }
560 560
@@ -587,7 +587,7 @@ func (fsm *FocusStateMachine) applyInitialFocus(node *paneNode) error {
587 return ErrInvalidTarget587 return ErrInvalidTarget
588 }588 }
589 589
590- log.Printf("[FSM] Applying initial focus to pane %p", node)590+ logging.Debug(fmt.Sprintf("[FSM] Applying initial focus to pane %p", node))
591 591
592 // Apply GTK focus592 // Apply GTK focus
593 if err := fsm.applyGTKFocus(node); err != nil {593 if err := fsm.applyGTKFocus(node); err != nil {
@@ -646,7 +646,7 @@ func (fsm *FocusStateMachine) applyGTKFocus(node *paneNode) error {
646 func (fsm *FocusStateMachine) processQueue() {646 func (fsm *FocusStateMachine) processQueue() {
647 defer fsm.queueProcessor.Done()647 defer fsm.queueProcessor.Done()
648 648
649- log.Printf("[FSM] Focus request queue processor started")649+ logging.Debug(fmt.Sprintf("[FSM] Focus request queue processor started"))
650 650
651 for {651 for {
652 select {652 select {
@@ -657,7 +657,7 @@ func (fsm *FocusStateMachine) processQueue() {
657 // Legacy channel for backward compatibility657 // Legacy channel for backward compatibility
658 fsm.handleFocusRequest(request)658 fsm.handleFocusRequest(request)
659 case <-fsm.shutdownChan:659 case <-fsm.shutdownChan:
660- log.Printf("[FSM] Focus request queue processor shutting down")660+ logging.Debug(fmt.Sprintf("[FSM] Focus request queue processor shutting down"))
661 return661 return
662 }662 }
663 }663 }
@@ -696,7 +696,7 @@ func (fsm *FocusStateMachine) handleFocusRequest(request FocusRequest) {
696 fsm.mu.Lock()696 fsm.mu.Lock()
697 fsm.metrics.FailedRequests++697 fsm.metrics.FailedRequests++
698 fsm.mu.Unlock()698 fsm.mu.Unlock()
699- log.Printf("[FSM] Invalid focus request %s: %v", request.ID, err)699+ logging.Error(fmt.Sprintf("[FSM] Invalid focus request %s: %v", request.ID, err))
700 return700 return
701 }701 }
702 702
@@ -706,13 +706,13 @@ func (fsm *FocusStateMachine) handleFocusRequest(request FocusRequest) {
706 fsm.mu.RUnlock()706 fsm.mu.RUnlock()
707 707
708 if currentActive == request.TargetNode {708 if currentActive == request.TargetNode {
709- log.Printf("[FSM] Focus request %s: already focused on target", request.ID)709+ logging.Debug(fmt.Sprintf("[FSM] Focus request %s: already focused on target", request.ID))
710 fsm.recordProcessTime(time.Since(startTime))710 fsm.recordProcessTime(time.Since(startTime))
711 return711 return
712 }712 }
713 713
714- log.Printf("[FSM] Processing focus request %s: %s -> %p",714+ logging.Debug(fmt.Sprintf("[FSM] Processing focus request %s: %s -> %p",
715- request.ID, request.Source, request.TargetNode)715+ request.ID, request.Source, request.TargetNode))
716 716
717 // GTK operations must run on main thread - always use IdleAdd for simplicity717 // GTK operations must run on main thread - always use IdleAdd for simplicity
718 var err error718 var err error
@@ -728,7 +728,7 @@ func (fsm *FocusStateMachine) handleFocusRequest(request FocusRequest) {
728 fsm.mu.Lock()728 fsm.mu.Lock()
729 fsm.metrics.FailedRequests++729 fsm.metrics.FailedRequests++
730 fsm.mu.Unlock()730 fsm.mu.Unlock()
731- log.Printf("[FSM] Focus request %s failed: %v", request.ID, err)731+ logging.Error(fmt.Sprintf("[FSM] Focus request %s failed: %v", request.ID, err))
732 } else {732 } else {
733 fsm.mu.Lock()733 fsm.mu.Lock()
734 fsm.metrics.SuccessfulRequests++734 fsm.metrics.SuccessfulRequests++
@@ -805,7 +805,7 @@ func (fsm *FocusStateMachine) executeFocusChange(request FocusRequest) error {
805 // Start settling timer805 // Start settling timer
806 fsm.startSettlingTimer()806 fsm.startSettlingTimer()
807 807
808- log.Printf("[FSM] Focus change completed: %p -> %p", oldPane, newPane)808+ logging.Debug(fmt.Sprintf("[FSM] Focus change completed: %p -> %p", oldPane, newPane))
809 return nil809 return nil
810 }810 }
811 811
@@ -866,14 +866,14 @@ func (fsm *FocusStateMachine) verifyCSSState() {
866 }866 }
867 867
868 if fsm.reconcileCount > fsm.maxReconcileAttempts {868 if fsm.reconcileCount > fsm.maxReconcileAttempts {
869- log.Printf("[FSM] WARNING: Reconciliation loop detected, skipping (attempt %d)", fsm.reconcileCount)869+ logging.Warn(fmt.Sprintf("[FSM] WARNING: Reconciliation loop detected, skipping (attempt %d)", fsm.reconcileCount))
870 fsm.mu.Unlock()870 fsm.mu.Unlock()
871 return871 return
872 }872 }
873 fsm.mu.Unlock()873 fsm.mu.Unlock()
874 874
875- log.Printf("[FSM] CSS inconsistencies found: %v", issues)875+ logging.Debug(fmt.Sprintf("[FSM] CSS inconsistencies found: %v", issues))
876- log.Printf("[FSM] Triggering CSS reconciliation (attempt %d)", fsm.reconcileCount)876+ logging.Debug(fmt.Sprintf("[FSM] Triggering CSS reconciliation (attempt %d)", fsm.reconcileCount))
877 877
878 // Track reconciliation in metrics878 // Track reconciliation in metrics
879 fsm.mu.Lock()879 fsm.mu.Lock()
@@ -896,7 +896,7 @@ func (fsm *FocusStateMachine) shouldCoalesce(request FocusRequest) bool {
896 for i := 0; i < fsm.priorityQueue.Len(); i++ {896 for i := 0; i < fsm.priorityQueue.Len(); i++ {
897 pending := (*fsm.priorityQueue)[i]897 pending := (*fsm.priorityQueue)[i]
898 if pending.TargetNode == request.TargetNode {898 if pending.TargetNode == request.TargetNode {
899- log.Printf("[FSM] Coalescing request to same target: %p", request.TargetNode)899+ logging.Debug(fmt.Sprintf("[FSM] Coalescing request to same target: %p", request.TargetNode))
900 return true900 return true
901 }901 }
902 }902 }
@@ -924,7 +924,7 @@ func (fsm *FocusStateMachine) requeue(request FocusRequest) {
924 defer fsm.mu.Unlock()924 defer fsm.mu.Unlock()
925 925
926 heap.Push(fsm.priorityQueue, &request)926 heap.Push(fsm.priorityQueue, &request)
927- log.Printf("[FSM] Requeued request due to higher priority: %s", request.ID)927+ logging.Debug(fmt.Sprintf("[FSM] Requeued request due to higher priority: %s", request.ID))
928 928
929 // Signal that a request is pending929 // Signal that a request is pending
930 select {930 select {
@@ -940,7 +940,7 @@ func (fsm *FocusStateMachine) attachGTKController(node *paneNode) {
940 }940 }
941 941
942 if node.focusControllerToken != 0 {942 if node.focusControllerToken != 0 {
943- log.Printf("[FSM] Controller already attached to pane %p", node)943+ logging.Debug(fmt.Sprintf("[FSM] Controller already attached to pane %p", node))
944 return944 return
945 }945 }
946 946
@@ -965,7 +965,7 @@ func (fsm *FocusStateMachine) attachGTKController(node *paneNode) {
965 }965 }
966 node.lastFocusEnterTime = now966 node.lastFocusEnterTime = now
967 967
968- log.Printf("[FSM] GTK focus enter: %p", node)968+ logging.Debug(fmt.Sprintf("[FSM] GTK focus enter: %p", node))
969 // Don't automatically change focus on GTK enter - let user interactions drive this969 // Don't automatically change focus on GTK enter - let user interactions drive this
970 // This prevents infinite loops with our own focus changes970 // This prevents infinite loops with our own focus changes
971 })971 })
@@ -978,7 +978,7 @@ func (fsm *FocusStateMachine) attachGTKController(node *paneNode) {
978 }978 }
979 node.lastFocusLeaveTime = now979 node.lastFocusLeaveTime = now
980 980
981- log.Printf("[FSM] GTK focus leave: %p", node)981+ logging.Debug(fmt.Sprintf("[FSM] GTK focus leave: %p", node))
982 // Similarly, don't react to GTK leave events automatically982 // Similarly, don't react to GTK leave events automatically
983 })983 })
984 984
@@ -988,7 +988,7 @@ func (fsm *FocusStateMachine) attachGTKController(node *paneNode) {
988 // Store controller pointer as token for later removal988 // Store controller pointer as token for later removal
989 node.focusControllerToken = uintptr(controller.Native())989 node.focusControllerToken = uintptr(controller.Native())
990 if node.focusControllerToken != 0 {990 if node.focusControllerToken != 0 {
991- log.Printf("[FSM] Attached GTK focus controller to pane %p with token %d", node, node.focusControllerToken)991+ logging.Debug(fmt.Sprintf("[FSM] Attached GTK focus controller to pane %p with token %d", node, node.focusControllerToken))
992 }992 }
993 }993 }
994 994
@@ -999,7 +999,7 @@ func (fsm *FocusStateMachine) detachGTKController(node *paneNode, token uintptr)
999 }999 }
1000 1000
1001 if node.focusControllerToken != token {1001 if node.focusControllerToken != token {
1002- log.Printf("[FSM] Token mismatch, skipping detach for pane %p", node)1002+ logging.Debug(fmt.Sprintf("[FSM] Token mismatch, skipping detach for pane %p", node))
1003 return1003 return
1004 }1004 }
1005 1005
@@ -1007,7 +1007,7 @@ func (fsm *FocusStateMachine) detachGTKController(node *paneNode, token uintptr)
1007 1007
1008 // Note: In GTK4, controllers are automatically removed when widget is destroyed1008 // Note: In GTK4, controllers are automatically removed when widget is destroyed
1009 // We just need to clear our token reference1009 // We just need to clear our token reference
1010- log.Printf("[FSM] Detached GTK focus controller from pane %p", node)1010+ logging.Debug(fmt.Sprintf("[FSM] Detached GTK focus controller from pane %p", node))
1011 }1011 }
1012 1012
1013 // attachGTKControllersToAllPanes attaches focus controllers to all existing panes1013 // attachGTKControllersToAllPanes attaches focus controllers to all existing panes
@@ -1021,7 +1021,7 @@ func (fsm *FocusStateMachine) attachGTKControllersToAllPanes() {
1021 fsm.attachGTKController(leaf)1021 fsm.attachGTKController(leaf)
1022 }1022 }
1023 1023
1024- log.Printf("[FSM] Attached GTK focus controllers to %d panes", len(leaves))1024+ logging.Debug(fmt.Sprintf("[FSM] Attached GTK focus controllers to %d panes", len(leaves)))
1025 }1025 }
1026 1026
1027 // FocusDebugInfo contains comprehensive debug information about the focus system1027 // FocusDebugInfo contains comprehensive debug information about the focus system
@@ -1070,7 +1070,7 @@ func (fsm *FocusStateMachine) DumpState() FocusDebugInfo {
1070 1070
1071 // ForceValidation manually triggers CSS consistency check and validation1071 // ForceValidation manually triggers CSS consistency check and validation
1072 func (fsm *FocusStateMachine) ForceValidation() []string {1072 func (fsm *FocusStateMachine) ForceValidation() []string {
1073- log.Printf("[FSM] Force validation requested")1073+ logging.Debug(fmt.Sprintf("[FSM] Force validation requested"))
1074 1074
1075 // Trigger CSS verification1075 // Trigger CSS verification
1076 fsm.verifyCSSState()1076 fsm.verifyCSSState()
@@ -1096,7 +1096,7 @@ func (fsm *FocusStateMachine) ForceValidation() []string {
1096 }1096 }
1097 }1097 }
1098 1098
1099- log.Printf("[FSM] Force validation completed: %d issues found", len(issues))1099+ logging.Debug(fmt.Sprintf("[FSM] Force validation completed: %d issues found", len(issues)))
1100 return issues1100 return issues
1101 }1101 }
1102 1102
@@ -1138,7 +1138,7 @@ func (fsm *FocusStateMachine) ClearHistory() {
1138 defer fsm.mu.Unlock()1138 defer fsm.mu.Unlock()
1139 1139
1140 fsm.historyRing = NewRingBuffer[FocusTransition](50)1140 fsm.historyRing = NewRingBuffer[FocusTransition](50)
1141- log.Printf("[FSM] Focus transition history cleared")1141+ logging.Debug(fmt.Sprintf("[FSM] Focus transition history cleared"))
1142 }1142 }
1143 1143
1144 // GetMetrics returns performance metrics for the focus system1144 // GetMetrics returns performance metrics for the focus system
M internal/app/browser/pane.go +6 -5
@@ -1,11 +1,12 @@
1 package browser1 package browser
2 2
3 import (3 import (
4- "log"4+ "fmt"
5 "time"5 "time"
6 6
7 "github.com/bnema/dumber/internal/app/control"7 "github.com/bnema/dumber/internal/app/control"
8 "github.com/bnema/dumber/internal/app/messaging"8 "github.com/bnema/dumber/internal/app/messaging"
9+ "github.com/bnema/dumber/internal/logging"
9 "github.com/bnema/dumber/pkg/webkit"10 "github.com/bnema/dumber/pkg/webkit"
10 )11 )
11 12
@@ -85,7 +86,7 @@ func (p *BrowserPane) Cleanup() {
85 86
86 if p.shortcutHandler != nil {87 if p.shortcutHandler != nil {
87 p.shortcutHandler.Detach()88 p.shortcutHandler.Detach()
88- log.Printf("[pane-%s] Cleaned up shortcuts", p.id)89+ logging.Debug(fmt.Sprintf("[pane-%s] Cleaned up shortcuts", p.id))
89 }90 }
90 91
91 if p.navigationController != nil {92 if p.navigationController != nil {
@@ -107,9 +108,9 @@ func (p *BrowserPane) Cleanup() {
107 // Destroy WebView to unregister from global registry and release resources108 // Destroy WebView to unregister from global registry and release resources
108 if p.webView != nil && !p.webView.IsDestroyed() {109 if p.webView != nil && !p.webView.IsDestroyed() {
109 if err := p.webView.Destroy(); err != nil {110 if err := p.webView.Destroy(); err != nil {
110- log.Printf("[pane-%s] failed to destroy webview: %v", p.id, err)111+ logging.Error(fmt.Sprintf("[pane-%s] failed to destroy webview: %v", p.id, err))
111 } else {112 } else {
112- log.Printf("[pane-%s] successfully destroyed webview", p.id)113+ logging.Debug(fmt.Sprintf("[pane-%s] successfully destroyed webview", p.id))
113 }114 }
114 }115 }
115 116
@@ -132,6 +133,6 @@ func (p *BrowserPane) CleanupFromWorkspace(wm interface{}) {
132 }); ok {133 }); ok {
133 workspaceManager.removeFromMaps(p.webView)134 workspaceManager.removeFromMaps(p.webView)
134 workspaceManager.removeFromAppPanes(p)135 workspaceManager.removeFromAppPanes(p)
135- log.Printf("[pane-%s] Cleaned up from workspace tracking", p.id)136+ logging.Debug(fmt.Sprintf("[pane-%s] Cleaned up from workspace tracking", p.id))
136 }137 }
137 }138 }
M internal/app/browser/shortcuts.go +6 -6
@@ -3,10 +3,10 @@ package browser
3 import (3 import (
4 "encoding/json"4 "encoding/json"
5 "fmt"5 "fmt"
6- "log"
7 6
8 "github.com/bnema/dumber/internal/app/control"7 "github.com/bnema/dumber/internal/app/control"
9 "github.com/bnema/dumber/internal/config"8 "github.com/bnema/dumber/internal/config"
9+ "github.com/bnema/dumber/internal/logging"
10 "github.com/bnema/dumber/pkg/webkit"10 "github.com/bnema/dumber/pkg/webkit"
11 )11 )
12 12
@@ -71,7 +71,7 @@ func (s *ShortcutHandler) RegisterShortcuts() {
71 if !s.isActivePane() {71 if !s.isActivePane() {
72 return72 return
73 }73 }
74- log.Printf("Shortcut: Reload page")74+ logging.Debug("Shortcut: Reload page")
75 _ = s.webView.Reload()75 _ = s.webView.Reload()
76 })76 })
77 77
@@ -79,7 +79,7 @@ func (s *ShortcutHandler) RegisterShortcuts() {
79 if !s.isActivePane() {79 if !s.isActivePane() {
80 return80 return
81 }81 }
82- log.Printf("Shortcut: Hard reload page")82+ logging.Debug("Shortcut: Hard reload page")
83 _ = s.webView.ReloadBypassCache()83 _ = s.webView.ReloadBypassCache()
84 })84 })
85 85
@@ -87,7 +87,7 @@ func (s *ShortcutHandler) RegisterShortcuts() {
87 if !s.isActivePane() {87 if !s.isActivePane() {
88 return88 return
89 }89 }
90- log.Printf("Shortcut: F5 reload")90+ logging.Debug("Shortcut: F5 reload")
91 _ = s.webView.Reload()91 _ = s.webView.Reload()
92 })92 })
93 93
@@ -117,7 +117,7 @@ func (s *ShortcutHandler) exposeWorkspaceConfig() {
117 117
118 payload, err := json.Marshal(s.config.Workspace)118 payload, err := json.Marshal(s.config.Workspace)
119 if err != nil {119 if err != nil {
120- log.Printf("Shortcut: failed to marshal workspace config: %v", err)120+ logging.Error(fmt.Sprintf("Shortcut: failed to marshal workspace config: %v", err))
121 return121 return
122 }122 }
123 123
@@ -128,7 +128,7 @@ func (s *ShortcutHandler) exposeWorkspaceConfig() {
128 })();`, string(payload))128 })();`, string(payload))
129 129
130 if err := s.webView.InjectScript(script); err != nil {130 if err := s.webView.InjectScript(script); err != nil {
131- log.Printf("Shortcut: failed to expose workspace config: %v", err)131+ logging.Error(fmt.Sprintf("Shortcut: failed to expose workspace config: %v", err))
132 }132 }
133 }133 }
134 134
M internal/app/browser/stacked_panes.go +34 -35
@@ -3,14 +3,13 @@ package browser
3 import (3 import (
4 "errors"4 "errors"
5 "fmt"5 "fmt"
6- "log"
7 "sync/atomic"6 "sync/atomic"
8 "time"7 "time"
9 8
9+ "github.com/bnema/dumber/internal/logging"
10+ "github.com/bnema/dumber/pkg/webkit"
10 "github.com/diamondburned/gotk4/pkg/gdk/v4"11 "github.com/diamondburned/gotk4/pkg/gdk/v4"
11 "github.com/diamondburned/gotk4/pkg/gtk/v4"12 "github.com/diamondburned/gotk4/pkg/gtk/v4"
12-
13- "github.com/bnema/dumber/pkg/webkit"
14 )13 )
15 14
16 const (15 const (
@@ -139,8 +138,8 @@ func (spm *StackedPaneManager) addPaneToExistingStack(target, newLeaf *paneNode)
139 138
140 // Insert the new pane right after the current pane139 // Insert the new pane right after the current pane
141 insertIndex := currentIndex + 1140 insertIndex := currentIndex + 1
142- log.Printf("[workspace] adding to existing stack: currentIndex=%d insertIndex=%d stackSize=%d",141+ logging.Debug(fmt.Sprintf("[workspace] adding to existing stack: currentIndex=%d insertIndex=%d stackSize=%d",
143- currentIndex, insertIndex, len(stackNode.stackedPanes))142+ currentIndex, insertIndex, len(stackNode.stackedPanes)))
144 143
145 // Set parent relationship144 // Set parent relationship
146 newLeaf.parent = stackNode145 newLeaf.parent = stackNode
@@ -179,7 +178,7 @@ func (spm *StackedPaneManager) addPaneToExistingStack(target, newLeaf *paneNode)
179 if box, ok := stackNode.stackWrapper.(*gtk.Box); ok && newLeaf.container != nil && newLeaf.titleBar != nil {178 if box, ok := stackNode.stackWrapper.(*gtk.Box); ok && newLeaf.container != nil && newLeaf.titleBar != nil {
180 box.InsertChildAfter(newLeaf.container, newLeaf.titleBar)179 box.InsertChildAfter(newLeaf.container, newLeaf.titleBar)
181 }180 }
182- log.Printf("[workspace] inserted widgets at position %d (after widget %p)", insertIndex, insertAfterWidget)181+ logging.Debug(fmt.Sprintf("[workspace] inserted widgets at position %d (after widget %p)", insertIndex, insertAfterWidget))
183 } else {182 } else {
184 // Insert at the beginning (insertIndex = 0)183 // Insert at the beginning (insertIndex = 0)
185 if box, ok := stackNode.stackWrapper.(*gtk.Box); ok && newLeaf.container != nil {184 if box, ok := stackNode.stackWrapper.(*gtk.Box); ok && newLeaf.container != nil {
@@ -188,7 +187,7 @@ func (spm *StackedPaneManager) addPaneToExistingStack(target, newLeaf *paneNode)
188 if box, ok := stackNode.stackWrapper.(*gtk.Box); ok && newLeaf.titleBar != nil {187 if box, ok := stackNode.stackWrapper.(*gtk.Box); ok && newLeaf.titleBar != nil {
189 box.Prepend(newLeaf.titleBar)188 box.Prepend(newLeaf.titleBar)
190 }189 }
191- log.Printf("[workspace] prepended widgets at position 0")190+ logging.Debug("[workspace] prepended widgets at position 0")
192 }191 }
193 192
194 return stackNode, insertIndex, nil193 return stackNode, insertIndex, nil
@@ -196,7 +195,7 @@ func (spm *StackedPaneManager) addPaneToExistingStack(target, newLeaf *paneNode)
196 195
197 // convertToStackedContainer converts a simple pane to a stacked container196 // convertToStackedContainer converts a simple pane to a stacked container
198 func (spm *StackedPaneManager) convertToStackedContainer(target, newLeaf *paneNode) (*paneNode, int, error) {197 func (spm *StackedPaneManager) convertToStackedContainer(target, newLeaf *paneNode) (*paneNode, int, error) {
199- log.Printf("[workspace] converting pane to stacked: %p", target)198+ logging.Debug(fmt.Sprintf("[workspace] converting pane to stacked: %p", target))
200 199
201 // Create the wrapper container - this is what will be used by splitNode200 // Create the wrapper container - this is what will be used by splitNode
202 stackWrapperContainer := gtk.NewBox(gtk.OrientationVertical, 0)201 stackWrapperContainer := gtk.NewBox(gtk.OrientationVertical, 0)
@@ -292,11 +291,11 @@ func (spm *StackedPaneManager) convertToStackedContainer(target, newLeaf *paneNo
292 if newLeaf.container != nil {291 if newLeaf.container != nil {
293 parentWidget := webkit.WidgetGetParent(newLeaf.container)292 parentWidget := webkit.WidgetGetParent(newLeaf.container)
294 if parentWidget != nil {293 if parentWidget != nil {
295- log.Printf("[workspace] unparenting new pane container %p from parent %p before stack append", newLeaf.container, parentWidget)294+ logging.Debug(fmt.Sprintf("[workspace] unparenting new pane container %p from parent %p before stack append", newLeaf.container, parentWidget))
296 webkit.WidgetUnparent(newLeaf.container)295 webkit.WidgetUnparent(newLeaf.container)
297 // Verify unparent succeeded296 // Verify unparent succeeded
298 if finalParent := webkit.WidgetGetParent(newLeaf.container); finalParent != nil {297 if finalParent := webkit.WidgetGetParent(newLeaf.container); finalParent != nil {
299- log.Printf("[workspace] WARNING: container %p still has parent %p after unparent", newLeaf.container, finalParent)298+ logging.Warn(fmt.Sprintf("[workspace] container %p still has parent %p after unparent", newLeaf.container, finalParent))
300 }299 }
301 }300 }
302 }301 }
@@ -369,7 +368,7 @@ func (spm *StackedPaneManager) finalizeStackCreation(stackNode, newLeaf *paneNod
369 currentTitle := currentlyActivePane.pane.webView.GetTitle()368 currentTitle := currentlyActivePane.pane.webView.GetTitle()
370 if currentTitle != "" {369 if currentTitle != "" {
371 spm.updateTitleBarLabel(currentlyActivePane, currentTitle)370 spm.updateTitleBarLabel(currentlyActivePane, currentTitle)
372- log.Printf("[workspace] updated title bar for pane becoming inactive during stack creation: %s", currentTitle)371+ logging.Debug(fmt.Sprintf("[workspace] updated title bar for pane becoming inactive during stack creation: %s", currentTitle))
373 }372 }
374 }373 }
375 }374 }
@@ -381,38 +380,38 @@ func (spm *StackedPaneManager) finalizeStackCreation(stackNode, newLeaf *paneNod
381 // Set focus on the new pane synchronously380 // Set focus on the new pane synchronously
382 spm.wm.SetActivePane(newLeaf, SourceSplit)381 spm.wm.SetActivePane(newLeaf, SourceSplit)
383 382
384- log.Printf("[workspace] stacked new pane: stackNode=%p newLeaf=%p stackSize=%d activeIndex=%d insertIndex=%d",383+ logging.Debug(fmt.Sprintf("[workspace] stacked new pane: stackNode=%p newLeaf=%p stackSize=%d activeIndex=%d insertIndex=%d",
385- stackNode, newLeaf, len(stackNode.stackedPanes), stackNode.activeStackIndex, insertIndex)384+ stackNode, newLeaf, len(stackNode.stackedPanes), stackNode.activeStackIndex, insertIndex))
386 return newLeaf, nil385 return newLeaf, nil
387 }386 }
388 387
389 // UpdateStackVisibility updates the visibility of panes in a stack388 // UpdateStackVisibility updates the visibility of panes in a stack
390 func (spm *StackedPaneManager) UpdateStackVisibility(stackNode *paneNode) {389 func (spm *StackedPaneManager) UpdateStackVisibility(stackNode *paneNode) {
391- log.Printf("[workspace] updateStackVisibility called: stackNode=%p", stackNode)390+ logging.Debug(fmt.Sprintf("[workspace] updateStackVisibility called: stackNode=%p", stackNode))
392 391
393 if stackNode == nil {392 if stackNode == nil {
394- log.Printf("[workspace] updateStackVisibility aborted: stackNode is nil")393+ logging.Debug("[workspace] updateStackVisibility aborted: stackNode is nil")
395 return394 return
396 }395 }
397 396
398 if !stackNode.isStacked {397 if !stackNode.isStacked {
399- log.Printf("[workspace] updateStackVisibility aborted: stackNode.isStacked=%v", stackNode.isStacked)398+ logging.Debug(fmt.Sprintf("[workspace] updateStackVisibility aborted: stackNode.isStacked=%v", stackNode.isStacked))
400 return399 return
401 }400 }
402 401
403 if len(stackNode.stackedPanes) == 0 {402 if len(stackNode.stackedPanes) == 0 {
404- log.Printf("[workspace] updateStackVisibility aborted: stackedPanes empty, len=%d", len(stackNode.stackedPanes))403+ logging.Debug(fmt.Sprintf("[workspace] updateStackVisibility aborted: stackedPanes empty, len=%d", len(stackNode.stackedPanes)))
405 return404 return
406 }405 }
407 406
408 activeIndex := stackNode.activeStackIndex407 activeIndex := stackNode.activeStackIndex
409 if activeIndex < 0 || activeIndex >= len(stackNode.stackedPanes) {408 if activeIndex < 0 || activeIndex >= len(stackNode.stackedPanes) {
410- log.Printf("[workspace] updateStackVisibility: correcting activeIndex from %d to 0", activeIndex)409+ logging.Debug(fmt.Sprintf("[workspace] updateStackVisibility: correcting activeIndex from %d to 0", activeIndex))
411 activeIndex = 0410 activeIndex = 0
412 stackNode.activeStackIndex = activeIndex411 stackNode.activeStackIndex = activeIndex
413 }412 }
414 413
415- log.Printf("[workspace] updating stack visibility: activeIndex=%d stackSize=%d", activeIndex, len(stackNode.stackedPanes))414+ logging.Debug(fmt.Sprintf("[workspace] updating stack visibility: activeIndex=%d stackSize=%d", activeIndex, len(stackNode.stackedPanes)))
416 415
417 // CRITICAL: Process ALL panes in a single pass to prevent flickering416 // CRITICAL: Process ALL panes in a single pass to prevent flickering
418 for i, pane := range stackNode.stackedPanes {417 for i, pane := range stackNode.stackedPanes {
@@ -424,7 +423,7 @@ func (spm *StackedPaneManager) UpdateStackVisibility(stackNode *paneNode) {
424 if pane.titleBar != nil {423 if pane.titleBar != nil {
425 webkit.WidgetSetVisible(pane.titleBar, false) // ABSOLUTE RULE: never visible for active pane424 webkit.WidgetSetVisible(pane.titleBar, false) // ABSOLUTE RULE: never visible for active pane
426 }425 }
427- log.Printf("[workspace] active pane %d: container=visible, titleBar=HIDDEN", i)426+ logging.Debug(fmt.Sprintf("[workspace] active pane %d: container=visible, titleBar=HIDDEN", i))
428 } else {427 } else {
429 // Inactive panes: hide container, show title bar428 // Inactive panes: hide container, show title bar
430 if pane.container != nil {429 if pane.container != nil {
@@ -474,7 +473,7 @@ func (spm *StackedPaneManager) NavigateStack(direction string) bool {
474 // This is more reliable than trying to match panes473 // This is more reliable than trying to match panes
475 currentIndex := stackNode.activeStackIndex474 currentIndex := stackNode.activeStackIndex
476 if currentIndex < 0 || currentIndex >= len(stackNode.stackedPanes) {475 if currentIndex < 0 || currentIndex >= len(stackNode.stackedPanes) {
477- log.Printf("[workspace] navigateStack: invalid activeStackIndex=%d, resetting to 0", currentIndex)476+ logging.Debug(fmt.Sprintf("[workspace] navigateStack: invalid activeStackIndex=%d, resetting to 0", currentIndex))
478 currentIndex = 0477 currentIndex = 0
479 stackNode.activeStackIndex = 0478 stackNode.activeStackIndex = 0
480 }479 }
@@ -509,7 +508,7 @@ func (spm *StackedPaneManager) NavigateStack(direction string) bool {
509 currentTitle := currentActivePane.pane.webView.GetTitle()508 currentTitle := currentActivePane.pane.webView.GetTitle()
510 if currentTitle != "" {509 if currentTitle != "" {
511 spm.updateTitleBarLabel(currentActivePane, currentTitle)510 spm.updateTitleBarLabel(currentActivePane, currentTitle)
512- log.Printf("[workspace] updated title bar for pane transitioning to INACTIVE: %s", currentTitle)511+ logging.Debug(fmt.Sprintf("[workspace] updated title bar for pane transitioning to INACTIVE: %s", currentTitle))
513 }512 }
514 }513 }
515 514
@@ -521,8 +520,8 @@ func (spm *StackedPaneManager) NavigateStack(direction string) bool {
521 newActivePane := stackNode.stackedPanes[newIndex]520 newActivePane := stackNode.stackedPanes[newIndex]
522 spm.wm.SetActivePane(newActivePane, SourceStackNav)521 spm.wm.SetActivePane(newActivePane, SourceStackNav)
523 522
524- log.Printf("[workspace] navigated stack: direction=%s from=%d to=%d stackSize=%d",523+ logging.Debug(fmt.Sprintf("[workspace] navigated stack: direction=%s from=%d to=%d stackSize=%d",
525- direction, currentIndex, newIndex, len(stackNode.stackedPanes))524+ direction, currentIndex, newIndex, len(stackNode.stackedPanes)))
526 return true525 return true
527 }526 }
528 527
@@ -586,10 +585,10 @@ func (spm *StackedPaneManager) UpdateTitleBar(webView *webkit.WebView, title str
586 if paneIndex != -1 && paneIndex != activeIndex {585 if paneIndex != -1 && paneIndex != activeIndex {
587 // This is an INACTIVE pane - safe to update title bar586 // This is an INACTIVE pane - safe to update title bar
588 spm.updateTitleBarLabel(node, title)587 spm.updateTitleBarLabel(node, title)
589- log.Printf("[workspace] updated title bar for INACTIVE WebView %d: %s", webView.ID(), title)588+ logging.Debug(fmt.Sprintf("[workspace] updated title bar for INACTIVE WebView %d: %s", webView.ID(), title))
590 } else {589 } else {
591 // This is the ACTIVE pane - title bar should remain hidden590 // This is the ACTIVE pane - title bar should remain hidden
592- log.Printf("[workspace] skipped title bar update for ACTIVE WebView %d: %s", webView.ID(), title)591+ logging.Debug(fmt.Sprintf("[workspace] skipped title bar update for ACTIVE WebView %d: %s", webView.ID(), title))
593 }592 }
594 }593 }
595 }594 }
@@ -609,7 +608,7 @@ func (spm *StackedPaneManager) updateTitleBarLabel(node *paneNode, title string)
609 // Create new title bar with updated title and favicon608 // Create new title bar with updated title and favicon
610 newTitleBar := spm.createTitleBarWithTitle(title, pageURL)609 newTitleBar := spm.createTitleBarWithTitle(title, pageURL)
611 if newTitleBar == nil {610 if newTitleBar == nil {
612- log.Printf("[workspace] failed to create new title bar")611+ logging.Error("[workspace] failed to create new title bar")
613 return612 return
614 }613 }
615 614
@@ -655,7 +654,7 @@ func (spm *StackedPaneManager) updateTitleBarLabel(node *paneNode, title string)
655 }654 }
656 }655 }
657 656
658- log.Printf("[workspace] replaced title bar for pane %d: %p", paneIndex, node)657+ logging.Debug(fmt.Sprintf("[workspace] replaced title bar for pane %d: %p", paneIndex, node))
659 }658 }
660 }659 }
661 660
@@ -680,7 +679,7 @@ func (spm *StackedPaneManager) createTitleBarWithTitle(title string, pageURL str
680 if pageURL != "" && spm.wm != nil && spm.wm.app != nil && spm.wm.app.faviconService != nil {679 if pageURL != "" && spm.wm != nil && spm.wm.app != nil && spm.wm.app.faviconService != nil {
681 spm.wm.app.faviconService.GetFaviconTexture(pageURL, func(texture *gdk.Texture, err error) {680 spm.wm.app.faviconService.GetFaviconTexture(pageURL, func(texture *gdk.Texture, err error) {
682 if err != nil {681 if err != nil {
683- log.Printf("[favicon] Failed to load favicon for title bar: %s - %v", pageURL, err)682+ logging.Warn(fmt.Sprintf("[favicon] Failed to load favicon for title bar: %s - %v", pageURL, err))
684 return683 return
685 }684 }
686 685
@@ -753,7 +752,7 @@ func (spm *StackedPaneManager) CloseStackedPane(node *paneNode) error {
753 return errors.New("node not found in stack")752 return errors.New("node not found in stack")
754 }753 }
755 754
756- log.Printf("[workspace] closing stacked pane: index=%d stackSize=%d", nodeIndex, len(stackNode.stackedPanes))755+ logging.Debug(fmt.Sprintf("[workspace] closing stacked pane: index=%d stackSize=%d", nodeIndex, len(stackNode.stackedPanes)))
757 756
758 // Detach hover/focus controllers before tearing down widgets to avoid GTK757 // Detach hover/focus controllers before tearing down widgets to avoid GTK
759 // callbacks referencing freed memory during destruction.758 // callbacks referencing freed memory during destruction.
@@ -901,7 +900,7 @@ func (spm *StackedPaneManager) CloseStackedPane(node *paneNode) error {
901 stackNode.stackWrapper = nil900 stackNode.stackWrapper = nil
902 stackNode.stackedPanes = nil901 stackNode.stackedPanes = nil
903 902
904- log.Printf("[workspace] converted stack back to regular pane")903+ logging.Debug("[workspace] converted stack back to regular pane")
905 return nil904 return nil
906 905
907 default:906 default:
@@ -922,8 +921,8 @@ func (spm *StackedPaneManager) CloseStackedPane(node *paneNode) error {
922 generation := spm.wm.nextCleanupGeneration()921 generation := spm.wm.nextCleanupGeneration()
923 spm.wm.cleanupPane(node, generation)922 spm.wm.cleanupPane(node, generation)
924 923
925- log.Printf("[workspace] closed pane from stack: remaining=%d activeIndex=%d",924+ logging.Debug(fmt.Sprintf("[workspace] closed pane from stack: remaining=%d activeIndex=%d",
926- len(stackNode.stackedPanes), stackNode.activeStackIndex)925+ len(stackNode.stackedPanes), stackNode.activeStackIndex))
927 return nil926 return nil
928 }927 }
929 }928 }
@@ -942,8 +941,8 @@ func (spm *StackedPaneManager) handleTitleBarClick(titleBarID uint64) {
942 if p == pane {941 if p == pane {
943 // Only switch if it's not already active942 // Only switch if it's not already active
944 if i != stackNode.activeStackIndex {943 if i != stackNode.activeStackIndex {
945- log.Printf("[workspace] title bar clicked: switching from pane %d to pane %d",944+ logging.Debug(fmt.Sprintf("[workspace] title bar clicked: switching from pane %d to pane %d",
946- stackNode.activeStackIndex, i)945+ stackNode.activeStackIndex, i))
947 946
948 // Update title bar for the pane transitioning from ACTIVE to INACTIVE947 // Update title bar for the pane transitioning from ACTIVE to INACTIVE
949 currentActivePane := stackNode.stackedPanes[stackNode.activeStackIndex]948 currentActivePane := stackNode.stackedPanes[stackNode.activeStackIndex]
M internal/app/browser/webview.go +41 -41
@@ -4,7 +4,6 @@ import (
4 "context"4 "context"
5 "encoding/json"5 "encoding/json"
6 "fmt"6 "fmt"
7- "log"
8 "os"7 "os"
9 "path/filepath"8 "path/filepath"
10 "time"9 "time"
@@ -14,6 +13,7 @@ import (
14 "github.com/bnema/dumber/internal/app/messaging"13 "github.com/bnema/dumber/internal/app/messaging"
15 "github.com/bnema/dumber/internal/config"14 "github.com/bnema/dumber/internal/config"
16 "github.com/bnema/dumber/internal/filtering"15 "github.com/bnema/dumber/internal/filtering"
16+ "github.com/bnema/dumber/internal/logging"
17 "github.com/bnema/dumber/internal/services"17 "github.com/bnema/dumber/internal/services"
18 "github.com/bnema/dumber/pkg/webkit"18 "github.com/bnema/dumber/pkg/webkit"
19 )19 )
@@ -43,19 +43,19 @@ func (app *BrowserApp) buildWebkitConfig() (*webkit.Config, error) {
43 43
44 // Build WebKit configuration with data directories for persistence44 // Build WebKit configuration with data directories for persistence
45 cfg := &webkit.Config{45 cfg := &webkit.Config{
46- UserAgent: buildUserAgent(app.config),46+ UserAgent: buildUserAgent(app.config),
47- EnableJavaScript: true,47+ EnableJavaScript: true,
48- EnableWebGL: true,48+ EnableWebGL: true,
49- EnableMediaStream: true,49+ EnableMediaStream: true,
50- HardwareAcceleration: true,50+ HardwareAcceleration: true,
51- DefaultFontSize: app.config.Appearance.DefaultFontSize,51+ DefaultFontSize: app.config.Appearance.DefaultFontSize,
52- MinimumFontSize: 8,52+ MinimumFontSize: 8,
53- EnablePageCache: true, // Instant back/forward navigation (bfcache)53+ EnablePageCache: true, // Instant back/forward navigation (bfcache)
54- EnableSmoothScrolling: true, // Smooth scrolling animations54+ EnableSmoothScrolling: true, // Smooth scrolling animations
55- DataDir: webkitData,55+ DataDir: webkitData,
56- CacheDir: webkitCache,56+ CacheDir: webkitCache,
57- AppearanceConfigJSON: app.buildAppearanceConfigJSON(),57+ AppearanceConfigJSON: app.buildAppearanceConfigJSON(),
58- CreateWindow: true, // Default to creating a window for standalone WebViews58+ CreateWindow: true, // Default to creating a window for standalone WebViews
59 }59 }
60 60
61 return cfg, nil61 return cfg, nil
@@ -102,7 +102,7 @@ func (app *BrowserApp) buildAppearanceConfigJSON() string {
102 102
103 payload, err := json.Marshal(paletteConfig)103 payload, err := json.Marshal(paletteConfig)
104 if err != nil {104 if err != nil {
105- log.Printf("[webview] Failed to marshal palette config: %v", err)105+ logging.Error(fmt.Sprintf("[webview] Failed to marshal palette config: %v", err))
106 return ""106 return ""
107 }107 }
108 108
@@ -149,7 +149,7 @@ func (app *BrowserApp) createPaneForView(view *webkit.WebView) (*BrowserPane, er
149 pane.initializeGUITracking()149 pane.initializeGUITracking()
150 150
151 // GUI manager will be loaded on-demand when needed151 // GUI manager will be loaded on-demand when needed
152- log.Printf("[webview] Created pane for webview: %d", view.ID())152+ logging.Debug(fmt.Sprintf("[webview] Created pane for webview: %d", view.ID()))
153 153
154 app.attachPaneHandlers(pane)154 app.attachPaneHandlers(pane)
155 return pane, nil155 return pane, nil
@@ -173,7 +173,7 @@ func (app *BrowserApp) attachPaneHandlers(pane *BrowserPane) {
173 go func(url, title string) {173 go func(url, title string) {
174 ctx := context.Background()174 ctx := context.Background()
175 if err := app.browserService.UpdatePageTitle(ctx, url, title); err != nil {175 if err := app.browserService.UpdatePageTitle(ctx, url, title); err != nil {
176- log.Printf("Warning: failed to update page title: %v", err)176+ logging.Warn(fmt.Sprintf("Warning: failed to update page title: %v", err))
177 }177 }
178 }(url, title)178 }(url, title)
179 179
@@ -210,7 +210,7 @@ func (app *BrowserApp) attachPaneHandlers(pane *BrowserPane) {
210 node := app.workspace.GetNodeForWebView(pane.webView)210 node := app.workspace.GetNodeForWebView(pane.webView)
211 if node != nil {211 if node != nil {
212 app.workspace.setupPopupHandling(pane.webView, node)212 app.workspace.setupPopupHandling(pane.webView, node)
213- log.Printf("[webview] Setup native popup handling for WebView ID: %d", pane.webView.ID())213+ logging.Debug(fmt.Sprintf("[webview] Setup native popup handling for WebView ID: %d", pane.webView.ID()))
214 }214 }
215 }215 }
216 }216 }
@@ -245,7 +245,7 @@ func shouldFocusForScriptMessage(payload string) bool {
245 245
246 // createWebView creates and configures the WebView246 // createWebView creates and configures the WebView
247 func (app *BrowserApp) createWebView() error {247 func (app *BrowserApp) createWebView() error {
248- log.Printf("Creating WebView (native backend expected: %v)", webkit.IsNativeAvailable())248+ logging.Info(fmt.Sprintf("Creating WebView (native backend expected: %v)", webkit.IsNativeAvailable()))
249 249
250 cfg, err := app.buildWebkitConfig()250 cfg, err := app.buildWebkitConfig()
251 if err != nil {251 if err != nil {
@@ -281,7 +281,7 @@ func (app *BrowserApp) createWebView() error {
281 281
282 // Initialize tab system (creates root container with tab bar and first tab)282 // Initialize tab system (creates root container with tab bar and first tab)
283 if err := app.tabManager.Initialize(initialURL); err != nil {283 if err := app.tabManager.Initialize(initialURL); err != nil {
284- log.Printf("ERROR: failed to initialize tab manager: %v", err)284+ logging.Error(fmt.Sprintf("ERROR: failed to initialize tab manager: %v", err))
285 return err285 return err
286 }286 }
287 287
@@ -289,9 +289,9 @@ func (app *BrowserApp) createWebView() error {
289 rootContainer := app.tabManager.GetRootContainer()289 rootContainer := app.tabManager.GetRootContainer()
290 if rootContainer != nil {290 if rootContainer != nil {
291 window.SetChild(rootContainer)291 window.SetChild(rootContainer)
292- log.Printf("[tabs] Tab system initialized and set as window content")292+ logging.Info(fmt.Sprintf("[tabs] Tab system initialized and set as window content"))
293 } else {293 } else {
294- log.Printf("ERROR: tab manager root container is nil")294+ logging.Error(fmt.Sprintf("ERROR: tab manager root container is nil"))
295 return fmt.Errorf("failed to get tab manager root container")295 return fmt.Errorf("failed to get tab manager root container")
296 }296 }
297 }297 }
@@ -300,9 +300,9 @@ func (app *BrowserApp) createWebView() error {
300 if window != nil {300 if window != nil {
301 app.windowShortcutHandler = NewWindowShortcutHandler(window, app)301 app.windowShortcutHandler = NewWindowShortcutHandler(window, app)
302 if app.windowShortcutHandler != nil {302 if app.windowShortcutHandler != nil {
303- log.Printf("Window-level global shortcuts initialized")303+ logging.Info(fmt.Sprintf("Window-level global shortcuts initialized"))
304 } else {304 } else {
305- log.Printf("Warning: failed to initialize window-level shortcuts")305+ logging.Warn(fmt.Sprintf("Warning: failed to initialize window-level shortcuts"))
306 }306 }
307 }307 }
308 308
@@ -310,18 +310,18 @@ func (app *BrowserApp) createWebView() error {
310 310
311 // Initialize FaviconService after WebView is created311 // Initialize FaviconService after WebView is created
312 if err := app.initializeFaviconService(); err != nil {312 if err := app.initializeFaviconService(); err != nil {
313- log.Printf("Warning: failed to initialize favicon service: %v", err)313+ logging.Warn(fmt.Sprintf("Warning: failed to initialize favicon service: %v", err))
314 // Continue without favicon service - not critical314 // Continue without favicon service - not critical
315 }315 }
316 316
317 // Apply initial zoom and show window317 // Apply initial zoom and show window
318 app.zoomController.ApplyInitialZoom()318 app.zoomController.ApplyInitialZoom()
319 319
320- log.Printf("Showing WebView window…")320+ logging.Info(fmt.Sprintf("Showing WebView window…"))
321 if err := view.Show(); err != nil {321 if err := view.Show(); err != nil {
322- log.Printf("Warning: failed to show WebView: %v", err)322+ logging.Warn(fmt.Sprintf("Warning: failed to show WebView: %v", err))
323 } else if !webkit.IsNativeAvailable() {323 } else if !webkit.IsNativeAvailable() {
324- log.Printf("Notice: running without webkit_cgo tag — no native window will be displayed.")324+ logging.Info(fmt.Sprintf("Notice: running without webkit_cgo tag — no native window will be displayed."))
325 }325 }
326 326
327 return nil327 return nil
@@ -362,27 +362,27 @@ func (app *BrowserApp) initializeFaviconService() error {
362 }362 }
363 363
364 app.faviconService = faviconService364 app.faviconService = faviconService
365- log.Printf("[favicon] FaviconService initialized successfully")365+ logging.Info(fmt.Sprintf("[favicon] FaviconService initialized successfully"))
366 return nil366 return nil
367 }367 }
368 368
369 // setupContentBlocking initializes the content blocking system with proper timing369 // setupContentBlocking initializes the content blocking system with proper timing
370 func (app *BrowserApp) setupContentBlocking() error {370 func (app *BrowserApp) setupContentBlocking() error {
371- log.Printf("Initializing content blocking system...")371+ logging.Info(fmt.Sprintf("Initializing content blocking system..."))
372 372
373 // Enable WebKit debug logging if requested373 // Enable WebKit debug logging if requested
374 webkit.SetupWebKitDebugLogging()374 webkit.SetupWebKitDebugLogging()
375 375
376 defer func() {376 defer func() {
377 if r := recover(); r != nil {377 if r := recover(); r != nil {
378- log.Printf("Content blocking setup panic recovered: %v", r)378+ logging.Error(fmt.Sprintf("Content blocking setup panic recovered: %v", r))
379 }379 }
380 }()380 }()
381 381
382 // Setup filter system382 // Setup filter system
383 filterManager, err := filtering.SetupFilterSystem()383 filterManager, err := filtering.SetupFilterSystem()
384 if err != nil {384 if err != nil {
385- log.Printf("Warning: Failed to setup filter system: %v", err)385+ logging.Warn(fmt.Sprintf("Warning: Failed to setup filter system: %v", err))
386 return nil // Don't fail browser startup, continue without filters386 return nil // Don't fail browser startup, continue without filters
387 }387 }
388 388
@@ -392,14 +392,14 @@ func (app *BrowserApp) setupContentBlocking() error {
392 // Get data directory for content blocking service392 // Get data directory for content blocking service
393 dataDir, err := config.GetDataDir()393 dataDir, err := config.GetDataDir()
394 if err != nil {394 if err != nil {
395- log.Printf("Warning: Failed to get data dir for content blocking: %v", err)395+ logging.Warn(fmt.Sprintf("Warning: Failed to get data dir for content blocking: %v", err))
396 return nil396 return nil
397 }397 }
398 398
399 // Create content blocking service399 // Create content blocking service
400 cbService, err := filtering.NewContentBlockingService(dataDir+"/webkit", filterManager)400 cbService, err := filtering.NewContentBlockingService(dataDir+"/webkit", filterManager)
401 if err != nil {401 if err != nil {
402- log.Printf("Warning: Failed to create content blocking service: %v", err)402+ logging.Warn(fmt.Sprintf("Warning: Failed to create content blocking service: %v", err))
403 return nil403 return nil
404 }404 }
405 app.contentBlockingService = cbService405 app.contentBlockingService = cbService
@@ -407,36 +407,36 @@ func (app *BrowserApp) setupContentBlocking() error {
407 // Set up callback to apply network filters when they become ready407 // Set up callback to apply network filters when they become ready
408 // This callback will be called from the async filter loading process408 // This callback will be called from the async filter loading process
409 filterManager.SetFiltersReadyCallback(func() {409 filterManager.SetFiltersReadyCallback(func() {
410- log.Printf("[filtering] Filters ready, distributing to all WebViews...")410+ logging.Info(fmt.Sprintf("[filtering] Filters ready, distributing to all WebViews..."))
411 411
412 // Get the WebKit JSON rules from filter manager412 // Get the WebKit JSON rules from filter manager
413 filterJSON, err := filterManager.GetNetworkFilters()413 filterJSON, err := filterManager.GetNetworkFilters()
414 if err != nil {414 if err != nil {
415- log.Printf("[filtering] Failed to get network filters: %v", err)415+ logging.Error(fmt.Sprintf("[filtering] Failed to get network filters: %v", err))
416 return416 return
417 }417 }
418 418
419 if len(filterJSON) == 0 {419 if len(filterJSON) == 0 {
420- log.Printf("[filtering] No network filters to apply")420+ logging.Info(fmt.Sprintf("[filtering] No network filters to apply"))
421 return421 return
422 }422 }
423 423
424- log.Printf("[filtering] Got %d bytes of WebKit JSON rules", len(filterJSON))424+ logging.Debug(fmt.Sprintf("[filtering] Got %d bytes of WebKit JSON rules", len(filterJSON)))
425 425
426 // Signal the service that filters are ready - it will apply to all registered WebViews426 // Signal the service that filters are ready - it will apply to all registered WebViews
427 cbService.SetFiltersReady(filterJSON)427 cbService.SetFiltersReady(filterJSON)
428- log.Printf("[filtering] Content blocking enabled for all WebViews")428+ logging.Info(fmt.Sprintf("[filtering] Content blocking enabled for all WebViews"))
429 })429 })
430 430
431 // Start async filter loading431 // Start async filter loading
432 // This allows filters to compile in the background while the browser starts432 // This allows filters to compile in the background while the browser starts
433 go func() {433 go func() {
434 if err := filtering.InitializeFiltersAsync(filterManager); err != nil {434 if err := filtering.InitializeFiltersAsync(filterManager); err != nil {
435- log.Printf("Warning: failed to initialize filters asynchronously: %v", err)435+ logging.Warn(fmt.Sprintf("Warning: failed to initialize filters asynchronously: %v", err))
436 }436 }
437 }()437 }()
438 438
439- log.Printf("Content blocking system initialization started")439+ logging.Info(fmt.Sprintf("Content blocking system initialization started"))
440 return nil440 return nil
441 }441 }
442 442
M internal/app/browser/window_shortcuts.go +61 -61
@@ -2,11 +2,11 @@ package browser
2 2
3 import (3 import (
4 "fmt"4 "fmt"
5- "log"
6 "sync"5 "sync"
7 "time"6 "time"
8 7
9 "github.com/bnema/dumber/internal/app/messaging"8 "github.com/bnema/dumber/internal/app/messaging"
9+ "github.com/bnema/dumber/internal/logging"
10 "github.com/bnema/dumber/pkg/webkit"10 "github.com/bnema/dumber/pkg/webkit"
11 "github.com/diamondburned/gotk4/pkg/gdk/v4"11 "github.com/diamondburned/gotk4/pkg/gdk/v4"
12 "github.com/diamondburned/gotk4/pkg/gtk/v4"12 "github.com/diamondburned/gotk4/pkg/gtk/v4"
@@ -35,7 +35,7 @@ func NewWindowShortcutHandler(window *webkit.Window, app *BrowserApp) *WindowSho
35 }35 }
36 36
37 if err := h.initialize(); err != nil {37 if err := h.initialize(); err != nil {
38- log.Printf("[window-shortcuts] Failed to initialize: %v", err)38+ logging.Error(fmt.Sprintf("[window-shortcuts] Failed to initialize: %v", err))
39 return nil39 return nil
40 }40 }
41 41
@@ -51,7 +51,7 @@ func (h *WindowShortcutHandler) initialize() error {
51 51
52 // Register hardware keycode-based shortcuts (for AZERTY/international keyboards)52 // Register hardware keycode-based shortcuts (for AZERTY/international keyboards)
53 if err := h.registerKeycodeShortcuts(); err != nil {53 if err := h.registerKeycodeShortcuts(); err != nil {
54- log.Printf("[window-shortcuts] Failed to register keycode shortcuts: %v", err)54+ logging.Error(fmt.Sprintf("[window-shortcuts] Failed to register keycode shortcuts: %v", err))
55 }55 }
56 56
57 return h.registerGlobalShortcuts()57 return h.registerGlobalShortcuts()
@@ -103,8 +103,8 @@ func (h *WindowShortcutHandler) registerGlobalShortcuts() error {
103 103
104 for _, shortcut := range shortcuts {104 for _, shortcut := range shortcuts {
105 h.shortcuts.RegisterShortcut(shortcut.key, shortcut.handler)105 h.shortcuts.RegisterShortcut(shortcut.key, shortcut.handler)
106- log.Printf("[window-shortcuts] Registered global shortcut: %s (%s)",106+ logging.Debug(fmt.Sprintf("[window-shortcuts] Registered global shortcut: %s (%s)",
107- shortcut.key, shortcut.desc)107+ shortcut.key, shortcut.desc))
108 }108 }
109 109
110 // NOTE: Ctrl+P is NOT registered here - it's handled by WebView's keyboard bridge110 // NOTE: Ctrl+P is NOT registered here - it's handled by WebView's keyboard bridge
@@ -164,14 +164,14 @@ func (h *WindowShortcutHandler) registerKeycodeShortcuts() error {
164 164
165 // Handle Ctrl+Tab for next tab165 // Handle Ctrl+Tab for next tab
166 if keyval == gdkKeyTab && state.Has(gdk.ControlMask) && !state.Has(gdk.ShiftMask) {166 if keyval == gdkKeyTab && state.Has(gdk.ControlMask) && !state.Has(gdk.ShiftMask) {
167- log.Printf("[window-shortcuts] Ctrl+Tab -> next tab")167+ logging.Debug(fmt.Sprintf("[window-shortcuts] Ctrl+Tab -> next tab"))
168 h.handleNextTab()168 h.handleNextTab()
169 return true // Consume event to prevent focus cycling169 return true // Consume event to prevent focus cycling
170 }170 }
171 171
172 // Handle Ctrl+Shift+Tab for previous tab172 // Handle Ctrl+Shift+Tab for previous tab
173 if keyval == gdkKeyTab && state.Has(gdk.ControlMask) && state.Has(gdk.ShiftMask) {173 if keyval == gdkKeyTab && state.Has(gdk.ControlMask) && state.Has(gdk.ShiftMask) {
174- log.Printf("[window-shortcuts] Ctrl+Shift+Tab -> previous tab")174+ logging.Debug(fmt.Sprintf("[window-shortcuts] Ctrl+Shift+Tab -> previous tab"))
175 h.handlePrevTab()175 h.handlePrevTab()
176 return true // Consume event to prevent focus cycling176 return true // Consume event to prevent focus cycling
177 }177 }
@@ -180,7 +180,7 @@ func (h *WindowShortcutHandler) registerKeycodeShortcuts() error {
180 if state.Has(gdk.AltMask) && !state.Has(gdk.ControlMask) && !state.Has(gdk.ShiftMask) {180 if state.Has(gdk.AltMask) && !state.Has(gdk.ControlMask) && !state.Has(gdk.ShiftMask) {
181 // Check if this is a number key181 // Check if this is a number key
182 if tabIndex, ok := keycodeToTab[keycode]; ok {182 if tabIndex, ok := keycodeToTab[keycode]; ok {
183- log.Printf("[window-shortcuts] Hardware keycode shortcut: Alt+keycode(%d) -> tab %d", keycode, tabIndex)183+ logging.Debug(fmt.Sprintf("[window-shortcuts] Hardware keycode shortcut: Alt+keycode(%d) -> tab %d", keycode, tabIndex))
184 h.handleDirectTabSwitch(tabIndex)184 h.handleDirectTabSwitch(tabIndex)
185 return true // Handled185 return true // Handled
186 }186 }
@@ -236,7 +236,7 @@ func (h *WindowShortcutHandler) registerKeycodeShortcuts() error {
236 }236 }
237 237
238 if handled {238 if handled {
239- log.Printf("[window-shortcuts] Tab mode action: %s", action)239+ logging.Debug(fmt.Sprintf("[window-shortcuts] Tab mode action: %s", action))
240 h.handleTabModeAction(action)240 h.handleTabModeAction(action)
241 return true // Consume event241 return true // Consume event
242 }242 }
@@ -248,7 +248,7 @@ func (h *WindowShortcutHandler) registerKeycodeShortcuts() error {
248 })248 })
249 249
250 gtkWindow.AddController(keyController)250 gtkWindow.AddController(keyController)
251- log.Printf("[window-shortcuts] Registered capture phase shortcuts: Ctrl+Tab, Alt+number")251+ logging.Debug(fmt.Sprintf("[window-shortcuts] Registered capture phase shortcuts: Ctrl+Tab, Alt+number"))
252 252
253 return nil253 return nil
254 }254 }
@@ -271,11 +271,11 @@ func (h *WindowShortcutHandler) handleCopyURL() {
271 h.lastCopyURL = time.Now()271 h.lastCopyURL = time.Now()
272 272
273 if h.app.activePane == nil || h.app.activePane.webView == nil {273 if h.app.activePane == nil || h.app.activePane.webView == nil {
274- log.Printf("[window-shortcuts] No active pane for copy URL")274+ logging.Debug(fmt.Sprintf("[window-shortcuts] No active pane for copy URL"))
275 return275 return
276 }276 }
277 277
278- log.Printf("[window-shortcuts] Copy URL -> pane %p", h.app.activePane.webView)278+ logging.Debug(fmt.Sprintf("[window-shortcuts] Copy URL -> pane %p", h.app.activePane.webView))
279 279
280 // Execute copy URL script directly on active pane280 // Execute copy URL script directly on active pane
281 script := `281 script := `
@@ -299,7 +299,7 @@ func (h *WindowShortcutHandler) handleCopyURL() {
299 `299 `
300 300
301 if err := h.app.activePane.webView.InjectScript(script); err != nil {301 if err := h.app.activePane.webView.InjectScript(script); err != nil {
302- log.Printf("[window-shortcuts] Failed to inject copy URL script: %v", err)302+ logging.Error(fmt.Sprintf("[window-shortcuts] Failed to inject copy URL script: %v", err))
303 }303 }
304 }304 }
305 305
@@ -313,14 +313,14 @@ func (h *WindowShortcutHandler) handleDevTools() {
313 h.lastDevTools = time.Now()313 h.lastDevTools = time.Now()
314 314
315 if h.app.activePane == nil || h.app.activePane.webView == nil {315 if h.app.activePane == nil || h.app.activePane.webView == nil {
316- log.Printf("[window-shortcuts] No active pane for devtools")316+ logging.Debug(fmt.Sprintf("[window-shortcuts] No active pane for devtools"))
317 return317 return
318 }318 }
319 319
320- log.Printf("[window-shortcuts] DevTools -> pane %p", h.app.activePane.webView)320+ logging.Debug(fmt.Sprintf("[window-shortcuts] DevTools -> pane %p", h.app.activePane.webView))
321 321
322 if err := h.app.activePane.webView.ShowDevTools(); err != nil {322 if err := h.app.activePane.webView.ShowDevTools(); err != nil {
323- log.Printf("[window-shortcuts] Failed to show devtools: %v", err)323+ logging.Error(fmt.Sprintf("[window-shortcuts] Failed to show devtools: %v", err))
324 }324 }
325 }325 }
326 326
@@ -334,14 +334,14 @@ func (h *WindowShortcutHandler) handlePrint() {
334 h.lastPrint = time.Now()334 h.lastPrint = time.Now()
335 335
336 if h.app.activePane == nil || h.app.activePane.webView == nil {336 if h.app.activePane == nil || h.app.activePane.webView == nil {
337- log.Printf("[window-shortcuts] No active pane for print")337+ logging.Debug(fmt.Sprintf("[window-shortcuts] No active pane for print"))
338 return338 return
339 }339 }
340 340
341- log.Printf("[window-shortcuts] Print -> pane %p", h.app.activePane.webView)341+ logging.Debug(fmt.Sprintf("[window-shortcuts] Print -> pane %p", h.app.activePane.webView))
342 342
343 if err := h.app.activePane.webView.ShowPrintDialog(); err != nil {343 if err := h.app.activePane.webView.ShowPrintDialog(); err != nil {
344- log.Printf("[window-shortcuts] Failed to show print dialog: %v", err)344+ logging.Error(fmt.Sprintf("[window-shortcuts] Failed to show print dialog: %v", err))
345 }345 }
346 }346 }
347 347
@@ -350,14 +350,14 @@ func (h *WindowShortcutHandler) handleReload() {
350 defer h.mu.Unlock()350 defer h.mu.Unlock()
351 351
352 if h.app.activePane == nil || h.app.activePane.webView == nil {352 if h.app.activePane == nil || h.app.activePane.webView == nil {
353- log.Printf("[window-shortcuts] No active pane for reload")353+ logging.Debug(fmt.Sprintf("[window-shortcuts] No active pane for reload"))
354 return354 return
355 }355 }
356 356
357- log.Printf("[window-shortcuts] Reload page -> pane %p", h.app.activePane.webView)357+ logging.Debug(fmt.Sprintf("[window-shortcuts] Reload page -> pane %p", h.app.activePane.webView))
358 358
359 if err := h.app.activePane.webView.Reload(); err != nil {359 if err := h.app.activePane.webView.Reload(); err != nil {
360- log.Printf("[window-shortcuts] Failed to reload page: %v", err)360+ logging.Error(fmt.Sprintf("[window-shortcuts] Failed to reload page: %v", err))
361 }361 }
362 }362 }
363 363
@@ -366,14 +366,14 @@ func (h *WindowShortcutHandler) handleHardReload() {
366 defer h.mu.Unlock()366 defer h.mu.Unlock()
367 367
368 if h.app.activePane == nil || h.app.activePane.webView == nil {368 if h.app.activePane == nil || h.app.activePane.webView == nil {
369- log.Printf("[window-shortcuts] No active pane for hard reload")369+ logging.Debug(fmt.Sprintf("[window-shortcuts] No active pane for hard reload"))
370 return370 return
371 }371 }
372 372
373- log.Printf("[window-shortcuts] Hard reload (bypass cache) -> pane %p", h.app.activePane.webView)373+ logging.Debug(fmt.Sprintf("[window-shortcuts] Hard reload (bypass cache) -> pane %p", h.app.activePane.webView))
374 374
375 if err := h.app.activePane.webView.ReloadBypassCache(); err != nil {375 if err := h.app.activePane.webView.ReloadBypassCache(); err != nil {
376- log.Printf("[window-shortcuts] Failed to hard reload page: %v", err)376+ logging.Error(fmt.Sprintf("[window-shortcuts] Failed to hard reload page: %v", err))
377 }377 }
378 }378 }
379 379
@@ -398,12 +398,12 @@ func (h *WindowShortcutHandler) handleZoom(action string, multiplier float64) {
398 defer h.mu.Unlock()398 defer h.mu.Unlock()
399 399
400 if h.app.activePane == nil || h.app.activePane.webView == nil {400 if h.app.activePane == nil || h.app.activePane.webView == nil {
401- log.Printf("[window-shortcuts] No active pane for zoom %s", action)401+ logging.Debug(fmt.Sprintf("[window-shortcuts] No active pane for zoom %s", action))
402 return402 return
403 }403 }
404 404
405 activeWebView := h.app.activePane.webView405 activeWebView := h.app.activePane.webView
406- log.Printf("[window-shortcuts] Zoom %s -> pane %p", action, activeWebView)406+ logging.Debug(fmt.Sprintf("[window-shortcuts] Zoom %s -> pane %p", action, activeWebView))
407 407
408 // Get current zoom level408 // Get current zoom level
409 currentZoom := activeWebView.GetZoom()409 currentZoom := activeWebView.GetZoom()
@@ -423,11 +423,11 @@ func (h *WindowShortcutHandler) handleZoom(action string, multiplier float64) {
423 }423 }
424 }424 }
425 425
426- log.Printf("[window-shortcuts] Zoom %s: %.2f -> %.2f", action, currentZoom, newZoom)426+ logging.Debug(fmt.Sprintf("[window-shortcuts] Zoom %s: %.2f -> %.2f", action, currentZoom, newZoom))
427 427
428 // Apply zoom to active pane428 // Apply zoom to active pane
429 if err := activeWebView.SetZoom(newZoom); err != nil {429 if err := activeWebView.SetZoom(newZoom); err != nil {
430- log.Printf("[window-shortcuts] Failed to set zoom: %v", err)430+ logging.Error(fmt.Sprintf("[window-shortcuts] Failed to set zoom: %v", err))
431 return431 return
432 }432 }
433 433
@@ -448,7 +448,7 @@ func (h *WindowShortcutHandler) handleZoom(action string, multiplier float64) {
448 `, newZoom)448 `, newZoom)
449 449
450 if err := activeWebView.InjectScript(toastScript); err != nil {450 if err := activeWebView.InjectScript(toastScript); err != nil {
451- log.Printf("[window-shortcuts] Failed to show zoom toast: %v", err)451+ logging.Error(fmt.Sprintf("[window-shortcuts] Failed to show zoom toast: %v", err))
452 }452 }
453 }453 }
454 454
@@ -459,7 +459,7 @@ func (h *WindowShortcutHandler) ensureGUIInActivePane(component string) {
459 459
460 pane := h.app.activePane460 pane := h.app.activePane
461 if !pane.HasGUI() {461 if !pane.HasGUI() {
462- log.Printf("[window-shortcuts] Injecting GUI into pane %s for %s", pane.ID(), component)462+ logging.Debug(fmt.Sprintf("[window-shortcuts] Injecting GUI into pane %s for %s", pane.ID(), component))
463 if h.app.workspace != nil {463 if h.app.workspace != nil {
464 h.app.workspace.ensureGUIInPane(pane)464 h.app.workspace.ensureGUIInPane(pane)
465 }465 }
@@ -467,7 +467,7 @@ func (h *WindowShortcutHandler) ensureGUIInActivePane(component string) {
467 467
468 // Ensure specific component is available468 // Ensure specific component is available
469 if !pane.HasGUIComponent(component) {469 if !pane.HasGUIComponent(component) {
470- log.Printf("[window-shortcuts] Ensuring %s component in pane %s", component, pane.ID())470+ logging.Debug(fmt.Sprintf("[window-shortcuts] Ensuring %s component in pane %s", component, pane.ID()))
471 pane.SetGUIComponent(component, true)471 pane.SetGUIComponent(component, true)
472 }472 }
473 }473 }
@@ -484,17 +484,17 @@ func (h *WindowShortcutHandler) handleUIToggle(lastToggle *time.Time, featureNam
484 defer h.mu.Unlock()484 defer h.mu.Unlock()
485 485
486 if time.Since(*lastToggle) < 50*time.Millisecond {486 if time.Since(*lastToggle) < 50*time.Millisecond {
487- log.Printf("[window-shortcuts] %s toggle debounced", featureName)487+ logging.Debug(fmt.Sprintf("[window-shortcuts] %s toggle debounced", featureName))
488 return488 return
489 }489 }
490 *lastToggle = time.Now()490 *lastToggle = time.Now()
491 491
492 if h.app.activePane == nil || h.app.activePane.webView == nil {492 if h.app.activePane == nil || h.app.activePane.webView == nil {
493- log.Printf("[window-shortcuts] No active pane for %s", featureName)493+ logging.Debug(fmt.Sprintf("[window-shortcuts] No active pane for %s", featureName))
494 return494 return
495 }495 }
496 496
497- log.Printf("[window-shortcuts] %s toggle -> pane %p", featureName, h.app.activePane.webView)497+ logging.Debug(fmt.Sprintf("[window-shortcuts] %s toggle -> pane %p", featureName, h.app.activePane.webView))
498 498
499 h.ensureGUIInActivePane("omnibox")499 h.ensureGUIInActivePane("omnibox")
500 500
@@ -504,48 +504,48 @@ func (h *WindowShortcutHandler) handleUIToggle(lastToggle *time.Time, featureNam
504 "timestamp": time.Now().UnixMilli(),504 "timestamp": time.Now().UnixMilli(),
505 "source": "window-global",505 "source": "window-global",
506 }); err != nil {506 }); err != nil {
507- log.Printf("[window-shortcuts] Failed to dispatch %s toggle: %v", featureName, err)507+ logging.Error(fmt.Sprintf("[window-shortcuts] Failed to dispatch %s toggle: %v", featureName, err))
508 }508 }
509 }509 }
510 510
511 func (h *WindowShortcutHandler) handleWorkspaceNavigation(direction string) {511 func (h *WindowShortcutHandler) handleWorkspaceNavigation(direction string) {
512 if h.app == nil || h.app.workspace == nil {512 if h.app == nil || h.app.workspace == nil {
513- log.Printf("[window-shortcuts] No workspace for navigation")513+ logging.Debug(fmt.Sprintf("[window-shortcuts] No workspace for navigation"))
514 return514 return
515 }515 }
516 516
517- log.Printf("[window-shortcuts] Workspace navigation: %s", direction)517+ logging.Debug(fmt.Sprintf("[window-shortcuts] Workspace navigation: %s", direction))
518 518
519 if h.app.workspace.FocusNeighbor(direction) {519 if h.app.workspace.FocusNeighbor(direction) {
520- log.Printf("[window-shortcuts] Workspace navigation %s successful", direction)520+ logging.Debug(fmt.Sprintf("[window-shortcuts] Workspace navigation %s successful", direction))
521 } else {521 } else {
522- log.Printf("[window-shortcuts] Workspace navigation %s failed", direction)522+ logging.Debug(fmt.Sprintf("[window-shortcuts] Workspace navigation %s failed", direction))
523 }523 }
524 }524 }
525 525
526 func (h *WindowShortcutHandler) handleClosePane() {526 func (h *WindowShortcutHandler) handleClosePane() {
527 if h.app == nil || h.app.workspace == nil {527 if h.app == nil || h.app.workspace == nil {
528- log.Printf("[window-shortcuts] No workspace for close pane")528+ logging.Debug(fmt.Sprintf("[window-shortcuts] No workspace for close pane"))
529 return529 return
530 }530 }
531 531
532 // Find the currently active WebView532 // Find the currently active WebView
533 var activeWebView *webkit.WebView533 var activeWebView *webkit.WebView
534- log.Printf("[window-shortcuts] Searching for active WebView among %d WebViews", len(h.app.workspace.viewToNode))534+ logging.Debug(fmt.Sprintf("[window-shortcuts] Searching for active WebView among %d WebViews", len(h.app.workspace.viewToNode)))
535 for webView := range h.app.workspace.viewToNode {535 for webView := range h.app.workspace.viewToNode {
536 if webView != nil {536 if webView != nil {
537 isActive := webView.IsActive()537 isActive := webView.IsActive()
538- log.Printf("[window-shortcuts] WebView %d: IsActive=%t", webView.ID(), isActive)538+ logging.Debug(fmt.Sprintf("[window-shortcuts] WebView %d: IsActive=%t", webView.ID(), isActive))
539 if isActive {539 if isActive {
540 activeWebView = webView540 activeWebView = webView
541- log.Printf("[window-shortcuts] Found active WebView: %d", webView.ID())541+ logging.Debug(fmt.Sprintf("[window-shortcuts] Found active WebView: %d", webView.ID()))
542 break542 break
543 }543 }
544 }544 }
545 }545 }
546 546
547 if activeWebView == nil {547 if activeWebView == nil {
548- log.Printf("[window-shortcuts] No active WebView found, using workspace closeCurrentPane")548+ logging.Debug(fmt.Sprintf("[window-shortcuts] No active WebView found, using workspace closeCurrentPane"))
549 h.app.workspace.closeCurrentPane()549 h.app.workspace.closeCurrentPane()
550 return550 return
551 }551 }
@@ -553,7 +553,7 @@ func (h *WindowShortcutHandler) handleClosePane() {
553 // Check if the active WebView is a popup553 // Check if the active WebView is a popup
554 node := h.app.workspace.viewToNode[activeWebView]554 node := h.app.workspace.viewToNode[activeWebView]
555 if node != nil && node.isPopup {555 if node != nil && node.isPopup {
556- log.Printf("[window-shortcuts] Closing popup via OnWorkspaceMessage")556+ logging.Debug(fmt.Sprintf("[window-shortcuts] Closing popup via OnWorkspaceMessage"))
557 // Use the proper close-popup message for popups557 // Use the proper close-popup message for popups
558 msg := messaging.Message{558 msg := messaging.Message{
559 Event: "close-popup",559 Event: "close-popup",
@@ -562,7 +562,7 @@ func (h *WindowShortcutHandler) handleClosePane() {
562 }562 }
563 h.app.workspace.OnWorkspaceMessage(activeWebView, msg)563 h.app.workspace.OnWorkspaceMessage(activeWebView, msg)
564 } else {564 } else {
565- log.Printf("[window-shortcuts] Closing regular pane via workspace closeCurrentPane")565+ logging.Debug(fmt.Sprintf("[window-shortcuts] Closing regular pane via workspace closeCurrentPane"))
566 // Use regular close for non-popup panes566 // Use regular close for non-popup panes
567 h.app.workspace.closeCurrentPane()567 h.app.workspace.closeCurrentPane()
568 }568 }
@@ -571,61 +571,61 @@ func (h *WindowShortcutHandler) handleClosePane() {
571 // handlePaneMode enters pane mode for modal pane management.571 // handlePaneMode enters pane mode for modal pane management.
572 func (h *WindowShortcutHandler) handlePaneMode() {572 func (h *WindowShortcutHandler) handlePaneMode() {
573 if h.app == nil || h.app.workspace == nil {573 if h.app == nil || h.app.workspace == nil {
574- log.Printf("[window-shortcuts] Cannot enter pane mode: workspace not available")574+ logging.Debug(fmt.Sprintf("[window-shortcuts] Cannot enter pane mode: workspace not available"))
575 return575 return
576 }576 }
577 577
578- log.Printf("[window-shortcuts] Entering pane mode")578+ logging.Debug(fmt.Sprintf("[window-shortcuts] Entering pane mode"))
579 h.app.workspace.EnterPaneMode()579 h.app.workspace.EnterPaneMode()
580 }580 }
581 581
582 // handleTabMode enters tab mode for modal tab management.582 // handleTabMode enters tab mode for modal tab management.
583 func (h *WindowShortcutHandler) handleTabMode() {583 func (h *WindowShortcutHandler) handleTabMode() {
584 if h.app == nil || h.app.tabManager == nil {584 if h.app == nil || h.app.tabManager == nil {
585- log.Printf("[window-shortcuts] Cannot enter tab mode: tab manager not available")585+ logging.Debug(fmt.Sprintf("[window-shortcuts] Cannot enter tab mode: tab manager not available"))
586 return586 return
587 }587 }
588 588
589- log.Printf("[window-shortcuts] Entering tab mode")589+ logging.Debug(fmt.Sprintf("[window-shortcuts] Entering tab mode"))
590 h.app.tabManager.EnterTabMode()590 h.app.tabManager.EnterTabMode()
591 }591 }
592 592
593 // handleNextTab switches to the next tab.593 // handleNextTab switches to the next tab.
594 func (h *WindowShortcutHandler) handleNextTab() {594 func (h *WindowShortcutHandler) handleNextTab() {
595 if h.app == nil || h.app.tabManager == nil {595 if h.app == nil || h.app.tabManager == nil {
596- log.Printf("[window-shortcuts] Cannot switch tab: tab manager not available")596+ logging.Debug(fmt.Sprintf("[window-shortcuts] Cannot switch tab: tab manager not available"))
597 return597 return
598 }598 }
599 599
600- log.Printf("[window-shortcuts] Switching to next tab")600+ logging.Debug(fmt.Sprintf("[window-shortcuts] Switching to next tab"))
601 if err := h.app.tabManager.NextTab(); err != nil {601 if err := h.app.tabManager.NextTab(); err != nil {
602- log.Printf("[window-shortcuts] Failed to switch to next tab: %v", err)602+ logging.Error(fmt.Sprintf("[window-shortcuts] Failed to switch to next tab: %v", err))
603 }603 }
604 }604 }
605 605
606 // handlePrevTab switches to the previous tab.606 // handlePrevTab switches to the previous tab.
607 func (h *WindowShortcutHandler) handlePrevTab() {607 func (h *WindowShortcutHandler) handlePrevTab() {
608 if h.app == nil || h.app.tabManager == nil {608 if h.app == nil || h.app.tabManager == nil {
609- log.Printf("[window-shortcuts] Cannot switch tab: tab manager not available")609+ logging.Debug(fmt.Sprintf("[window-shortcuts] Cannot switch tab: tab manager not available"))
610 return610 return
611 }611 }
612 612
613- log.Printf("[window-shortcuts] Switching to previous tab")613+ logging.Debug(fmt.Sprintf("[window-shortcuts] Switching to previous tab"))
614 if err := h.app.tabManager.PreviousTab(); err != nil {614 if err := h.app.tabManager.PreviousTab(); err != nil {
615- log.Printf("[window-shortcuts] Failed to switch to previous tab: %v", err)615+ logging.Error(fmt.Sprintf("[window-shortcuts] Failed to switch to previous tab: %v", err))
616 }616 }
617 }617 }
618 618
619 // handleDirectTabSwitch switches to a specific tab by index (0-based).619 // handleDirectTabSwitch switches to a specific tab by index (0-based).
620 func (h *WindowShortcutHandler) handleDirectTabSwitch(index int) {620 func (h *WindowShortcutHandler) handleDirectTabSwitch(index int) {
621 if h.app == nil || h.app.tabManager == nil {621 if h.app == nil || h.app.tabManager == nil {
622- log.Printf("[window-shortcuts] Cannot switch tab: tab manager not available")622+ logging.Debug(fmt.Sprintf("[window-shortcuts] Cannot switch tab: tab manager not available"))
623 return623 return
624 }624 }
625 625
626- log.Printf("[window-shortcuts] Direct tab switch to index %d", index)626+ logging.Debug(fmt.Sprintf("[window-shortcuts] Direct tab switch to index %d", index))
627 if err := h.app.tabManager.SwitchToTab(index); err != nil {627 if err := h.app.tabManager.SwitchToTab(index); err != nil {
628- log.Printf("[window-shortcuts] Failed to switch to tab %d: %v", index, err)628+ logging.Error(fmt.Sprintf("[window-shortcuts] Failed to switch to tab %d: %v", index, err))
629 }629 }
630 }630 }
631 631
@@ -653,7 +653,7 @@ func (h *WindowShortcutHandler) handleTabModeAction(action string) {
653 }653 }
654 654
655 // Tab mode is active, handle the action655 // Tab mode is active, handle the action
656- log.Printf("[window-shortcuts] Tab mode action: %s", action)656+ logging.Debug(fmt.Sprintf("[window-shortcuts] Tab mode action: %s", action))
657 h.app.tabManager.HandleTabAction(action)657 h.app.tabManager.HandleTabAction(action)
658 }658 }
659 659
M internal/app/browser/workspace_css.go +9 -4
@@ -3,10 +3,10 @@ package browser
3 3
4 import (4 import (
5 "fmt"5 "fmt"
6- "log"
7 "strings"6 "strings"
8 7
9 "github.com/bnema/dumber/internal/config"8 "github.com/bnema/dumber/internal/config"
9+ "github.com/bnema/dumber/internal/logging"
10 "github.com/bnema/dumber/pkg/webkit"10 "github.com/bnema/dumber/pkg/webkit"
11 )11 )
12 12
@@ -393,12 +393,17 @@ func (wm *WorkspaceManager) ensureWorkspaceStyles() {
393 // Only apply CSS if it hasn't been applied globally or if content changed393 // Only apply CSS if it hasn't been applied globally or if content changed
394 if !globalCSSInitialized || globalCSSContent != workspaceCSS {394 if !globalCSSInitialized || globalCSSContent != workspaceCSS {
395 if err := webkit.AddCSSProvider(workspaceCSS); err != nil {395 if err := webkit.AddCSSProvider(workspaceCSS); err != nil {
396- log.Printf("[workspace] WARNING: Failed to add CSS provider: %v", err)396+ logging.Error(fmt.Sprintf("[workspace] WARNING: Failed to add CSS provider: %v", err))
397 }397 }
398 globalCSSInitialized = true398 globalCSSInitialized = true
399 globalCSSContent = workspaceCSS399 globalCSSContent = workspaceCSS
400- log.Printf("[workspace] Applied workspace CSS styles (%d bytes)", len(workspaceCSS))400+ logging.Info(fmt.Sprintf("[workspace] Applied workspace CSS styles (%d bytes)", len(workspaceCSS)))
401- log.Printf("[workspace] CSS preview: %s...", workspaceCSS[:min(200, len(workspaceCSS))])401+
402+ previewLen := 200
403+ if len(workspaceCSS) < previewLen {
404+ previewLen = len(workspaceCSS)
405+ }
406+ logging.Info(fmt.Sprintf("[workspace] CSS preview: %s...", workspaceCSS[:previewLen]))
402 }407 }
403 408
404 wm.cssInitialized = true409 wm.cssInitialized = true
M internal/app/browser/workspace_debug.go +6 -2
@@ -1,12 +1,16 @@
1 package browser1 package browser
2 2
3-import "log"3+import (
4+ "fmt"
5+
6+ "github.com/bnema/dumber/internal/logging"
7+)
4 8
5 func (wm *WorkspaceManager) paneCloseLogf(format string, args ...interface{}) {9 func (wm *WorkspaceManager) paneCloseLogf(format string, args ...interface{}) {
6 if wm == nil || !wm.debugPaneClose {10 if wm == nil || !wm.debugPaneClose {
7 return11 return
8 }12 }
9- log.Printf("[pane-close] "+format, args...)13+ logging.Info(fmt.Sprintf("[pane-close] "+format, args...))
10 }14 }
11 15
12 func (wm *WorkspaceManager) dumpTreeState(label string) {16 func (wm *WorkspaceManager) dumpTreeState(label string) {
M internal/app/browser/workspace_diagnostics.go +3 -2
@@ -2,9 +2,10 @@ package browser
2 2
3 import (3 import (
4 "fmt"4 "fmt"
5- "log"
6 "strings"5 "strings"
7 "sync"6 "sync"
7+
8+ "github.com/bnema/dumber/internal/logging"
8 )9 )
9 10
10 type TreeSnapshot struct {11 type TreeSnapshot struct {
@@ -63,7 +64,7 @@ func (d *WorkspaceDiagnostics) Capture(label string, root *paneNode) TreeSnapsho
63 d.snapshots = append(d.snapshots, snapshot)64 d.snapshots = append(d.snapshots, snapshot)
64 d.mu.Unlock()65 d.mu.Unlock()
65 66
66- log.Printf("[pane-close] snapshot %s\n%s", snapshot.Label, strings.Join(snapshot.Lines, "\n"))67+ logging.Info(fmt.Sprintf("[pane-close] snapshot %s\n%s", snapshot.Label, strings.Join(snapshot.Lines, "\n")))
67 return snapshot68 return snapshot
68 }69 }
69 70
M internal/app/browser/workspace_geometry_validator.go +4 -5
@@ -3,8 +3,7 @@ package browser
3 3
4 import (4 import (
5 "fmt"5 "fmt"
6- "log"6+ "github.com/bnema/dumber/internal/logging"
7-
8 "github.com/bnema/dumber/pkg/webkit"7 "github.com/bnema/dumber/pkg/webkit"
9 )8 )
10 9
@@ -52,7 +51,7 @@ func (gv *GeometryValidator) GetPaneGeometry(node *paneNode) PaneGeometry {
52 51
53 if node == nil || node.container == nil {52 if node == nil || node.container == nil {
54 if gv.debugMode {53 if gv.debugMode {
55- log.Printf("[geometry] Invalid node or missing container")54+ logging.Error(fmt.Sprintf("[geometry] Invalid node or missing container"))
56 }55 }
57 return geometry56 return geometry
58 }57 }
@@ -67,8 +66,8 @@ func (gv *GeometryValidator) GetPaneGeometry(node *paneNode) PaneGeometry {
67 geometry.IsValid = true66 geometry.IsValid = true
68 67
69 if gv.debugMode {68 if gv.debugMode {
70- log.Printf("[geometry] Pane geometry: %dx%d at (%d,%d)",69+ logging.Info(fmt.Sprintf("[geometry] Pane geometry: %dx%d at (%d,%d)",
71- geometry.Width, geometry.Height, geometry.X, geometry.Y)70+ geometry.Width, geometry.Height, geometry.X, geometry.Y))
72 }71 }
73 72
74 return geometry73 return geometry
M internal/app/browser/workspace_hover.go +6 -5
@@ -2,8 +2,9 @@
2 package browser2 package browser
3 3
4 import (4 import (
5- "log"5+ "fmt"
6 6
7+ "github.com/bnema/dumber/internal/logging"
7 "github.com/bnema/dumber/pkg/webkit"8 "github.com/bnema/dumber/pkg/webkit"
8 "github.com/diamondburned/gotk4/pkg/glib/v2"9 "github.com/diamondburned/gotk4/pkg/glib/v2"
9 )10 )
@@ -43,9 +44,9 @@ func (wm *WorkspaceManager) ensureHover(node *paneNode) {
43 44
44 if controller != nil {45 if controller != nil {
45 node.hoverToken = controller46 node.hoverToken = controller
46- log.Printf("[workspace] Attached hover handler to pane %p (controller=%p)", node, controller)47+ logging.Debug(fmt.Sprintf("[workspace] Attached hover handler to pane %p (controller=%p)", node, controller))
47 } else {48 } else {
48- log.Printf("[workspace] Failed to attach hover handler to pane %p", node)49+ logging.Error(fmt.Sprintf("[workspace] Failed to attach hover handler to pane %p", node))
49 }50 }
50 }51 }
51 52
@@ -61,7 +62,7 @@ func (wm *WorkspaceManager) detachHover(node *paneNode) {
61 }62 }
62 63
63 webkit.WidgetRemoveHoverHandler(node.container, node.hoverToken)64 webkit.WidgetRemoveHoverHandler(node.container, node.hoverToken)
64- log.Printf("[workspace] Detached hover handler from pane %p (controller=%p)", node, node.hoverToken)65+ logging.Debug(fmt.Sprintf("[workspace] Detached hover handler from pane %p (controller=%p)", node, node.hoverToken))
65 node.hoverToken = nil66 node.hoverToken = nil
66 }67 }
67 68
@@ -76,5 +77,5 @@ func (wm *WorkspaceManager) attachHoverHandlersToAllPanes() {
76 wm.ensureHover(leaf)77 wm.ensureHover(leaf)
77 }78 }
78 79
79- log.Printf("[workspace] Attached hover handlers to %d panes", len(leaves))80+ logging.Debug(fmt.Sprintf("[workspace] Attached hover handlers to %d panes", len(leaves)))
80 }81 }
M internal/app/browser/workspace_manager.go +39 -39
@@ -4,13 +4,13 @@ package browser
4 import (4 import (
5 "errors"5 "errors"
6 "fmt"6 "fmt"
7- "log"
8 "strings"7 "strings"
9 "sync"8 "sync"
10 "sync/atomic"9 "sync/atomic"
11 "time"10 "time"
12 11
13 "github.com/bnema/dumber/internal/app/messaging"12 "github.com/bnema/dumber/internal/app/messaging"
13+ "github.com/bnema/dumber/internal/logging"
14 "github.com/bnema/dumber/pkg/webkit"14 "github.com/bnema/dumber/pkg/webkit"
15 "github.com/diamondburned/gotk4/pkg/glib/v2"15 "github.com/diamondburned/gotk4/pkg/glib/v2"
16 "github.com/diamondburned/gotk4/pkg/gtk/v4"16 "github.com/diamondburned/gotk4/pkg/gtk/v4"
@@ -208,7 +208,7 @@ func NewWorkspaceManager(app *BrowserApp, rootPane *BrowserPane) *WorkspaceManag
208 208
209 // Initialize focus state machine after all setup is complete209 // Initialize focus state machine after all setup is complete
210 if err := manager.focusStateMachine.Initialize(); err != nil {210 if err := manager.focusStateMachine.Initialize(); err != nil {
211- log.Printf("[workspace] Failed to initialize focus state machine: %v", err)211+ logging.Error(fmt.Sprintf("[workspace] Failed to initialize focus state machine: %v", err))
212 }212 }
213 213
214 // Ensure hover controllers are attached to all panes for mouse-driven focus changes214 // Ensure hover controllers are attached to all panes for mouse-driven focus changes
@@ -225,7 +225,7 @@ func NewWorkspaceManager(app *BrowserApp, rootPane *BrowserPane) *WorkspaceManag
225 func (wm *WorkspaceManager) shouldDebounce(source *webkit.WebView, threshold time.Duration) bool {225 func (wm *WorkspaceManager) shouldDebounce(source *webkit.WebView, threshold time.Duration) bool {
226 if last, ok := wm.lastSplitMsg[source]; ok {226 if last, ok := wm.lastSplitMsg[source]; ok {
227 if elapsed := time.Since(last); elapsed < threshold {227 if elapsed := time.Since(last); elapsed < threshold {
228- log.Printf("[workspace] operation debounced: %.0fms", elapsed.Seconds()*1000)228+ logging.Debug(fmt.Sprintf("[workspace] operation debounced: %.0fms", elapsed.Seconds()*1000))
229 return true229 return true
230 }230 }
231 }231 }
@@ -236,14 +236,14 @@ func (wm *WorkspaceManager) shouldDebounce(source *webkit.WebView, threshold tim
236 // withSplitLock executes an operation with atomic splitting lock protection236 // withSplitLock executes an operation with atomic splitting lock protection
237 func (wm *WorkspaceManager) withSplitLock(operation string, fn func() error) error {237 func (wm *WorkspaceManager) withSplitLock(operation string, fn func() error) error {
238 if !atomic.CompareAndSwapInt32(&wm.splitting, 0, 1) {238 if !atomic.CompareAndSwapInt32(&wm.splitting, 0, 1) {
239- log.Printf("[workspace] %s ignored: operation in progress", operation)239+ logging.Debug(fmt.Sprintf("[workspace] %s ignored: operation in progress", operation))
240 return fmt.Errorf("operation in progress")240 return fmt.Errorf("operation in progress")
241 }241 }
242 242
243 defer func() {243 defer func() {
244 atomic.StoreInt32(&wm.splitting, 0)244 atomic.StoreInt32(&wm.splitting, 0)
245 if r := recover(); r != nil {245 if r := recover(); r != nil {
246- log.Printf("[workspace] %s panicked: %v", operation, r)246+ logging.Error(fmt.Sprintf("[workspace] %s panicked: %v", operation, r))
247 panic(r)247 panic(r)
248 }248 }
249 }()249 }()
@@ -272,9 +272,9 @@ func (wm *WorkspaceManager) cleanupPopupStorage(parentNode, targetNode *paneNode
272 `, parentWebViewID, requestID, requestID, requestID, requestID, webviewID)272 `, parentWebViewID, requestID, requestID, requestID, requestID, webviewID)
273 273
274 if err := parentNode.pane.WebView().InjectScript(script); err != nil {274 if err := parentNode.pane.WebView().InjectScript(script); err != nil {
275- log.Printf("[workspace] localStorage cleanup failed: %v", err)275+ logging.Error(fmt.Sprintf("[workspace] localStorage cleanup failed: %v", err))
276 } else {276 } else {
277- log.Printf("[workspace] Cleaned localStorage for popup requestId=%s webviewId=%s", requestID, webviewID)277+ logging.Debug(fmt.Sprintf("[workspace] Cleaned localStorage for popup requestId=%s webviewId=%s", requestID, webviewID))
278 }278 }
279 }279 }
280 280
@@ -292,7 +292,7 @@ func (wm *WorkspaceManager) OnWorkspaceMessage(source *webkit.WebView, msg messa
292 }292 }
293 293
294 if node == nil {294 if node == nil {
295- log.Printf("[workspace] message from unknown webview: event=%s webviewId=%s", msg.Event, msg.WebViewID)295+ logging.Warn(fmt.Sprintf("[workspace] message from unknown webview: event=%s webviewId=%s", msg.Event, msg.WebViewID))
296 return296 return
297 }297 }
298 298
@@ -321,7 +321,7 @@ func (wm *WorkspaceManager) OnWorkspaceMessage(source *webkit.WebView, msg messa
321 })321 })
322 322
323 if err != nil {323 if err != nil {
324- log.Printf("[workspace] split failed: %v", err)324+ logging.Error(fmt.Sprintf("[workspace] split failed: %v", err))
325 }325 }
326 326
327 case "pane-stack":327 case "pane-stack":
@@ -344,12 +344,12 @@ func (wm *WorkspaceManager) OnWorkspaceMessage(source *webkit.WebView, msg messa
344 })344 })
345 345
346 if err != nil {346 if err != nil {
347- log.Printf("[workspace] stack failed: %v", err)347+ logging.Error(fmt.Sprintf("[workspace] stack failed: %v", err))
348 }348 }
349 349
350 case "close-popup":350 case "close-popup":
351 if msg.WebViewID == "" {351 if msg.WebViewID == "" {
352- log.Printf("[workspace] close-popup: empty webviewId, ignoring")352+ logging.Warn(fmt.Sprintf("[workspace] close-popup: empty webviewId, ignoring"))
353 return353 return
354 }354 }
355 355
@@ -363,16 +363,16 @@ func (wm *WorkspaceManager) OnWorkspaceMessage(source *webkit.WebView, msg messa
363 }363 }
364 364
365 if targetNode == nil {365 if targetNode == nil {
366- log.Printf("[workspace] close-popup: webview not found: %s", msg.WebViewID)366+ logging.Warn(fmt.Sprintf("[workspace] close-popup: webview not found: %s", msg.WebViewID))
367 return367 return
368 }368 }
369 369
370 if !targetNode.isPopup {370 if !targetNode.isPopup {
371- log.Printf("[workspace] close-popup: target is not a popup: %s", msg.WebViewID)371+ logging.Warn(fmt.Sprintf("[workspace] close-popup: target is not a popup: %s", msg.WebViewID))
372 return372 return
373 }373 }
374 374
375- log.Printf("[workspace] Closing popup due to %s", msg.Reason)375+ logging.Info(fmt.Sprintf("[workspace] Closing popup due to %s", msg.Reason))
376 376
377 // Remove from parent's active popup list377 // Remove from parent's active popup list
378 if targetNode.parentPane != nil {378 if targetNode.parentPane != nil {
@@ -380,7 +380,7 @@ func (wm *WorkspaceManager) OnWorkspaceMessage(source *webkit.WebView, msg messa
380 for i, childID := range parentNode.activePopupChildren {380 for i, childID := range parentNode.activePopupChildren {
381 if childID == msg.WebViewID {381 if childID == msg.WebViewID {
382 parentNode.activePopupChildren = append(parentNode.activePopupChildren[:i], parentNode.activePopupChildren[i+1:]...)382 parentNode.activePopupChildren = append(parentNode.activePopupChildren[:i], parentNode.activePopupChildren[i+1:]...)
383- log.Printf("[workspace] Removed popup from parent (remaining: %d)", len(parentNode.activePopupChildren))383+ logging.Debug(fmt.Sprintf("[workspace] Removed popup from parent (remaining: %d)", len(parentNode.activePopupChildren)))
384 break384 break
385 }385 }
386 }386 }
@@ -389,11 +389,11 @@ func (wm *WorkspaceManager) OnWorkspaceMessage(source *webkit.WebView, msg messa
389 }389 }
390 390
391 if err := wm.ClosePane(targetNode); err != nil {391 if err := wm.ClosePane(targetNode); err != nil {
392- log.Printf("[workspace] Failed to close popup: %v", err)392+ logging.Error(fmt.Sprintf("[workspace] Failed to close popup: %v", err))
393 }393 }
394 394
395 default:395 default:
396- log.Printf("[workspace] unhandled workspace event: %s", msg.Event)396+ logging.Warn(fmt.Sprintf("[workspace] unhandled workspace event: %s", msg.Event))
397 }397 }
398 }398 }
399 399
@@ -433,7 +433,7 @@ func (wm *WorkspaceManager) SetActivePane(node *paneNode, source FocusSource) {
433 wm.lastFocusTarget = node433 wm.lastFocusTarget = node
434 434
435 if err := wm.focusStateMachine.RequestFocus(node, source); err != nil {435 if err := wm.focusStateMachine.RequestFocus(node, source); err != nil {
436- log.Printf("[workspace] Focus request failed: %v", err)436+ logging.Error(fmt.Sprintf("[workspace] Focus request failed: %v", err))
437 }437 }
438 }438 }
439 439
@@ -516,12 +516,12 @@ func (wm *WorkspaceManager) RegisterNavigationHandler(webView *webkit.WebView) {
516 return false516 return false
517 }517 }
518 518
519- log.Printf("[workspace] Middle-click/Ctrl+click on link, opening in new pane: %s", linkURL)519+ logging.Info(fmt.Sprintf("[workspace] Middle-click/Ctrl+click on link, opening in new pane: %s", linkURL))
520 520
521 // Get the node for this webview521 // Get the node for this webview
522 node := wm.GetNodeForWebView(webView)522 node := wm.GetNodeForWebView(webView)
523 if node == nil {523 if node == nil {
524- log.Printf("[workspace] Cannot find node for middle-clicked link")524+ logging.Error(fmt.Sprintf("[workspace] Cannot find node for middle-clicked link"))
525 return false525 return false
526 }526 }
527 527
@@ -537,18 +537,18 @@ func (wm *WorkspaceManager) RegisterNavigationHandler(webView *webkit.WebView) {
537 switch behavior {537 switch behavior {
538 case "stacked":538 case "stacked":
539 // Stack the current pane - this also creates a new pane in the stack539 // Stack the current pane - this also creates a new pane in the stack
540- log.Printf("[workspace] Using stacked behavior for gesture link")540+ logging.Debug(fmt.Sprintf("[workspace] Using stacked behavior for gesture link"))
541 newNode, err = wm.stackedPaneManager.StackPane(node)541 newNode, err = wm.stackedPaneManager.StackPane(node)
542 if err != nil {542 if err != nil {
543- log.Printf("[workspace] Failed to stack pane for gesture link: %v", err)543+ logging.Error(fmt.Sprintf("[workspace] Failed to stack pane for gesture link: %v", err))
544 return false544 return false
545 }545 }
546 // newNode is the new pane that was added to the stack546 // newNode is the new pane that was added to the stack
547- log.Printf("[workspace] StackPane created new pane in stack: %p", newNode)547+ logging.Debug(fmt.Sprintf("[workspace] StackPane created new pane in stack: %p", newNode))
548 548
549 case "split":549 case "split":
550 // Regular split behavior550 // Regular split behavior
551- log.Printf("[workspace] Using split behavior for gesture link")551+ logging.Debug(fmt.Sprintf("[workspace] Using split behavior for gesture link"))
552 direction := "right"552 direction := "right"
553 if wm.app.config.Workspace.Popups.Placement != "" {553 if wm.app.config.Workspace.Popups.Placement != "" {
554 direction = strings.ToLower(wm.app.config.Workspace.Popups.Placement)554 direction = strings.ToLower(wm.app.config.Workspace.Popups.Placement)
@@ -557,7 +557,7 @@ func (wm *WorkspaceManager) RegisterNavigationHandler(webView *webkit.WebView) {
557 557
558 case "tabbed":558 case "tabbed":
559 // Tabbed not yet implemented, fall back to split559 // Tabbed not yet implemented, fall back to split
560- log.Printf("[workspace] WARNING: Tabbed behavior not yet implemented for gesture links, falling back to split")560+ logging.Warn(fmt.Sprintf("[workspace] WARNING: Tabbed behavior not yet implemented for gesture links, falling back to split"))
561 direction := "right"561 direction := "right"
562 if wm.app.config.Workspace.Popups.Placement != "" {562 if wm.app.config.Workspace.Popups.Placement != "" {
563 direction = strings.ToLower(wm.app.config.Workspace.Popups.Placement)563 direction = strings.ToLower(wm.app.config.Workspace.Popups.Placement)
@@ -566,7 +566,7 @@ func (wm *WorkspaceManager) RegisterNavigationHandler(webView *webkit.WebView) {
566 566
567 default:567 default:
568 // Unknown behavior, fall back to split568 // Unknown behavior, fall back to split
569- log.Printf("[workspace] WARNING: Unknown behavior '%s' for gesture links, falling back to split", behavior)569+ logging.Warn(fmt.Sprintf("[workspace] WARNING: Unknown behavior '%s' for gesture links, falling back to split", behavior))
570 direction := "right"570 direction := "right"
571 if wm.app.config.Workspace.Popups.Placement != "" {571 if wm.app.config.Workspace.Popups.Placement != "" {
572 direction = strings.ToLower(wm.app.config.Workspace.Popups.Placement)572 direction = strings.ToLower(wm.app.config.Workspace.Popups.Placement)
@@ -575,14 +575,14 @@ func (wm *WorkspaceManager) RegisterNavigationHandler(webView *webkit.WebView) {
575 }575 }
576 576
577 if err != nil {577 if err != nil {
578- log.Printf("[workspace] Failed to handle gesture link: %v", err)578+ logging.Error(fmt.Sprintf("[workspace] Failed to handle gesture link: %v", err))
579 return false579 return false
580 }580 }
581 581
582 // Navigate the new pane to the URL582 // Navigate the new pane to the URL
583 if newNode != nil && newNode.pane != nil && newNode.pane.WebView() != nil {583 if newNode != nil && newNode.pane != nil && newNode.pane.WebView() != nil {
584 if err := newNode.pane.WebView().LoadURL(linkURL); err != nil {584 if err := newNode.pane.WebView().LoadURL(linkURL); err != nil {
585- log.Printf("[workspace] Failed to load URL in new pane: %v", err)585+ logging.Error(fmt.Sprintf("[workspace] Failed to load URL in new pane: %v", err))
586 return false586 return false
587 }587 }
588 588
@@ -593,7 +593,7 @@ func (wm *WorkspaceManager) RegisterNavigationHandler(webView *webkit.WebView) {
593 return true // Indicate we handled the click593 return true // Indicate we handled the click
594 })594 })
595 595
596- log.Printf("[workspace] Registered navigation handler for webview: %d", webView.ID())596+ logging.Debug(fmt.Sprintf("[workspace] Registered navigation handler for webview: %d", webView.ID()))
597 }597 }
598 598
599 // GetAllPanes returns all BrowserPanes in this workspace.599 // GetAllPanes returns all BrowserPanes in this workspace.
@@ -621,7 +621,7 @@ func (wm *WorkspaceManager) GetActivePane() *BrowserPane {
621 // RestoreFocus restores focus to the active pane in this workspace.621 // RestoreFocus restores focus to the active pane in this workspace.
622 // Called when switching to a tab to ensure the correct pane has focus.622 // Called when switching to a tab to ensure the correct pane has focus.
623 func (wm *WorkspaceManager) RestoreFocus() {623 func (wm *WorkspaceManager) RestoreFocus() {
624- log.Printf("[workspace] RestoreFocus: workspace %p", wm)624+ logging.Debug(fmt.Sprintf("[workspace] RestoreFocus: workspace %p", wm))
625 625
626 // Get the active pane and explicitly grab GTK focus626 // Get the active pane and explicitly grab GTK focus
627 activeNode := wm.GetActiveNode()627 activeNode := wm.GetActiveNode()
@@ -630,7 +630,7 @@ func (wm *WorkspaceManager) RestoreFocus() {
630 // This ensures keyboard events go to the active tab's WebView630 // This ensures keyboard events go to the active tab's WebView
631 // Note: RestoreFocus is called from switchToTabInternal which is already on the main thread631 // Note: RestoreFocus is called from switchToTabInternal which is already on the main thread
632 webkit.WidgetGrabFocus(activeNode.pane.webView.AsWidget())632 webkit.WidgetGrabFocus(activeNode.pane.webView.AsWidget())
633- log.Printf("[workspace] Grabbed GTK focus for WebView in workspace %p", wm)633+ logging.Debug(fmt.Sprintf("[workspace] Grabbed GTK focus for WebView in workspace %p", wm))
634 634
635 // Also request focus via FSM635 // Also request focus via FSM
636 if wm.focusStateMachine != nil {636 if wm.focusStateMachine != nil {
@@ -647,14 +647,14 @@ func (wm *WorkspaceManager) IsInActiveTab() bool {
647 }647 }
648 // The active workspace is the one currently assigned to app.workspace648 // The active workspace is the one currently assigned to app.workspace
649 isActive := wm.app.workspace == wm649 isActive := wm.app.workspace == wm
650- log.Printf("[workspace] IsInActiveTab check: workspace %p, app.workspace %p, isActive=%v", wm, wm.app.workspace, isActive)650+ logging.Debug(fmt.Sprintf("[workspace] IsInActiveTab check: workspace %p, app.workspace %p, isActive=%v", wm, wm.app.workspace, isActive))
651 return isActive651 return isActive
652 }652 }
653 653
654 // ClearFocus removes GTK focus from all panes in this workspace.654 // ClearFocus removes GTK focus from all panes in this workspace.
655 // This prevents inactive tab's WebViews from catching keyboard events.655 // This prevents inactive tab's WebViews from catching keyboard events.
656 func (wm *WorkspaceManager) ClearFocus() {656 func (wm *WorkspaceManager) ClearFocus() {
657- log.Printf("[workspace] ClearFocus: workspace %p", wm)657+ logging.Debug(fmt.Sprintf("[workspace] ClearFocus: workspace %p", wm))
658 // No state to clear - IsInActiveTab() checks dynamically658 // No state to clear - IsInActiveTab() checks dynamically
659 }659 }
660 660
@@ -675,19 +675,19 @@ func (wm *WorkspaceManager) setRootContainer(container gtk.Widgetter) {
675 }675 }
676 676
677 if wm.app == nil || wm.app.tabManager == nil || wm.app.tabManager.ContentArea == nil {677 if wm.app == nil || wm.app.tabManager == nil || wm.app.tabManager.ContentArea == nil {
678- log.Printf("[workspace] ERROR: Cannot set root - tab manager not initialized")678+ logging.Error(fmt.Sprintf("[workspace] ERROR: Cannot set root - tab manager not initialized"))
679 return679 return
680 }680 }
681 681
682 contentBox, ok := wm.app.tabManager.ContentArea.(*gtk.Box)682 contentBox, ok := wm.app.tabManager.ContentArea.(*gtk.Box)
683 if !ok || contentBox == nil {683 if !ok || contentBox == nil {
684- log.Printf("[workspace] ERROR: ContentArea is not a Box")684+ logging.Error(fmt.Sprintf("[workspace] ERROR: ContentArea is not a Box"))
685 return685 return
686 }686 }
687 687
688 contentBox.Append(container)688 contentBox.Append(container)
689 webkit.WidgetSetVisible(container, true)689 webkit.WidgetSetVisible(container, true)
690- log.Printf("[workspace] Attached root container %p to ContentArea", container)690+ logging.Debug(fmt.Sprintf("[workspace] Attached root container %p to ContentArea", container))
691 }691 }
692 692
693 // clearRootContainer removes the current workspace root from TabManager.ContentArea.693 // clearRootContainer removes the current workspace root from TabManager.ContentArea.
@@ -698,16 +698,16 @@ func (wm *WorkspaceManager) clearRootContainer() {
698 }698 }
699 699
700 if wm.app == nil || wm.app.tabManager == nil || wm.app.tabManager.ContentArea == nil {700 if wm.app == nil || wm.app.tabManager == nil || wm.app.tabManager.ContentArea == nil {
701- log.Printf("[workspace] ERROR: Cannot clear root - tab manager not initialized")701+ logging.Error(fmt.Sprintf("[workspace] ERROR: Cannot clear root - tab manager not initialized"))
702 return702 return
703 }703 }
704 704
705 contentBox, ok := wm.app.tabManager.ContentArea.(*gtk.Box)705 contentBox, ok := wm.app.tabManager.ContentArea.(*gtk.Box)
706 if !ok || contentBox == nil {706 if !ok || contentBox == nil {
707- log.Printf("[workspace] ERROR: ContentArea is not a Box")707+ logging.Error(fmt.Sprintf("[workspace] ERROR: ContentArea is not a Box"))
708 return708 return
709 }709 }
710 710
711 contentBox.Remove(wm.root.container)711 contentBox.Remove(wm.root.container)
712- log.Printf("[workspace] Removed root container %p from ContentArea", wm.root.container)712+ logging.Debug(fmt.Sprintf("[workspace] Removed root container %p from ContentArea", wm.root.container))
713 }713 }
M internal/app/browser/workspace_navigation.go +30 -30
@@ -3,11 +3,11 @@ package browser
3 3
4 import (4 import (
5 "fmt"5 "fmt"
6- "log"
7 "math"6 "math"
8 "strings"7 "strings"
9 "time"8 "time"
10 9
10+ "github.com/bnema/dumber/internal/logging"
11 "github.com/bnema/dumber/pkg/webkit"11 "github.com/bnema/dumber/pkg/webkit"
12 "github.com/diamondburned/gotk4/pkg/gtk/v4"12 "github.com/diamondburned/gotk4/pkg/gtk/v4"
13 )13 )
@@ -32,9 +32,9 @@ func (wm *WorkspaceManager) DispatchPaneFocusEvent(node *paneNode, active bool)
32 }32 }
33 33
34 if err := node.pane.webView.DispatchCustomEvent("dumber:workspace-focus", detail); err != nil {34 if err := node.pane.webView.DispatchCustomEvent("dumber:workspace-focus", detail); err != nil {
35- log.Printf("[workspace] failed to dispatch focus event: %v", err)35+ logging.Error(fmt.Sprintf("[workspace] failed to dispatch focus event: %v", err))
36 } else if wm.app.config != nil && wm.app.config.Debug.EnableWorkspaceDebug {36 } else if wm.app.config != nil && wm.app.config.Debug.EnableWorkspaceDebug {
37- log.Printf("[workspace] dispatched focus event for webview %d (active=%v)", node.pane.webView.ID(), active)37+ logging.Debug(fmt.Sprintf("[workspace] dispatched focus event for webview %d (active=%v)", node.pane.webView.ID(), active))
38 }38 }
39 }39 }
40 40
@@ -56,7 +56,7 @@ func (wm *WorkspaceManager) focusRespectingStack(node *paneNode, reason string)
56 activePane := stack.stackedPanes[activeIndex]56 activePane := stack.stackedPanes[activeIndex]
57 if activePane != nil && activePane != node {57 if activePane != nil && activePane != node {
58 if reason != "" {58 if reason != "" {
59- log.Printf("[workspace] %s: preserving active stacked pane index=%d", reason, activeIndex)59+ logging.Info(fmt.Sprintf("[workspace] %s: preserving active stacked pane index=%d", reason, activeIndex))
60 }60 }
61 wm.SetActivePane(activePane, SourceKeyboard)61 wm.SetActivePane(activePane, SourceKeyboard)
62 return62 return
@@ -122,7 +122,7 @@ func (wm *WorkspaceManager) FocusNeighbor(direction string) bool {
122 case DirectionLeft, DirectionRight:122 case DirectionLeft, DirectionRight:
123 return wm.focusAdjacent(strings.ToLower(direction))123 return wm.focusAdjacent(strings.ToLower(direction))
124 default:124 default:
125- log.Printf("[workspace] invalid focus direction: %s (expected: up, down, left, right)", direction)125+ logging.Error(fmt.Sprintf("[workspace] invalid focus direction: %s (expected: up, down, left, right)", direction))
126 return false126 return false
127 }127 }
128 }128 }
@@ -162,7 +162,7 @@ func (wm *WorkspaceManager) navigateStack(direction string) bool {
162 }162 }
163 163
164 if currentIndex == -1 {164 if currentIndex == -1 {
165- log.Printf("[workspace] navigateStack: current pane not found in stack")165+ logging.Error(fmt.Sprintf("[workspace] navigateStack: current pane not found in stack"))
166 return false166 return false
167 }167 }
168 168
@@ -195,8 +195,8 @@ func (wm *WorkspaceManager) navigateStack(direction string) bool {
195 newActivePane := stackNode.stackedPanes[newIndex]195 newActivePane := stackNode.stackedPanes[newIndex]
196 wm.SetActivePane(newActivePane, SourceStackNav)196 wm.SetActivePane(newActivePane, SourceStackNav)
197 197
198- log.Printf("[workspace] navigated stack: direction=%s from=%d to=%d stackSize=%d",198+ logging.Debug(fmt.Sprintf("[workspace] navigated stack: direction=%s from=%d to=%d stackSize=%d",
199- direction, currentIndex, newIndex, len(stackNode.stackedPanes))199+ direction, currentIndex, newIndex, len(stackNode.stackedPanes)))
200 return true200 return true
201 }201 }
202 202
@@ -208,7 +208,7 @@ func (wm *WorkspaceManager) focusAdjacent(direction string) bool {
208 }208 }
209 209
210 if neighbor := wm.structuralNeighbor(currentFocused, direction); neighbor != nil {210 if neighbor := wm.structuralNeighbor(currentFocused, direction); neighbor != nil {
211- log.Printf("[workspace] focusAdjacent: using structural neighbor for direction=%s", direction)211+ logging.Debug(fmt.Sprintf("[workspace] focusAdjacent: using structural neighbor for direction=%s", direction))
212 wm.SetActivePane(neighbor, SourceKeyboard)212 wm.SetActivePane(neighbor, SourceKeyboard)
213 return true213 return true
214 }214 }
@@ -216,7 +216,7 @@ func (wm *WorkspaceManager) focusAdjacent(direction string) bool {
216 curX, curY, curWidth, curHeight := wm.getNavigationAllocation(currentFocused)216 curX, curY, curWidth, curHeight := wm.getNavigationAllocation(currentFocused)
217 cx := float64(curX) + float64(curWidth)/2.0217 cx := float64(curX) + float64(curWidth)/2.0
218 cy := float64(curY) + float64(curHeight)/2.0218 cy := float64(curY) + float64(curHeight)/2.0
219- log.Printf("[workspace] focusAdjacent: current pane center=(%.0f, %.0f) direction=%s", cx, cy, direction)219+ logging.Debug(fmt.Sprintf("[workspace] focusAdjacent: current pane center=(%.0f, %.0f) direction=%s", cx, cy, direction))
220 220
221 leaves := wm.collectLeaves()221 leaves := wm.collectLeaves()
222 bestScore := math.MaxFloat64222 bestScore := math.MaxFloat64
@@ -271,13 +271,13 @@ func (wm *WorkspaceManager) focusAdjacent(direction string) bool {
271 271
272 if best != nil {272 if best != nil {
273 bx, by, _, _ := webkit.WidgetGetAllocation(best.container)273 bx, by, _, _ := webkit.WidgetGetAllocation(best.container)
274- log.Printf("[workspace] focusAdjacent: found best candidate at pos=(%d,%d) for direction=%s", bx, by, direction)274+ logging.Debug(fmt.Sprintf("[workspace] focusAdjacent: found best candidate at pos=(%d,%d) for direction=%s", bx, by, direction))
275 wm.SetActivePane(best, SourceKeyboard)275 wm.SetActivePane(best, SourceKeyboard)
276 return true276 return true
277 }277 }
278 278
279 if len(debugCandidates) > 0 {279 if len(debugCandidates) > 0 {
280- log.Printf("[workspace] focusAdjacent: NO candidate found for direction=%s current=%p center=(%.0f,%.0f) candidates=[%s]", direction, currentFocused.container, cx, cy, strings.Join(debugCandidates, "; "))280+ logging.Debug(fmt.Sprintf("[workspace] focusAdjacent: NO candidate found for direction=%s current=%p center=(%.0f,%.0f) candidates=[%s]", direction, currentFocused.container, cx, cy, strings.Join(debugCandidates, "; ")))
281 }281 }
282 return false282 return false
283 }283 }
@@ -311,14 +311,14 @@ func (wm *WorkspaceManager) structuralNeighbor(node *paneNode, direction string)
311 cy := float64(refY) + float64(refHeight)/2.0311 cy := float64(refY) + float64(refHeight)/2.0
312 axisVertical := direction == DirectionUp || direction == DirectionDown312 axisVertical := direction == DirectionUp || direction == DirectionDown
313 313
314- log.Printf("[workspace] structuralNeighbor: node=%p pos=(%d,%d) direction=%s", node.container, refX, refY, direction)314+ logging.Debug(fmt.Sprintf("[workspace] structuralNeighbor: node=%p pos=(%d,%d) direction=%s", node.container, refX, refY, direction))
315 315
316 // Start from the node's parent, but skip stack containers for vertical navigation316 // Start from the node's parent, but skip stack containers for vertical navigation
317 // Stack containers are transparent to vertical navigation - we want to navigate317 // Stack containers are transparent to vertical navigation - we want to navigate
318 // from stack to external panes, not within the stack (that's handled by NavigateStack)318 // from stack to external panes, not within the stack (that's handled by NavigateStack)
319 startParent := node.parent319 startParent := node.parent
320 if startParent != nil && startParent.isStacked && axisVertical {320 if startParent != nil && startParent.isStacked && axisVertical {
321- log.Printf("[workspace] structuralNeighbor: skipping stack container parent, using stack's parent instead")321+ logging.Debug(fmt.Sprintf("[workspace] structuralNeighbor: skipping stack container parent, using stack's parent instead"))
322 node = startParent // Treat the stack container as the navigation node322 node = startParent // Treat the stack container as the navigation node
323 startParent = startParent.parent323 startParent = startParent.parent
324 }324 }
@@ -326,27 +326,27 @@ func (wm *WorkspaceManager) structuralNeighbor(node *paneNode, direction string)
326 for parent := startParent; parent != nil; parent = parent.parent {326 for parent := startParent; parent != nil; parent = parent.parent {
327 isLeft := parent.left == node327 isLeft := parent.left == node
328 isRight := parent.right == node328 isRight := parent.right == node
329- log.Printf("[workspace] structuralNeighbor: checking parent orientation=%v isLeft=%v isRight=%v", parent.orientation, isLeft, isRight)329+ logging.Debug(fmt.Sprintf("[workspace] structuralNeighbor: checking parent orientation=%v isLeft=%v isRight=%v", parent.orientation, isLeft, isRight))
330 330
331 switch direction {331 switch direction {
332 case DirectionUp:332 case DirectionUp:
333 if axisVertical && parent.orientation == gtk.OrientationVertical && parent.right == node {333 if axisVertical && parent.orientation == gtk.OrientationVertical && parent.right == node {
334- log.Printf("[workspace] structuralNeighbor: DirectionUp - we are RIGHT child, looking in LEFT subtree")334+ logging.Info(fmt.Sprintf("[workspace] structuralNeighbor: DirectionUp - we are RIGHT child, looking in LEFT subtree"))
335 if leaf := wm.closestLeafFromSubtree(parent.left, cx, cy, direction); leaf != nil {335 if leaf := wm.closestLeafFromSubtree(parent.left, cx, cy, direction); leaf != nil {
336 lx, ly, _, _ := webkit.WidgetGetAllocation(leaf.container)336 lx, ly, _, _ := webkit.WidgetGetAllocation(leaf.container)
337- log.Printf("[workspace] structuralNeighbor: found leaf at pos=(%d,%d)", lx, ly)337+ logging.Debug(fmt.Sprintf("[workspace] structuralNeighbor: found leaf at pos=(%d,%d)", lx, ly))
338 return leaf338 return leaf
339 }339 }
340 }340 }
341 case DirectionDown:341 case DirectionDown:
342 if axisVertical && parent.orientation == gtk.OrientationVertical && parent.left == node {342 if axisVertical && parent.orientation == gtk.OrientationVertical && parent.left == node {
343- log.Printf("[workspace] structuralNeighbor: DirectionDown - we are LEFT child, looking in RIGHT subtree")343+ logging.Info(fmt.Sprintf("[workspace] structuralNeighbor: DirectionDown - we are LEFT child, looking in RIGHT subtree"))
344 if leaf := wm.closestLeafFromSubtree(parent.right, cx, cy, direction); leaf != nil {344 if leaf := wm.closestLeafFromSubtree(parent.right, cx, cy, direction); leaf != nil {
345 lx, ly, _, _ := wm.getNavigationAllocation(leaf)345 lx, ly, _, _ := wm.getNavigationAllocation(leaf)
346- log.Printf("[workspace] structuralNeighbor: found leaf at pos=(%d,%d)", lx, ly)346+ logging.Debug(fmt.Sprintf("[workspace] structuralNeighbor: found leaf at pos=(%d,%d)", lx, ly))
347 return leaf347 return leaf
348 } else {348 } else {
349- log.Printf("[workspace] structuralNeighbor: closestLeafFromSubtree returned nil for DirectionDown")349+ logging.Error(fmt.Sprintf("[workspace] structuralNeighbor: closestLeafFromSubtree returned nil for DirectionDown"))
350 }350 }
351 }351 }
352 case DirectionLeft:352 case DirectionLeft:
@@ -370,7 +370,7 @@ func (wm *WorkspaceManager) structuralNeighbor(node *paneNode, direction string)
370 // closestLeafFromSubtree finds the closest leaf node in a subtree based on direction370 // closestLeafFromSubtree finds the closest leaf node in a subtree based on direction
371 func (wm *WorkspaceManager) closestLeafFromSubtree(node *paneNode, cx, cy float64, direction string) *paneNode {371 func (wm *WorkspaceManager) closestLeafFromSubtree(node *paneNode, cx, cy float64, direction string) *paneNode {
372 leaves := wm.collectLeavesFromWithDirection(node, direction)372 leaves := wm.collectLeavesFromWithDirection(node, direction)
373- log.Printf("[workspace] closestLeafFromSubtree: found %d leaves for direction=%s from cx=%.0f cy=%.0f", len(leaves), direction, cx, cy)373+ logging.Debug(fmt.Sprintf("[workspace] closestLeafFromSubtree: found %d leaves for direction=%s from cx=%.0f cy=%.0f", len(leaves), direction, cx, cy))
374 bestScore := math.MaxFloat64374 bestScore := math.MaxFloat64
375 var best *paneNode375 var best *paneNode
376 for _, leaf := range leaves {376 for _, leaf := range leaves {
@@ -383,47 +383,47 @@ func (wm *WorkspaceManager) closestLeafFromSubtree(node *paneNode, cx, cy float6
383 ty := float64(y) + float64(height)/2.0383 ty := float64(y) + float64(height)/2.0
384 dx := tx - cx384 dx := tx - cx
385 dy := ty - cy385 dy := ty - cy
386- log.Printf("[workspace] closestLeafFromSubtree: leaf=%p pos=(%d,%d) center=(%.0f,%.0f) dx=%.0f dy=%.0f", leaf.container, x, y, tx, ty, dx, dy)386+ logging.Info(fmt.Sprintf("[workspace] closestLeafFromSubtree: leaf=%p pos=(%d,%d) center=(%.0f,%.0f) dx=%.0f dy=%.0f", leaf.container, x, y, tx, ty, dx, dy))
387 var score float64387 var score float64
388 switch direction {388 switch direction {
389 case DirectionLeft:389 case DirectionLeft:
390 if dx >= -focusEpsilon {390 if dx >= -focusEpsilon {
391- log.Printf("[workspace] closestLeafFromSubtree: SKIPPED (dx=%.0f not left)", dx)391+ logging.Warn(fmt.Sprintf("[workspace] closestLeafFromSubtree: SKIPPED (dx=%.0f not left)", dx))
392 continue392 continue
393 }393 }
394 score = math.Abs(dx)*1000 + math.Abs(dy)394 score = math.Abs(dx)*1000 + math.Abs(dy)
395 case DirectionRight:395 case DirectionRight:
396 if dx <= focusEpsilon {396 if dx <= focusEpsilon {
397- log.Printf("[workspace] closestLeafFromSubtree: SKIPPED (dx=%.0f not right)", dx)397+ logging.Warn(fmt.Sprintf("[workspace] closestLeafFromSubtree: SKIPPED (dx=%.0f not right)", dx))
398 continue398 continue
399 }399 }
400 score = math.Abs(dx)*1000 + math.Abs(dy)400 score = math.Abs(dx)*1000 + math.Abs(dy)
401 case DirectionUp:401 case DirectionUp:
402 if dy >= -focusEpsilon {402 if dy >= -focusEpsilon {
403- log.Printf("[workspace] closestLeafFromSubtree: SKIPPED (dy=%.0f not above)", dy)403+ logging.Warn(fmt.Sprintf("[workspace] closestLeafFromSubtree: SKIPPED (dy=%.0f not above)", dy))
404 continue404 continue
405 }405 }
406 score = math.Abs(dy)*1000 + math.Abs(dx)406 score = math.Abs(dy)*1000 + math.Abs(dx)
407 case DirectionDown:407 case DirectionDown:
408 if dy <= focusEpsilon {408 if dy <= focusEpsilon {
409- log.Printf("[workspace] closestLeafFromSubtree: SKIPPED (dy=%.0f not below)", dy)409+ logging.Warn(fmt.Sprintf("[workspace] closestLeafFromSubtree: SKIPPED (dy=%.0f not below)", dy))
410 continue410 continue
411 }411 }
412 score = math.Abs(dy)*1000 + math.Abs(dx)412 score = math.Abs(dy)*1000 + math.Abs(dx)
413 default:413 default:
414 continue414 continue
415 }415 }
416- log.Printf("[workspace] closestLeafFromSubtree: ACCEPTED with score=%.0f", score)416+ logging.Debug(fmt.Sprintf("[workspace] closestLeafFromSubtree: ACCEPTED with score=%.0f", score))
417 if score < bestScore {417 if score < bestScore {
418 bestScore = score418 bestScore = score
419 best = leaf419 best = leaf
420 }420 }
421 }421 }
422 if best == nil {422 if best == nil {
423- log.Printf("[workspace] closestLeafFromSubtree: no match found, trying boundaryFallback")423+ logging.Warn(fmt.Sprintf("[workspace] closestLeafFromSubtree: no match found, trying boundaryFallback"))
424 return wm.boundaryFallback(node, direction)424 return wm.boundaryFallback(node, direction)
425 }425 }
426- log.Printf("[workspace] closestLeafFromSubtree: returning best=%p", best.container)426+ logging.Info(fmt.Sprintf("[workspace] closestLeafFromSubtree: returning best=%p", best.container))
427 return best427 return best
428 }428 }
429 429
@@ -437,7 +437,7 @@ func (wm *WorkspaceManager) boundaryFallbackWithDepth(node *paneNode, direction
437 // Prevent infinite recursion - max tree depth should be reasonable437 // Prevent infinite recursion - max tree depth should be reasonable
438 const maxDepth = 50438 const maxDepth = 50
439 if depth > maxDepth {439 if depth > maxDepth {
440- log.Printf("[workspace] boundaryFallback: max depth exceeded, possible tree corruption")440+ logging.Error(fmt.Sprintf("[workspace] boundaryFallback: max depth exceeded, possible tree corruption"))
441 return nil441 return nil
442 }442 }
443 443
M internal/app/browser/workspace_operations.go +39 -40
@@ -3,8 +3,7 @@ package browser
3 3
4 import (4 import (
5 "fmt"5 "fmt"
6- "log"6+ "github.com/bnema/dumber/internal/logging"
7-
8 "github.com/bnema/dumber/pkg/webkit"7 "github.com/bnema/dumber/pkg/webkit"
9 )8 )
10 9
@@ -25,7 +24,7 @@ func (wm *WorkspaceManager) SplitPane(target *paneNode, direction string) (*pane
25 return nil, fmt.Errorf("invalid split direction '%s', expected one of: left, right, up, down", direction)24 return nil, fmt.Errorf("invalid split direction '%s', expected one of: left, right, up, down", direction)
26 }25 }
27 26
28- log.Printf("[workspace] Starting split operation: target=%p direction=%s", target, direction)27+ logging.Debug(fmt.Sprintf("[workspace] Starting split operation: target=%p direction=%s", target, direction))
29 28
30 // Step 1: Validate geometry constraints (only in DebugBasic+)29 // Step 1: Validate geometry constraints (only in DebugBasic+)
31 if wm.debugLevel >= DebugBasic {30 if wm.debugLevel >= DebugBasic {
@@ -34,12 +33,12 @@ func (wm *WorkspaceManager) SplitPane(target *paneNode, direction string) (*pane
34 if wm.debugLevel == DebugFull {33 if wm.debugLevel == DebugFull {
35 return nil, fmt.Errorf("split validation failed: %s", validation.Reason)34 return nil, fmt.Errorf("split validation failed: %s", validation.Reason)
36 }35 }
37- log.Printf("[workspace] WARNING: Split validation failed but allowing operation: %s", validation.Reason)36+ logging.Error(fmt.Sprintf("[workspace] WARNING: Split validation failed but allowing operation: %s", validation.Reason))
38 }37 }
39 38
40 // Log if re-validation will be needed due to pending widget allocation39 // Log if re-validation will be needed due to pending widget allocation
41 if validation.RequiresRevalidation {40 if validation.RequiresRevalidation {
42- log.Printf("[workspace] Split validation passed with pending allocation - operation will proceed")41+ logging.Info(fmt.Sprintf("[workspace] Split validation passed with pending allocation - operation will proceed"))
43 }42 }
44 43
45 // Step 3: Validate tree invariants before operation44 // Step 3: Validate tree invariants before operation
@@ -47,13 +46,13 @@ func (wm *WorkspaceManager) SplitPane(target *paneNode, direction string) (*pane
47 if wm.debugLevel == DebugFull {46 if wm.debugLevel == DebugFull {
48 return nil, fmt.Errorf("tree validation failed before split: %w", err)47 return nil, fmt.Errorf("tree validation failed before split: %w", err)
49 }48 }
50- log.Printf("[workspace] WARNING: Tree validation failed before split but allowing operation: %v", err)49+ logging.Error(fmt.Sprintf("[workspace] WARNING: Tree validation failed before split but allowing operation: %v", err))
51 }50 }
52 }51 }
53 52
54 // Step 2: Execute directly if we're already on the GTK main thread53 // Step 2: Execute directly if we're already on the GTK main thread
55 if webkit.IsMainThread() {54 if webkit.IsMainThread() {
56- log.Printf("[workspace] Already on main thread, executing split directly")55+ logging.Info(fmt.Sprintf("[workspace] Already on main thread, executing split directly"))
57 newNode, err := wm.splitNode(target, direction, nil)56 newNode, err := wm.splitNode(target, direction, nil)
58 if err != nil {57 if err != nil {
59 return nil, err58 return nil, err
@@ -61,18 +60,18 @@ func (wm *WorkspaceManager) SplitPane(target *paneNode, direction string) (*pane
61 60
62 if wm.debugLevel >= DebugBasic {61 if wm.debugLevel >= DebugBasic {
63 if err := wm.treeValidator.ValidateTree(wm.root, "after_split"); err != nil {62 if err := wm.treeValidator.ValidateTree(wm.root, "after_split"); err != nil {
64- log.Printf("[workspace] Tree validation failed after split: %v", err)63+ logging.Error(fmt.Sprintf("[workspace] Tree validation failed after split: %v", err))
65 }64 }
66 }65 }
67 66
68 // Tree rebalancing is only needed after close promotions. Splits have correct allocation from GTK.67 // Tree rebalancing is only needed after close promotions. Splits have correct allocation from GTK.
69 68
70- log.Printf("[workspace] Split operation completed successfully (direct execution): newNode=%p", newNode)69+ logging.Info(fmt.Sprintf("[workspace] Split operation completed successfully (direct execution): newNode=%p", newNode))
71 return newNode, nil70 return newNode, nil
72 }71 }
73 72
74 // Step 3: Not on main thread, marshal via IdleAdd73 // Step 3: Not on main thread, marshal via IdleAdd
75- log.Printf("[workspace] Not on main thread, marshalling split via IdleAdd")74+ logging.Info(fmt.Sprintf("[workspace] Not on main thread, marshalling split via IdleAdd"))
76 var newNode *paneNode75 var newNode *paneNode
77 var splitErr error76 var splitErr error
78 done := make(chan struct{})77 done := make(chan struct{})
@@ -82,7 +81,7 @@ func (wm *WorkspaceManager) SplitPane(target *paneNode, direction string) (*pane
82 if splitErr == nil {81 if splitErr == nil {
83 if wm.debugLevel >= DebugBasic {82 if wm.debugLevel >= DebugBasic {
84 if verr := wm.treeValidator.ValidateTree(wm.root, "after_split"); verr != nil {83 if verr := wm.treeValidator.ValidateTree(wm.root, "after_split"); verr != nil {
85- log.Printf("[workspace] Tree validation failed after split: %v", verr)84+ logging.Error(fmt.Sprintf("[workspace] Tree validation failed after split: %v", verr))
86 }85 }
87 }86 }
88 }87 }
@@ -96,7 +95,7 @@ func (wm *WorkspaceManager) SplitPane(target *paneNode, direction string) (*pane
96 return nil, splitErr95 return nil, splitErr
97 }96 }
98 97
99- log.Printf("[workspace] Split operation completed successfully: newNode=%p", newNode)98+ logging.Info(fmt.Sprintf("[workspace] Split operation completed successfully: newNode=%p", newNode))
100 return newNode, nil99 return newNode, nil
101 }100 }
102 101
@@ -106,7 +105,7 @@ func (wm *WorkspaceManager) ClosePane(node *paneNode) error {
106 return fmt.Errorf("workspace manager is nil")105 return fmt.Errorf("workspace manager is nil")
107 }106 }
108 107
109- log.Printf("[workspace] Starting close operation: node=%p", node)108+ logging.Info(fmt.Sprintf("[workspace] Starting close operation: node=%p", node))
110 wm.paneCloseLogf("start bulletproof close node=%p", node)109 wm.paneCloseLogf("start bulletproof close node=%p", node)
111 wm.dumpTreeState("before_close")110 wm.dumpTreeState("before_close")
112 111
@@ -116,19 +115,19 @@ func (wm *WorkspaceManager) ClosePane(node *paneNode) error {
116 if wm.debugLevel == DebugFull {115 if wm.debugLevel == DebugFull {
117 return fmt.Errorf("tree validation failed before close: %w", err)116 return fmt.Errorf("tree validation failed before close: %w", err)
118 }117 }
119- log.Printf("[workspace] WARNING: Tree validation failed before close but allowing operation: %v", err)118+ logging.Error(fmt.Sprintf("[workspace] WARNING: Tree validation failed before close but allowing operation: %v", err))
120 }119 }
121 }120 }
122 121
123 // Step 3: Check if this is a stacked pane and use enhanced lifecycle management122 // Step 3: Check if this is a stacked pane and use enhanced lifecycle management
124 if node.parent != nil && node.parent.isStacked {123 if node.parent != nil && node.parent.isStacked {
125- log.Printf("[workspace] Using enhanced stack lifecycle management for close")124+ logging.Debug(fmt.Sprintf("[workspace] Using enhanced stack lifecycle management for close"))
126 return wm.stackLifecycleManager.CloseStackedPaneWithLifecycle(node)125 return wm.stackLifecycleManager.CloseStackedPaneWithLifecycle(node)
127 }126 }
128 127
129 // Step 4: Execute directly when already on the GTK main thread128 // Step 4: Execute directly when already on the GTK main thread
130 if webkit.IsMainThread() {129 if webkit.IsMainThread() {
131- log.Printf("[workspace] Already on main thread, executing close directly")130+ logging.Info(fmt.Sprintf("[workspace] Already on main thread, executing close directly"))
132 131
133 wm.paneCloseLogf("invoking closePane node=%p", node)132 wm.paneCloseLogf("invoking closePane node=%p", node)
134 promoted, err := wm.closePane(node)133 promoted, err := wm.closePane(node)
@@ -140,7 +139,7 @@ func (wm *WorkspaceManager) ClosePane(node *paneNode) error {
140 139
141 if wm.debugLevel >= DebugBasic {140 if wm.debugLevel >= DebugBasic {
142 if err := wm.treeValidator.ValidateTree(wm.root, "after_close"); err != nil {141 if err := wm.treeValidator.ValidateTree(wm.root, "after_close"); err != nil {
143- log.Printf("[workspace] Tree validation failed after close: %v", err)142+ logging.Error(fmt.Sprintf("[workspace] Tree validation failed after close: %v", err))
144 }143 }
145 }144 }
146 wm.paneCloseLogf("closePane succeeded node=%p promoted=%p root=%p", node, promoted, wm.root)145 wm.paneCloseLogf("closePane succeeded node=%p promoted=%p root=%p", node, promoted, wm.root)
@@ -148,16 +147,16 @@ func (wm *WorkspaceManager) ClosePane(node *paneNode) error {
148 147
149 if wm.treeRebalancer != nil {148 if wm.treeRebalancer != nil {
150 if err := wm.treeRebalancer.RebalanceAfterClose(node, promoted); err != nil {149 if err := wm.treeRebalancer.RebalanceAfterClose(node, promoted); err != nil {
151- log.Printf("[workspace] Tree rebalancing failed after close: %v", err)150+ logging.Error(fmt.Sprintf("[workspace] Tree rebalancing failed after close: %v", err))
152 }151 }
153 }152 }
154 153
155- log.Printf("[workspace] Close operation completed successfully (direct execution)")154+ logging.Info(fmt.Sprintf("[workspace] Close operation completed successfully (direct execution)"))
156 return nil155 return nil
157 }156 }
158 157
159 // Step 5: Not on main thread, marshal via IdleAdd158 // Step 5: Not on main thread, marshal via IdleAdd
160- log.Printf("[workspace] Not on main thread, marshalling close via IdleAdd")159+ logging.Info(fmt.Sprintf("[workspace] Not on main thread, marshalling close via IdleAdd"))
161 var promoted *paneNode160 var promoted *paneNode
162 var closeErr error161 var closeErr error
163 done := make(chan struct{})162 done := make(chan struct{})
@@ -171,7 +170,7 @@ func (wm *WorkspaceManager) ClosePane(node *paneNode) error {
171 } else {170 } else {
172 if wm.debugLevel >= DebugBasic {171 if wm.debugLevel >= DebugBasic {
173 if verr := wm.treeValidator.ValidateTree(wm.root, "after_close"); verr != nil {172 if verr := wm.treeValidator.ValidateTree(wm.root, "after_close"); verr != nil {
174- log.Printf("[workspace] Tree validation failed after close: %v", verr)173+ logging.Error(fmt.Sprintf("[workspace] Tree validation failed after close: %v", verr))
175 }174 }
176 }175 }
177 wm.paneCloseLogf("closePane succeeded node=%p promoted=%p root=%p", node, promoted, wm.root)176 wm.paneCloseLogf("closePane succeeded node=%p promoted=%p root=%p", node, promoted, wm.root)
@@ -179,7 +178,7 @@ func (wm *WorkspaceManager) ClosePane(node *paneNode) error {
179 178
180 if wm.treeRebalancer != nil {179 if wm.treeRebalancer != nil {
181 if rerr := wm.treeRebalancer.RebalanceAfterClose(node, promoted); rerr != nil {180 if rerr := wm.treeRebalancer.RebalanceAfterClose(node, promoted); rerr != nil {
182- log.Printf("[workspace] Tree rebalancing failed after close: %v", rerr)181+ logging.Error(fmt.Sprintf("[workspace] Tree rebalancing failed after close: %v", rerr))
183 }182 }
184 }183 }
185 }184 }
@@ -193,7 +192,7 @@ func (wm *WorkspaceManager) ClosePane(node *paneNode) error {
193 return closeErr192 return closeErr
194 }193 }
195 194
196- log.Printf("[workspace] Close operation completed successfully")195+ logging.Info(fmt.Sprintf("[workspace] Close operation completed successfully"))
197 return nil196 return nil
198 }197 }
199 198
@@ -203,7 +202,7 @@ func (wm *WorkspaceManager) StackPane(target *paneNode) (*paneNode, error) {
203 return nil, fmt.Errorf("workspace manager is nil")202 return nil, fmt.Errorf("workspace manager is nil")
204 }203 }
205 204
206- log.Printf("[workspace] Starting stack operation: target=%p", target)205+ logging.Info(fmt.Sprintf("[workspace] Starting stack operation: target=%p", target))
207 206
208 // Step 1: Validate stack operation constraints (only in DebugBasic+)207 // Step 1: Validate stack operation constraints (only in DebugBasic+)
209 if wm.debugLevel >= DebugBasic {208 if wm.debugLevel >= DebugBasic {
@@ -212,12 +211,12 @@ func (wm *WorkspaceManager) StackPane(target *paneNode) (*paneNode, error) {
212 if wm.debugLevel == DebugFull {211 if wm.debugLevel == DebugFull {
213 return nil, fmt.Errorf("stack validation failed: %s", validation.Reason)212 return nil, fmt.Errorf("stack validation failed: %s", validation.Reason)
214 }213 }
215- log.Printf("[workspace] WARNING: Stack validation failed but allowing operation: %s", validation.Reason)214+ logging.Error(fmt.Sprintf("[workspace] WARNING: Stack validation failed but allowing operation: %s", validation.Reason))
216 }215 }
217 216
218 // Log if re-validation will be needed due to pending widget allocation217 // Log if re-validation will be needed due to pending widget allocation
219 if validation.RequiresRevalidation {218 if validation.RequiresRevalidation {
220- log.Printf("[workspace] Stack validation passed with pending allocation - operation will proceed")219+ logging.Info(fmt.Sprintf("[workspace] Stack validation passed with pending allocation - operation will proceed"))
221 }220 }
222 }221 }
223 222
@@ -227,13 +226,13 @@ func (wm *WorkspaceManager) StackPane(target *paneNode) (*paneNode, error) {
227 if wm.debugLevel == DebugFull {226 if wm.debugLevel == DebugFull {
228 return nil, fmt.Errorf("tree validation failed before stack: %w", err)227 return nil, fmt.Errorf("tree validation failed before stack: %w", err)
229 }228 }
230- log.Printf("[workspace] WARNING: Tree validation failed before stack but allowing operation: %v", err)229+ logging.Error(fmt.Sprintf("[workspace] WARNING: Tree validation failed before stack but allowing operation: %v", err))
231 }230 }
232 }231 }
233 232
234 // Step 3: Execute directly if already on the GTK main thread233 // Step 3: Execute directly if already on the GTK main thread
235 if webkit.IsMainThread() {234 if webkit.IsMainThread() {
236- log.Printf("[workspace] Already on main thread, executing stack directly")235+ logging.Info(fmt.Sprintf("[workspace] Already on main thread, executing stack directly"))
237 newNode, err := wm.stackedPaneManager.StackPane(target)236 newNode, err := wm.stackedPaneManager.StackPane(target)
238 if err != nil {237 if err != nil {
239 return nil, err238 return nil, err
@@ -241,16 +240,16 @@ func (wm *WorkspaceManager) StackPane(target *paneNode) (*paneNode, error) {
241 240
242 if wm.debugLevel >= DebugBasic {241 if wm.debugLevel >= DebugBasic {
243 if err := wm.treeValidator.ValidateTree(wm.root, "after_stack"); err != nil {242 if err := wm.treeValidator.ValidateTree(wm.root, "after_stack"); err != nil {
244- log.Printf("[workspace] Tree validation failed after stack: %v", err)243+ logging.Error(fmt.Sprintf("[workspace] Tree validation failed after stack: %v", err))
245 }244 }
246 }245 }
247 246
248- log.Printf("[workspace] Stack operation completed successfully (direct execution): newNode=%p", newNode)247+ logging.Info(fmt.Sprintf("[workspace] Stack operation completed successfully (direct execution): newNode=%p", newNode))
249 return newNode, nil248 return newNode, nil
250 }249 }
251 250
252 // Step 4: Not on main thread, marshal via IdleAdd251 // Step 4: Not on main thread, marshal via IdleAdd
253- log.Printf("[workspace] Not on main thread, marshalling stack via IdleAdd")252+ logging.Info(fmt.Sprintf("[workspace] Not on main thread, marshalling stack via IdleAdd"))
254 var newNode *paneNode253 var newNode *paneNode
255 var stackErr error254 var stackErr error
256 done := make(chan struct{})255 done := make(chan struct{})
@@ -260,7 +259,7 @@ func (wm *WorkspaceManager) StackPane(target *paneNode) (*paneNode, error) {
260 if stackErr == nil {259 if stackErr == nil {
261 if wm.debugLevel >= DebugBasic {260 if wm.debugLevel >= DebugBasic {
262 if verr := wm.treeValidator.ValidateTree(wm.root, "after_stack"); verr != nil {261 if verr := wm.treeValidator.ValidateTree(wm.root, "after_stack"); verr != nil {
263- log.Printf("[workspace] Tree validation failed after stack: %v", verr)262+ logging.Error(fmt.Sprintf("[workspace] Tree validation failed after stack: %v", verr))
264 }263 }
265 }264 }
266 }265 }
@@ -274,7 +273,7 @@ func (wm *WorkspaceManager) StackPane(target *paneNode) (*paneNode, error) {
274 return nil, stackErr273 return nil, stackErr
275 }274 }
276 275
277- log.Printf("[workspace] Stack operation completed successfully: newNode=%p", newNode)276+ logging.Info(fmt.Sprintf("[workspace] Stack operation completed successfully: newNode=%p", newNode))
278 return newNode, nil277 return newNode, nil
279 }278 }
280 279
@@ -286,7 +285,7 @@ func (wm *WorkspaceManager) EnableEnhancedMode() {
286 if wm.treeRebalancer != nil {285 if wm.treeRebalancer != nil {
287 wm.treeRebalancer.Enable()286 wm.treeRebalancer.Enable()
288 }287 }
289- log.Printf("[workspace] Enhanced validation mode enabled")288+ logging.Info(fmt.Sprintf("[workspace] Enhanced validation mode enabled"))
290 }289 }
291 290
292 // DisableEnhancedMode disables enhanced features for performance291 // DisableEnhancedMode disables enhanced features for performance
@@ -297,7 +296,7 @@ func (wm *WorkspaceManager) DisableEnhancedMode() {
297 if wm.treeRebalancer != nil {296 if wm.treeRebalancer != nil {
298 wm.treeRebalancer.Disable()297 wm.treeRebalancer.Disable()
299 }298 }
300- log.Printf("[workspace] Enhanced validation mode disabled")299+ logging.Info(fmt.Sprintf("[workspace] Enhanced validation mode disabled"))
301 }300 }
302 301
303 // SetEnhancedDebugMode enables/disables debug mode for all enhanced validation components302 // SetEnhancedDebugMode enables/disables debug mode for all enhanced validation components
@@ -308,7 +307,7 @@ func (wm *WorkspaceManager) SetEnhancedDebugMode(debug bool) {
308 if wm.geometryValidator != nil {307 if wm.geometryValidator != nil {
309 wm.geometryValidator.SetDebugMode(debug)308 wm.geometryValidator.SetDebugMode(debug)
310 }309 }
311- log.Printf("[workspace] Enhanced debug mode set to: %v", debug)310+ logging.Debug(fmt.Sprintf("[workspace] Enhanced debug mode set to: %v", debug))
312 }311 }
313 312
314 // GetEnhancedStats returns comprehensive statistics about all enhanced validation components313 // GetEnhancedStats returns comprehensive statistics about all enhanced validation components
@@ -330,7 +329,7 @@ func (wm *WorkspaceManager) GetEnhancedStats() map[string]interface{} {
330 329
331 // ValidateWorkspaceIntegrity performs a comprehensive validation of the workspace330 // ValidateWorkspaceIntegrity performs a comprehensive validation of the workspace
332 func (wm *WorkspaceManager) ValidateWorkspaceIntegrity() error {331 func (wm *WorkspaceManager) ValidateWorkspaceIntegrity() error {
333- log.Printf("[workspace] Performing comprehensive workspace integrity check")332+ logging.Info(fmt.Sprintf("[workspace] Performing comprehensive workspace integrity check"))
334 333
335 // Tree structure validation334 // Tree structure validation
336 if wm.treeValidator != nil {335 if wm.treeValidator != nil {
@@ -341,14 +340,14 @@ func (wm *WorkspaceManager) ValidateWorkspaceIntegrity() error {
341 340
342 // Geometry validation simplified - ValidateWorkspaceLayout removed341 // Geometry validation simplified - ValidateWorkspaceLayout removed
343 342
344- log.Printf("[workspace] Workspace integrity check completed successfully")343+ logging.Info(fmt.Sprintf("[workspace] Workspace integrity check completed successfully"))
345 return nil344 return nil
346 }345 }
347 346
348 // ShutdownEnhancedComponents gracefully shuts down all enhanced validation components347 // ShutdownEnhancedComponents gracefully shuts down all enhanced validation components
349 func (wm *WorkspaceManager) ShutdownEnhancedComponents() {348 func (wm *WorkspaceManager) ShutdownEnhancedComponents() {
350- log.Printf("[workspace] Shutting down enhanced validation components")349+ logging.Info(fmt.Sprintf("[workspace] Shutting down enhanced validation components"))
351 350
352 // Other components don't require explicit shutdown currently351 // Other components don't require explicit shutdown currently
353- log.Printf("[workspace] Enhanced validation components shutdown complete")352+ logging.Info(fmt.Sprintf("[workspace] Enhanced validation components shutdown complete"))
354 }353 }
M internal/app/browser/workspace_pane_mode.go +21 -19
@@ -2,7 +2,9 @@
2 package browser2 package browser
3 3
4 import (4 import (
5- "log"5+ "fmt"
6+
7+ "github.com/bnema/dumber/internal/logging"
6 )8 )
7 9
8 // EnterPaneMode activates pane mode on the currently focused pane10 // EnterPaneMode activates pane mode on the currently focused pane
@@ -16,17 +18,17 @@ func (wm *WorkspaceManager) EnterPaneMode() {
16 // Get the currently focused pane18 // Get the currently focused pane
17 activeNode := wm.GetActiveNode()19 activeNode := wm.GetActiveNode()
18 if activeNode == nil {20 if activeNode == nil {
19- log.Printf("[pane-mode] Cannot enter pane mode: no active pane")21+ logging.Error(fmt.Sprintf("[pane-mode] Cannot enter pane mode: no active pane"))
20 return22 return
21 }23 }
22 24
23 // Already in pane mode on this pane?25 // Already in pane mode on this pane?
24 if wm.paneModeActive && wm.paneModeActivePane == activeNode {26 if wm.paneModeActive && wm.paneModeActivePane == activeNode {
25- log.Printf("[pane-mode] Pane mode already active on this pane")27+ logging.Info(fmt.Sprintf("[pane-mode] Pane mode already active on this pane"))
26 return28 return
27 }29 }
28 30
29- log.Printf("[pane-mode] Entering pane mode on pane %p (workspace=%p)", activeNode, wm)31+ logging.Debug(fmt.Sprintf("[pane-mode] Entering pane mode on pane %p (workspace=%p)", activeNode, wm))
30 32
31 wm.paneModeActive = true33 wm.paneModeActive = true
32 wm.paneModeActivePane = activeNode34 wm.paneModeActivePane = activeNode
@@ -51,7 +53,7 @@ func (wm *WorkspaceManager) ExitPaneMode(reason string) {
51 return53 return
52 }54 }
53 55
54- log.Printf("[pane-mode] Exiting pane mode: %s", reason)56+ logging.Info(fmt.Sprintf("[pane-mode] Exiting pane mode: %s", reason))
55 57
56 wm.paneModeActive = false58 wm.paneModeActive = false
57 wm.paneModeActivePane = nil59 wm.paneModeActivePane = nil
@@ -74,7 +76,7 @@ func (wm *WorkspaceManager) HandlePaneAction(action string) {
74 76
75 if !wm.paneModeActive {77 if !wm.paneModeActive {
76 wm.paneMutex.Unlock()78 wm.paneMutex.Unlock()
77- log.Printf("[pane-mode] Ignoring action '%s': pane mode not active", action)79+ logging.Info(fmt.Sprintf("[pane-mode] Ignoring action '%s': pane mode not active", action))
78 return80 return
79 }81 }
80 82
@@ -82,12 +84,12 @@ func (wm *WorkspaceManager) HandlePaneAction(action string) {
82 wm.paneMutex.Unlock()84 wm.paneMutex.Unlock()
83 85
84 if activePane == nil {86 if activePane == nil {
85- log.Printf("[pane-mode] Ignoring action '%s': no active pane", action)87+ logging.Info(fmt.Sprintf("[pane-mode] Ignoring action '%s': no active pane", action))
86 wm.ExitPaneMode("no-active-pane")88 wm.ExitPaneMode("no-active-pane")
87 return89 return
88 }90 }
89 91
90- log.Printf("[pane-mode] Handling action '%s' on pane %p", action, activePane)92+ logging.Debug(fmt.Sprintf("[pane-mode] Handling action '%s' on pane %p", action, activePane))
91 93
92 switch action {94 switch action {
93 case "close":95 case "close":
@@ -97,7 +99,7 @@ func (wm *WorkspaceManager) HandlePaneAction(action string) {
97 case "stack":99 case "stack":
98 wm.handlePaneStack(activePane)100 wm.handlePaneStack(activePane)
99 default:101 default:
100- log.Printf("[pane-mode] Unknown action: %s", action)102+ logging.Warn(fmt.Sprintf("[pane-mode] Unknown action: %s", action))
101 }103 }
102 104
103 // Exit pane mode after action105 // Exit pane mode after action
@@ -110,14 +112,14 @@ func (wm *WorkspaceManager) handlePaneClose(node *paneNode) {
110 return112 return
111 }113 }
112 114
113- log.Printf("[pane-mode] Closing pane %p", node)115+ logging.Debug(fmt.Sprintf("[pane-mode] Closing pane %p", node))
114 116
115 // Notify JavaScript first so it can show toast117 // Notify JavaScript first so it can show toast
116 wm.dispatchPaneModeEvent("action", "close")118 wm.dispatchPaneModeEvent("action", "close")
117 119
118 // Close the pane120 // Close the pane
119 if err := wm.ClosePane(node); err != nil {121 if err := wm.ClosePane(node); err != nil {
120- log.Printf("[pane-mode] Failed to close pane: %v", err)122+ logging.Error(fmt.Sprintf("[pane-mode] Failed to close pane: %v", err))
121 }123 }
122 }124 }
123 125
@@ -139,11 +141,11 @@ func (wm *WorkspaceManager) handlePaneSplit(node *paneNode, action string) {
139 case "split-down":141 case "split-down":
140 direction = DirectionDown142 direction = DirectionDown
141 default:143 default:
142- log.Printf("[pane-mode] Invalid split action: %s", action)144+ logging.Error(fmt.Sprintf("[pane-mode] Invalid split action: %s", action))
143 return145 return
144 }146 }
145 147
146- log.Printf("[pane-mode] Splitting pane %p in direction %s", node, direction)148+ logging.Debug(fmt.Sprintf("[pane-mode] Splitting pane %p in direction %s", node, direction))
147 149
148 // Notify JavaScript first so it can show toast150 // Notify JavaScript first so it can show toast
149 wm.dispatchPaneModeEvent("action", action)151 wm.dispatchPaneModeEvent("action", action)
@@ -151,14 +153,14 @@ func (wm *WorkspaceManager) handlePaneSplit(node *paneNode, action string) {
151 // Perform the split (pass nil so it creates a fresh pane, not popup mode)153 // Perform the split (pass nil so it creates a fresh pane, not popup mode)
152 newNode, err := wm.splitNode(node, direction, nil)154 newNode, err := wm.splitNode(node, direction, nil)
153 if err != nil {155 if err != nil {
154- log.Printf("[pane-mode] Failed to split pane: %v", err)156+ logging.Error(fmt.Sprintf("[pane-mode] Failed to split pane: %v", err))
155 return157 return
156 }158 }
157 159
158 // Load initial URL in the new pane so scripts execute160 // Load initial URL in the new pane so scripts execute
159 wm.clonePaneState(node, newNode)161 wm.clonePaneState(node, newNode)
160 162
161- log.Printf("[pane-mode] Split successful: direction=%s", direction)163+ logging.Debug(fmt.Sprintf("[pane-mode] Split successful: direction=%s", direction))
162 }164 }
163 165
164 // handlePaneStack creates a stacked pane166 // handlePaneStack creates a stacked pane
@@ -167,7 +169,7 @@ func (wm *WorkspaceManager) handlePaneStack(node *paneNode) {
167 return169 return
168 }170 }
169 171
170- log.Printf("[pane-mode] Stacking pane %p", node)172+ logging.Debug(fmt.Sprintf("[pane-mode] Stacking pane %p", node))
171 173
172 // Notify JavaScript first so it can show toast174 // Notify JavaScript first so it can show toast
173 wm.dispatchPaneModeEvent("action", "stack")175 wm.dispatchPaneModeEvent("action", "stack")
@@ -175,14 +177,14 @@ func (wm *WorkspaceManager) handlePaneStack(node *paneNode) {
175 // Perform the stack operation177 // Perform the stack operation
176 newNode, err := wm.stackedPaneManager.StackPane(node)178 newNode, err := wm.stackedPaneManager.StackPane(node)
177 if err != nil {179 if err != nil {
178- log.Printf("[pane-mode] Failed to stack pane: %v", err)180+ logging.Error(fmt.Sprintf("[pane-mode] Failed to stack pane: %v", err))
179 return181 return
180 }182 }
181 183
182 // Load initial URL in the new pane so scripts execute184 // Load initial URL in the new pane so scripts execute
183 wm.clonePaneState(node, newNode)185 wm.clonePaneState(node, newNode)
184 186
185- log.Printf("[pane-mode] Stack successful")187+ logging.Info(fmt.Sprintf("[pane-mode] Stack successful"))
186 }188 }
187 189
188 // dispatchPaneModeEvent sends a pane mode event to JavaScript for UI updates190 // dispatchPaneModeEvent sends a pane mode event to JavaScript for UI updates
@@ -198,7 +200,7 @@ func (wm *WorkspaceManager) dispatchPaneModeEvent(event string, detail string) {
198 }200 }
199 201
200 if err := view.DispatchCustomEvent("dumber:pane-mode", data); err != nil {202 if err := view.DispatchCustomEvent("dumber:pane-mode", data); err != nil {
201- log.Printf("[pane-mode] Failed to dispatch event: %v", err)203+ logging.Error(fmt.Sprintf("[pane-mode] Failed to dispatch event: %v", err))
202 }204 }
203 }205 }
204 206
M internal/app/browser/workspace_pane_ops.go +57 -57
@@ -4,11 +4,11 @@ package browser
4 import (4 import (
5 "errors"5 "errors"
6 "fmt"6 "fmt"
7- "log"
8 7
9 "github.com/diamondburned/gotk4/pkg/gdk/v4"8 "github.com/diamondburned/gotk4/pkg/gdk/v4"
10 "github.com/diamondburned/gotk4/pkg/gtk/v4"9 "github.com/diamondburned/gotk4/pkg/gtk/v4"
11 10
11+ "github.com/bnema/dumber/internal/logging"
12 "github.com/bnema/dumber/pkg/webkit"12 "github.com/bnema/dumber/pkg/webkit"
13 )13 )
14 14
@@ -67,7 +67,7 @@ func (wm *WorkspaceManager) collectLeavesFromWithDirection(node *paneNode, direc
67 return67 return
68 }68 }
69 if visited[n] {69 if visited[n] {
70- log.Printf("[workspace] collectLeavesFromWithDirection: cycle detected in tree")70+ logging.Error(fmt.Sprintf("[workspace] collectLeavesFromWithDirection: cycle detected in tree"))
71 return71 return
72 }72 }
73 visited[n] = true73 visited[n] = true
@@ -121,7 +121,7 @@ func (wm *WorkspaceManager) collectLeavesFrom(node *paneNode) []*paneNode {
121 return121 return
122 }122 }
123 if visited[n] {123 if visited[n] {
124- log.Printf("[workspace] collectLeavesFrom: cycle detected in tree")124+ logging.Error(fmt.Sprintf("[workspace] collectLeavesFrom: cycle detected in tree"))
125 return125 return
126 }126 }
127 visited[n] = true127 visited[n] = true
@@ -180,22 +180,22 @@ func (wm *WorkspaceManager) createPane(view *webkit.WebView) (*BrowserPane, erro
180 // clonePaneState sets up a new pane with cloned state from another pane180 // clonePaneState sets up a new pane with cloned state from another pane
181 func (wm *WorkspaceManager) clonePaneState(_ *paneNode, target *paneNode) {181 func (wm *WorkspaceManager) clonePaneState(_ *paneNode, target *paneNode) {
182 if wm == nil || target == nil {182 if wm == nil || target == nil {
183- log.Printf("[workspace] clonePaneState: wm or target is nil")183+ logging.Debug(fmt.Sprintf("[workspace] clonePaneState: wm or target is nil"))
184 return184 return
185 }185 }
186 if target.pane == nil || target.pane.webView == nil {186 if target.pane == nil || target.pane.webView == nil {
187- log.Printf("[workspace] clonePaneState: target pane or webView is nil")187+ logging.Debug(fmt.Sprintf("[workspace] clonePaneState: target pane or webView is nil"))
188 return188 return
189 }189 }
190 190
191 // Load about:blank and auto-open omnibox191 // Load about:blank and auto-open omnibox
192 const blankURL = "about:blank"192 const blankURL = "about:blank"
193 193
194- log.Printf("[workspace] clonePaneState: loading %s in pane %p", blankURL, target.pane)194+ logging.Debug(fmt.Sprintf("[workspace] clonePaneState: loading %s in pane %p", blankURL, target.pane))
195 if err := target.pane.webView.LoadURL(blankURL); err != nil {195 if err := target.pane.webView.LoadURL(blankURL); err != nil {
196- log.Printf("[workspace] failed to load blank page in new pane: %v", err)196+ logging.Error(fmt.Sprintf("[workspace] failed to load blank page in new pane: %v", err))
197 } else {197 } else {
198- log.Printf("[workspace] successfully initiated load of %s", blankURL)198+ logging.Debug(fmt.Sprintf("[workspace] successfully initiated load of %s", blankURL))
199 }199 }
200 }200 }
201 201
@@ -218,13 +218,13 @@ func (wm *WorkspaceManager) safelyDetachControllersBeforeReparent(node *paneNode
218 if target.hoverToken != nil {218 if target.hoverToken != nil {
219 target.pendingHoverReattach = true219 target.pendingHoverReattach = true
220 target.hoverToken = nil // Clear reference, don't manually remove220 target.hoverToken = nil // Clear reference, don't manually remove
221- log.Printf("[workspace] Marked hover controller for reattach on pane %p (GTK will auto-remove)", target)221+ logging.Debug(fmt.Sprintf("[workspace] Marked hover controller for reattach on pane %p (GTK will auto-remove)", target))
222 }222 }
223 223
224 if target.focusControllerToken != 0 {224 if target.focusControllerToken != 0 {
225 target.pendingFocusReattach = true225 target.pendingFocusReattach = true
226 target.focusControllerToken = 0 // Clear reference, don't manually remove226 target.focusControllerToken = 0 // Clear reference, don't manually remove
227- log.Printf("[workspace] Marked focus controller for reattach on pane %p (GTK will auto-remove)", target)227+ logging.Debug(fmt.Sprintf("[workspace] Marked focus controller for reattach on pane %p (GTK will auto-remove)", target))
228 }228 }
229 }229 }
230 230
@@ -246,7 +246,7 @@ func (wm *WorkspaceManager) closeCurrentPane() {
246 return246 return
247 }247 }
248 if err := wm.ClosePane(wm.GetActiveNode()); err != nil {248 if err := wm.ClosePane(wm.GetActiveNode()); err != nil {
249- log.Printf("[workspace] close current pane failed: %v", err)249+ logging.Error(fmt.Sprintf("[workspace] close current pane failed: %v", err))
250 }250 }
251 }251 }
252 252
@@ -343,7 +343,7 @@ func (wm *WorkspaceManager) removeFromMaps(webView *webkit.WebView) {
343 delete(wm.viewToNode, webView)343 delete(wm.viewToNode, webView)
344 delete(wm.lastSplitMsg, webView)344 delete(wm.lastSplitMsg, webView)
345 delete(wm.lastExitMsg, webView)345 delete(wm.lastExitMsg, webView)
346- log.Printf("[workspace] removed webview %p from tracking maps", webView)346+ logging.Debug(fmt.Sprintf("[workspace] removed webview %p from tracking maps", webView))
347 }347 }
348 348
349 // removeFromAppPanes removes a BrowserPane from the app.panes slice349 // removeFromAppPanes removes a BrowserPane from the app.panes slice
@@ -355,7 +355,7 @@ func (wm *WorkspaceManager) removeFromAppPanes(pane *BrowserPane) {
355 for i, p := range wm.app.panes {355 for i, p := range wm.app.panes {
356 if p == pane {356 if p == pane {
357 wm.app.panes = append(wm.app.panes[:i], wm.app.panes[i+1:]...)357 wm.app.panes = append(wm.app.panes[:i], wm.app.panes[i+1:]...)
358- log.Printf("[workspace] removed pane from app.panes slice, remaining: %d", len(wm.app.panes))358+ logging.Debug(fmt.Sprintf("[workspace] removed pane from app.panes slice, remaining: %d", len(wm.app.panes)))
359 return359 return
360 }360 }
361 }361 }
@@ -383,7 +383,7 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string, existi
383 if target.parent != nil && target.parent.isStacked {383 if target.parent != nil && target.parent.isStacked {
384 // Target is in a stack - we'll create the split around the entire stack384 // Target is in a stack - we'll create the split around the entire stack
385 stackContainer = target.parent385 stackContainer = target.parent
386- log.Printf("[workspace] target is in stack, will split around the stack: originalTarget=%p stackContainer=%p", originalTarget, stackContainer)386+ logging.Debug(fmt.Sprintf("[workspace] target is in stack, will split around the stack: originalTarget=%p stackContainer=%p", originalTarget, stackContainer))
387 }387 }
388 388
389 // Determine what we're actually splitting389 // Determine what we're actually splitting
@@ -394,12 +394,12 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string, existi
394 // Case: splitting from inside a stack - we split around the entire stack394 // Case: splitting from inside a stack - we split around the entire stack
395 splitTarget = stackContainer395 splitTarget = stackContainer
396 splitTargetContainer = stackContainer.container396 splitTargetContainer = stackContainer.container
397- log.Printf("[workspace] splitting around stack: stackContainer=%p", splitTarget)397+ logging.Debug(fmt.Sprintf("[workspace] splitting around stack: stackContainer=%p", splitTarget))
398 } else {398 } else {
399 // Case: normal split from a simple pane399 // Case: normal split from a simple pane
400 splitTarget = target400 splitTarget = target
401 splitTargetContainer = target.container401 splitTargetContainer = target.container
402- log.Printf("[workspace] normal split from pane: target=%p", splitTarget)402+ logging.Info(fmt.Sprintf("[workspace] normal split from pane: target=%p", splitTarget))
403 }403 }
404 404
405 // Use provided pane or create new one405 // Use provided pane or create new one
@@ -408,7 +408,7 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string, existi
408 if existingPane != nil {408 if existingPane != nil {
409 // Reuse existing pane (for popup insertion)409 // Reuse existing pane (for popup insertion)
410 newPane = existingPane410 newPane = existingPane
411- log.Printf("[workspace] Using existing pane for split: %p", newPane)411+ logging.Debug(fmt.Sprintf("[workspace] Using existing pane for split: %p", newPane))
412 } else {412 } else {
413 // Create new WebView and pane (for normal splits)413 // Create new WebView and pane (for normal splits)
414 newView, err := wm.createWebView()414 newView, err := wm.createWebView()
@@ -420,7 +420,7 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string, existi
420 if err != nil {420 if err != nil {
421 return nil, err421 return nil, err
422 }422 }
423- log.Printf("[workspace] Created new pane for split: %p", newPane)423+ logging.Info(fmt.Sprintf("[workspace] Created new pane for split: %p", newPane))
424 }424 }
425 425
426 if handler := newPane.MessageHandler(); handler != nil {426 if handler := newPane.MessageHandler(); handler != nil {
@@ -451,13 +451,13 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string, existi
451 if splitTargetContainer != nil {451 if splitTargetContainer != nil {
452 webkit.WidgetSetHExpand(splitTargetContainer, true)452 webkit.WidgetSetHExpand(splitTargetContainer, true)
453 webkit.WidgetSetVExpand(splitTargetContainer, true)453 webkit.WidgetSetVExpand(splitTargetContainer, true)
454- log.Printf("[workspace] configured stack wrapper for split: %p", splitTargetContainer)454+ logging.Debug(fmt.Sprintf("[workspace] configured stack wrapper for split: %p", splitTargetContainer))
455 }455 }
456 }456 }
457 457
458 orientation, existingFirst := mapDirection(direction)458 orientation, existingFirst := mapDirection(direction)
459- log.Printf("[workspace] splitting direction=%s orientation=%v existingFirst=%v splitTarget.parent=%p splitTarget.isStacked=%v isPopup=%v",459+ logging.Debug(fmt.Sprintf("[workspace] splitting direction=%s orientation=%v existingFirst=%v splitTarget.parent=%p splitTarget.isStacked=%v isPopup=%v",
460- direction, orientation, existingFirst, splitTarget.parent, splitTarget.isStacked, existingPane != nil)460+ direction, orientation, existingFirst, splitTarget.parent, splitTarget.isStacked, existingPane != nil))
461 461
462 paned := gtk.NewPaned(orientation)462 paned := gtk.NewPaned(orientation)
463 if paned == nil {463 if paned == nil {
@@ -500,7 +500,7 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string, existi
500 // Remove active border class if present (prevents GTK bloom filter corruption during unparent)500 // Remove active border class if present (prevents GTK bloom filter corruption during unparent)
501 if webkit.WidgetHasCSSClass(splitTargetContainer, activePaneClass) {501 if webkit.WidgetHasCSSClass(splitTargetContainer, activePaneClass) {
502 webkit.WidgetRemoveCSSClass(splitTargetContainer, activePaneClass)502 webkit.WidgetRemoveCSSClass(splitTargetContainer, activePaneClass)
503- log.Printf("[workspace] removed active class from split target (will transfer to new pane)")503+ logging.Debug(fmt.Sprintf("[workspace] removed active class from split target (will transfer to new pane)"))
504 }504 }
505 }505 }
506 506
@@ -510,7 +510,7 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string, existi
510 // For non-root splits, unparent from parent paned510 // For non-root splits, unparent from parent paned
511 if parent != nil && parent.container != nil {511 if parent != nil && parent.container != nil {
512 // Split target has a parent paned - remove it (automatically unparents in GTK4)512 // Split target has a parent paned - remove it (automatically unparents in GTK4)
513- log.Printf("[workspace] unparenting split target container=%p from parent paned=%p", splitTargetContainer, parent.container)513+ logging.Debug(fmt.Sprintf("[workspace] unparenting split target container=%p from parent paned=%p", splitTargetContainer, parent.container))
514 if parentPaned, ok := parent.container.(*gtk.Paned); ok && parentPaned != nil {514 if parentPaned, ok := parent.container.(*gtk.Paned); ok && parentPaned != nil {
515 if parent.left == splitTarget {515 if parent.left == splitTarget {
516 parentPaned.SetStartChild(nil)516 parentPaned.SetStartChild(nil)
@@ -553,33 +553,33 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string, existi
553 if parent == nil {553 if parent == nil {
554 // ROOT SPLIT: Manipulate TabManager.ContentArea554 // ROOT SPLIT: Manipulate TabManager.ContentArea
555 if wm.app == nil || wm.app.tabManager == nil || wm.app.tabManager.ContentArea == nil {555 if wm.app == nil || wm.app.tabManager == nil || wm.app.tabManager.ContentArea == nil {
556- log.Printf("[workspace] ERROR: Cannot perform root split - tab manager not initialized")556+ logging.Error(fmt.Sprintf("[workspace] ERROR: Cannot perform root split - tab manager not initialized"))
557 return nil, fmt.Errorf("tab manager not initialized")557 return nil, fmt.Errorf("tab manager not initialized")
558 }558 }
559 559
560 contentBox, ok := wm.app.tabManager.ContentArea.(*gtk.Box)560 contentBox, ok := wm.app.tabManager.ContentArea.(*gtk.Box)
561 if !ok || contentBox == nil {561 if !ok || contentBox == nil {
562- log.Printf("[workspace] ERROR: ContentArea is not a Box")562+ logging.Error(fmt.Sprintf("[workspace] ERROR: ContentArea is not a Box"))
563 return nil, fmt.Errorf("ContentArea is not a Box")563 return nil, fmt.Errorf("ContentArea is not a Box")
564 }564 }
565 565
566 // Remove the old workspace root from ContentArea566 // Remove the old workspace root from ContentArea
567 contentBox.Remove(splitTargetContainer)567 contentBox.Remove(splitTargetContainer)
568- log.Printf("[workspace] ROOT SPLIT: removed old root %p from ContentArea", splitTargetContainer)568+ logging.Debug(fmt.Sprintf("[workspace] ROOT SPLIT: removed old root %p from ContentArea", splitTargetContainer))
569 569
570 // Attach the new paned to ContentArea FIRST570 // Attach the new paned to ContentArea FIRST
571 contentBox.Append(paned)571 contentBox.Append(paned)
572- log.Printf("[workspace] ROOT SPLIT: attached paned %p to ContentArea", paned)572+ logging.Debug(fmt.Sprintf("[workspace] ROOT SPLIT: attached paned %p to ContentArea", paned))
573 573
574 // Now add children to the paned - GTK will handle reparenting automatically574 // Now add children to the paned - GTK will handle reparenting automatically
575 if existingFirst {575 if existingFirst {
576 paned.SetStartChild(splitTargetContainer)576 paned.SetStartChild(splitTargetContainer)
577 paned.SetEndChild(newContainer)577 paned.SetEndChild(newContainer)
578- log.Printf("[workspace] ROOT SPLIT: added splitTarget=%p (start), new=%p (end)", splitTargetContainer, newContainer)578+ logging.Debug(fmt.Sprintf("[workspace] ROOT SPLIT: added splitTarget=%p (start), new=%p (end)", splitTargetContainer, newContainer))
579 } else {579 } else {
580 paned.SetStartChild(newContainer)580 paned.SetStartChild(newContainer)
581 paned.SetEndChild(splitTargetContainer)581 paned.SetEndChild(splitTargetContainer)
582- log.Printf("[workspace] ROOT SPLIT: added new=%p (start), splitTarget=%p (end)", newContainer, splitTargetContainer)582+ logging.Debug(fmt.Sprintf("[workspace] ROOT SPLIT: added new=%p (start), splitTarget=%p (end)", newContainer, splitTargetContainer))
583 }583 }
584 } else {584 } else {
585 // NON-ROOT SPLIT: Standard approach585 // NON-ROOT SPLIT: Standard approach
@@ -589,13 +589,13 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string, existi
589 if splitTargetContainer != nil {589 if splitTargetContainer != nil {
590 paned.SetStartChild(splitTargetContainer)590 paned.SetStartChild(splitTargetContainer)
591 paned.SetEndChild(newContainer)591 paned.SetEndChild(newContainer)
592- log.Printf("[workspace] added splitTarget=%p as start child, new=%p as end child", splitTargetContainer, newContainer)592+ logging.Debug(fmt.Sprintf("[workspace] added splitTarget=%p as start child, new=%p as end child", splitTargetContainer, newContainer))
593 }593 }
594 } else {594 } else {
595 if splitTargetContainer != nil {595 if splitTargetContainer != nil {
596 paned.SetStartChild(newContainer)596 paned.SetStartChild(newContainer)
597 paned.SetEndChild(splitTargetContainer)597 paned.SetEndChild(splitTargetContainer)
598- log.Printf("[workspace] added new=%p as start child, splitTarget=%p as end child", newContainer, splitTargetContainer)598+ logging.Debug(fmt.Sprintf("[workspace] added new=%p as start child, splitTarget=%p as end child", newContainer, splitTargetContainer))
599 }599 }
600 }600 }
601 601
@@ -608,7 +608,7 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string, existi
608 }608 }
609 webkit.WidgetQueueAllocate(parent.container)609 webkit.WidgetQueueAllocate(parent.container)
610 paned.QueueAllocate()610 paned.QueueAllocate()
611- log.Printf("[workspace] paned inserted into parent=%p", parent.container)611+ logging.Info(fmt.Sprintf("[workspace] paned inserted into parent=%p", parent.container))
612 }612 }
613 }613 }
614 614
@@ -637,7 +637,7 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string, existi
637 wm.app.panes = append(wm.app.panes, newPane)637 wm.app.panes = append(wm.app.panes, newPane)
638 } else {638 } else {
639 // For existing popup panes, they're already in app.panes from createPane() in handlePopupReadyToShow639 // For existing popup panes, they're already in app.panes from createPane() in handlePopupReadyToShow
640- log.Printf("[workspace] Skipping app.panes append for existing popup pane (already added)")640+ logging.Info(fmt.Sprintf("[workspace] Skipping app.panes append for existing popup pane (already added)"))
641 }641 }
642 642
643 if newPane.zoomController != nil {643 if newPane.zoomController != nil {
@@ -755,26 +755,26 @@ func (wm *WorkspaceManager) swapContainers(grand *paneNode, parent *paneNode, si
755 // so it has no GTK parent now.755 // so it has no GTK parent now.
756 756
757 if wm.app == nil || wm.app.tabManager == nil || wm.app.tabManager.ContentArea == nil {757 if wm.app == nil || wm.app.tabManager == nil || wm.app.tabManager.ContentArea == nil {
758- log.Printf("[workspace] ERROR: Cannot promote sibling to root - tab manager not initialized")758+ logging.Error(fmt.Sprintf("[workspace] ERROR: Cannot promote sibling to root - tab manager not initialized"))
759 return759 return
760 }760 }
761 761
762 contentBox, ok := wm.app.tabManager.ContentArea.(*gtk.Box)762 contentBox, ok := wm.app.tabManager.ContentArea.(*gtk.Box)
763 if !ok || contentBox == nil {763 if !ok || contentBox == nil {
764- log.Printf("[workspace] ERROR: ContentArea is not a Box")764+ logging.Error(fmt.Sprintf("[workspace] ERROR: ContentArea is not a Box"))
765 return765 return
766 }766 }
767 767
768 // CRITICAL: Remove the old paned from ContentArea first768 // CRITICAL: Remove the old paned from ContentArea first
769 if parent != nil && parent.container != nil {769 if parent != nil && parent.container != nil {
770 contentBox.Remove(parent.container)770 contentBox.Remove(parent.container)
771- log.Printf("[workspace] Removed old paned %p from ContentArea", parent.container)771+ logging.Debug(fmt.Sprintf("[workspace] Removed old paned %p from ContentArea", parent.container))
772 }772 }
773 773
774 // Now add sibling to ContentArea as the new root774 // Now add sibling to ContentArea as the new root
775 contentBox.Append(sibling.container)775 contentBox.Append(sibling.container)
776 webkit.WidgetSetVisible(sibling.container, true)776 webkit.WidgetSetVisible(sibling.container, true)
777- log.Printf("[workspace] Promoted sibling %p to root in ContentArea", sibling.container)777+ logging.Debug(fmt.Sprintf("[workspace] Promoted sibling %p to root in ContentArea", sibling.container))
778 }778 }
779 return779 return
780 }780 }
@@ -789,15 +789,15 @@ func (wm *WorkspaceManager) swapContainers(grand *paneNode, parent *paneNode, si
789 // GTK4 PanedSetStartChild/EndChild auto-unparent from current parent789 // GTK4 PanedSetStartChild/EndChild auto-unparent from current parent
790 // Focus was already cleared before unparenting closed pane790 // Focus was already cleared before unparenting closed pane
791 if parent := webkit.WidgetGetParent(sibling.container); parent != nil && parent != grand.container {791 if parent := webkit.WidgetGetParent(sibling.container); parent != nil && parent != grand.container {
792- log.Printf("[workspace] widget %p reparenting from %p to %p (GTK4 will auto-unparent)", sibling.container, parent, grand.container)792+ logging.Debug(fmt.Sprintf("[workspace] widget %p reparenting from %p to %p (GTK4 will auto-unparent)", sibling.container, parent, grand.container))
793 }793 }
794 794
795 if grand.left == sibling {795 if grand.left == sibling {
796 grandPaned.SetStartChild(sibling.container)796 grandPaned.SetStartChild(sibling.container)
797- log.Printf("[workspace] swapContainers: set widget %p as start child of paned %p", sibling.container, grand.container)797+ logging.Debug(fmt.Sprintf("[workspace] swapContainers: set widget %p as start child of paned %p", sibling.container, grand.container))
798 } else {798 } else {
799 grandPaned.SetEndChild(sibling.container)799 grandPaned.SetEndChild(sibling.container)
800- log.Printf("[workspace] swapContainers: set widget %p as end child of paned %p", sibling.container, grand.container)800+ logging.Debug(fmt.Sprintf("[workspace] swapContainers: set widget %p as end child of paned %p", sibling.container, grand.container))
801 }801 }
802 webkit.WidgetQueueAllocate(grand.container)802 webkit.WidgetQueueAllocate(grand.container)
803 }803 }
@@ -821,7 +821,7 @@ func (wm *WorkspaceManager) cascadePromotion(singleChildPaned *paneNode) {
821 return821 return
822 }822 }
823 823
824- log.Printf("[workspace] Cascading promotion: replacing paned %p with its only child %p", singleChildPaned, onlyChild)824+ logging.Info(fmt.Sprintf("[workspace] Cascading promotion: replacing paned %p with its only child %p", singleChildPaned, onlyChild))
825 825
826 greatGrandparent := singleChildPaned.parent826 greatGrandparent := singleChildPaned.parent
827 827
@@ -830,7 +830,7 @@ func (wm *WorkspaceManager) cascadePromotion(singleChildPaned *paneNode) {
830 if greatGrandparent == nil {830 if greatGrandparent == nil {
831 // Single child becomes new root831 // Single child becomes new root
832 wm.root = onlyChild832 wm.root = onlyChild
833- log.Printf("[workspace] Only child %p promoted to root", onlyChild)833+ logging.Debug(fmt.Sprintf("[workspace] Only child %p promoted to root", onlyChild))
834 834
835 // Attach to window835 // Attach to window
836 if onlyChild.container != nil {836 if onlyChild.container != nil {
@@ -847,7 +847,7 @@ func (wm *WorkspaceManager) cascadePromotion(singleChildPaned *paneNode) {
847 greatGrandparent.right = onlyChild847 greatGrandparent.right = onlyChild
848 }848 }
849 849
850- log.Printf("[workspace] Only child %p attached to great-grandparent %p", onlyChild, greatGrandparent)850+ logging.Debug(fmt.Sprintf("[workspace] Only child %p attached to great-grandparent %p", onlyChild, greatGrandparent))
851 851
852 // Reparent widget in GTK852 // Reparent widget in GTK
853 if greatGrandPaned, ok := greatGrandparent.container.(*gtk.Paned); ok && greatGrandPaned != nil && onlyChild.container != nil {853 if greatGrandPaned, ok := greatGrandparent.container.(*gtk.Paned); ok && greatGrandPaned != nil && onlyChild.container != nil {
@@ -880,7 +880,7 @@ func (wm *WorkspaceManager) cascadePromotion(singleChildPaned *paneNode) {
880 880
881 // Check if great-grandparent now also has only one child (recursive case)881 // Check if great-grandparent now also has only one child (recursive case)
882 if greatGrandparent != nil && ((greatGrandparent.left == nil) != (greatGrandparent.right == nil)) {882 if greatGrandparent != nil && ((greatGrandparent.left == nil) != (greatGrandparent.right == nil)) {
883- log.Printf("[workspace] Great-grandparent %p also has only one child, continuing cascade", greatGrandparent)883+ logging.Info(fmt.Sprintf("[workspace] Great-grandparent %p also has only one child, continuing cascade", greatGrandparent))
884 wm.cascadePromotion(greatGrandparent)884 wm.cascadePromotion(greatGrandparent)
885 }885 }
886 }886 }
@@ -900,7 +900,7 @@ func (wm *WorkspaceManager) attachRoot(root *paneNode) {
900 // Detach from previous container (paned, stack, etc.) before attaching to ContentArea900 // Detach from previous container (paned, stack, etc.) before attaching to ContentArea
901 // While GTK4 auto-unparents for paned operations, Box.Append requires manual unparent901 // While GTK4 auto-unparents for paned operations, Box.Append requires manual unparent
902 if parent := webkit.WidgetGetParent(root.container); parent != nil {902 if parent := webkit.WidgetGetParent(root.container); parent != nil {
903- log.Printf("[workspace] unparenting widget %p from parent %p before root attach", root.container, parent)903+ logging.Debug(fmt.Sprintf("[workspace] unparenting widget %p from parent %p before root attach", root.container, parent))
904 webkit.WidgetUnparent(root.container)904 webkit.WidgetUnparent(root.container)
905 // Note: In gotk4, the Go object reference remains valid after unparent905 // Note: In gotk4, the Go object reference remains valid after unparent
906 // Only the GTK parent relationship is cleared906 // Only the GTK parent relationship is cleared
@@ -913,13 +913,13 @@ func (wm *WorkspaceManager) attachRoot(root *paneNode) {
913 913
914 // Add to ContentArea (the old root will be orphaned and garbage collected)914 // Add to ContentArea (the old root will be orphaned and garbage collected)
915 if wm.app == nil || wm.app.tabManager == nil || wm.app.tabManager.ContentArea == nil {915 if wm.app == nil || wm.app.tabManager == nil || wm.app.tabManager.ContentArea == nil {
916- log.Printf("[workspace] ERROR: Cannot attach root - tab manager not initialized")916+ logging.Error(fmt.Sprintf("[workspace] ERROR: Cannot attach root - tab manager not initialized"))
917 return917 return
918 }918 }
919 919
920 contentBox, ok := wm.app.tabManager.ContentArea.(*gtk.Box)920 contentBox, ok := wm.app.tabManager.ContentArea.(*gtk.Box)
921 if !ok || contentBox == nil {921 if !ok || contentBox == nil {
922- log.Printf("[workspace] ERROR: ContentArea is not a Box")922+ logging.Error(fmt.Sprintf("[workspace] ERROR: ContentArea is not a Box"))
923 return923 return
924 }924 }
925 925
@@ -945,9 +945,9 @@ func (wm *WorkspaceManager) attachRoot(root *paneNode) {
945 945
946 // Verify attachment succeeded946 // Verify attachment succeeded
947 if finalParent := webkit.WidgetGetParent(root.container); finalParent == nil {947 if finalParent := webkit.WidgetGetParent(root.container); finalParent == nil {
948- log.Printf("[workspace] WARNING: widget %p has no parent after SetChild", root.container)948+ logging.Warn(fmt.Sprintf("[workspace] WARNING: widget %p has no parent after SetChild", root.container))
949 } else {949 } else {
950- log.Printf("[workspace] attachRoot successful: widget %p now child of %p", root.container, finalParent)950+ logging.Debug(fmt.Sprintf("[workspace] attachRoot successful: widget %p now child of %p", root.container, finalParent))
951 }951 }
952 }952 }
953 953
@@ -974,7 +974,7 @@ func (wm *WorkspaceManager) cleanupPane(node *paneNode, generation uint) {
974 pane.Cleanup()974 pane.Cleanup()
975 pane.CleanupFromWorkspace(wm)975 pane.CleanupFromWorkspace(wm)
976 if pane.webView != nil {976 if pane.webView != nil {
977- log.Printf("[workspace] releasing WebView reference: %p", pane.webView)977+ logging.Debug(fmt.Sprintf("[workspace] releasing WebView reference: %p", pane.webView))
978 pane.webView = nil978 pane.webView = nil
979 }979 }
980 node.pane = nil980 node.pane = nil
@@ -1002,7 +1002,7 @@ func (wm *WorkspaceManager) decommissionParent(parent *paneNode, generation uint
1002 if !parent.isLeaf && parent.container != nil {1002 if !parent.isLeaf && parent.container != nil {
1003 // In gotk4, we don't need to manually destroy - GTK handles this when unparented1003 // In gotk4, we don't need to manually destroy - GTK handles this when unparented
1004 // Just mark it for cleanup1004 // Just mark it for cleanup
1005- log.Printf("[workspace] marked paned widget for cleanup: %p", parent.container)1005+ logging.Debug(fmt.Sprintf("[workspace] marked paned widget for cleanup: %p", parent.container))
1006 }1006 }
1007 1007
1008 wm.cleanupPane(parent, generation)1008 wm.cleanupPane(parent, generation)
@@ -1031,7 +1031,7 @@ func (wm *WorkspaceManager) promoteNewRoot(ctx closeContext, oldRoot *paneNode)
1031 1031
1032 // cleanupAndExit handles the final pane cleanup and application exit1032 // cleanupAndExit handles the final pane cleanup and application exit
1033 func (wm *WorkspaceManager) cleanupAndExit(node *paneNode) (*paneNode, error) {1033 func (wm *WorkspaceManager) cleanupAndExit(node *paneNode) (*paneNode, error) {
1034- log.Printf("[workspace] closing final pane; exiting browser")1034+ logging.Info(fmt.Sprintf("[workspace] closing final pane; exiting browser"))
1035 wm.cleanupPane(node, wm.nextCleanupGeneration())1035 wm.cleanupPane(node, wm.nextCleanupGeneration())
1036 wm.detachHover(node)1036 wm.detachHover(node)
1037 1037
@@ -1098,7 +1098,7 @@ func (wm *WorkspaceManager) closePane(node *paneNode) (*paneNode, error) {
1098 } else if parent.right == node {1098 } else if parent.right == node {
1099 parentPaned.SetEndChild(nil)1099 parentPaned.SetEndChild(nil)
1100 }1100 }
1101- log.Printf("[workspace] unparented closed pane container: %p", node.container)1101+ logging.Debug(fmt.Sprintf("[workspace] unparented closed pane container: %p", node.container))
1102 }1102 }
1103 }1103 }
1104 }1104 }
@@ -1112,7 +1112,7 @@ func (wm *WorkspaceManager) closePane(node *paneNode) (*paneNode, error) {
1112 } else if parent.right == sibling {1112 } else if parent.right == sibling {
1113 parentPaned.SetEndChild(nil)1113 parentPaned.SetEndChild(nil)
1114 }1114 }
1115- log.Printf("[workspace] unparented sibling container %p from parent paned %p", sibling.container, parent.container)1115+ logging.Debug(fmt.Sprintf("[workspace] unparented sibling container %p from parent paned %p", sibling.container, parent.container))
1116 }1116 }
1117 }1117 }
1118 1118
@@ -1142,7 +1142,7 @@ func (wm *WorkspaceManager) closePane(node *paneNode) (*paneNode, error) {
1142 // STEP 8: Check if grandparent now has only one child (cascade promotion needed)1142 // STEP 8: Check if grandparent now has only one child (cascade promotion needed)
1143 if grandparent != nil && ((grandparent.left == nil) != (grandparent.right == nil)) {1143 if grandparent != nil && ((grandparent.left == nil) != (grandparent.right == nil)) {
1144 // Grandparent has exactly one child - it should be promoted too1144 // Grandparent has exactly one child - it should be promoted too
1145- log.Printf("[workspace] Grandparent %p now has only one child, cascading promotion", grandparent)1145+ logging.Info(fmt.Sprintf("[workspace] Grandparent %p now has only one child, cascading promotion", grandparent))
1146 wm.cascadePromotion(grandparent)1146 wm.cascadePromotion(grandparent)
1147 }1147 }
1148 1148
@@ -1278,7 +1278,7 @@ func (wm *WorkspaceManager) syncPanedDivider(paned *gtk.Paned, orientation gtk.O
1278 }1278 }
1279 1279
1280 paned.SetPosition(pos)1280 paned.SetPosition(pos)
1281- log.Printf("[workspace] Set paned position to 50%%: %d (orientation=%d, size=%dx%d)", pos, orientation, alloc.Width(), alloc.Height())1281+ logging.Debug(fmt.Sprintf("[workspace] Set paned position to 50%%: %d (orientation=%d, size=%dx%d)", pos, orientation, alloc.Width(), alloc.Height()))
1282 return true1282 return true
1283 }1283 }
1284 1284
@@ -1305,7 +1305,7 @@ func (wm *WorkspaceManager) syncPanedDivider(paned *gtk.Paned, orientation gtk.O
1305 1305
1306 if frames >= maxFrames {1306 if frames >= maxFrames {
1307 alloc := paned.Allocation()1307 alloc := paned.Allocation()
1308- log.Printf("[workspace] Unable to stabilize paned position after %d frames (size=%dx%d)", frames, alloc.Width(), alloc.Height())1308+ logging.Error(fmt.Sprintf("[workspace] Unable to stabilize paned position after %d frames (size=%dx%d)", frames, alloc.Width(), alloc.Height()))
1309 return false1309 return false
1310 }1310 }
1311 1311
M internal/app/browser/workspace_popup.go +73 -72
@@ -2,11 +2,12 @@
2 package browser2 package browser
3 3
4 import (4 import (
5- "log"5+ "fmt"
6 "strings"6 "strings"
7 "time"7 "time"
8 8
9 "github.com/bnema/dumber/internal/config"9 "github.com/bnema/dumber/internal/config"
10+ "github.com/bnema/dumber/internal/logging"
10 "github.com/bnema/dumber/pkg/webkit"11 "github.com/bnema/dumber/pkg/webkit"
11 webkitgtk "github.com/diamondburned/gotk4-webkitgtk/pkg/webkit/v6"12 webkitgtk "github.com/diamondburned/gotk4-webkitgtk/pkg/webkit/v6"
12 )13 )
@@ -24,8 +25,8 @@ const (
24 // registerOAuthAutoClose sets up OAuth auto-close functionality for popups25 // registerOAuthAutoClose sets up OAuth auto-close functionality for popups
25 // Note: OAuth detection is now handled by the main-world.js injection script26 // Note: OAuth detection is now handled by the main-world.js injection script
26 func (wm *WorkspaceManager) registerOAuthAutoClose(view *webkit.WebView, url string) {27 func (wm *WorkspaceManager) registerOAuthAutoClose(view *webkit.WebView, url string) {
27- log.Printf("[workspace] OAuth auto-close enabled for popup with URL: %s", url)28+ logging.Info(fmt.Sprintf("[workspace] OAuth auto-close enabled for popup with URL: %s", url))
28- log.Printf("[workspace] OAuth detection will be handled by main-world.js injection script")29+ logging.Info(fmt.Sprintf("[workspace] OAuth detection will be handled by main-world.js injection script"))
29 }30 }
30 31
31 // insertIndependentPane inserts a new independent pane next to the source32 // insertIndependentPane inserts a new independent pane next to the source
@@ -59,7 +60,7 @@ func (wm *WorkspaceManager) configureRelatedPopup(sourceNode *paneNode, webView
59 related := webView60 related := webView
60 newPane, err := wm.createPane(related)61 newPane, err := wm.createPane(related)
61 if err != nil {62 if err != nil {
62- log.Printf("[workspace] failed to create related popup pane: %v", err)63+ logging.Error(fmt.Sprintf("[workspace] failed to create related popup pane: %v", err))
63 return64 return
64 }65 }
65 direction := strings.ToLower(wm.app.config.Workspace.Popups.Placement)66 direction := strings.ToLower(wm.app.config.Workspace.Popups.Placement)
@@ -68,7 +69,7 @@ func (wm *WorkspaceManager) configureRelatedPopup(sourceNode *paneNode, webView
68 }69 }
69 // Use splitNode with existing pane instead of insertPopupPane70 // Use splitNode with existing pane instead of insertPopupPane
70 if _, err := wm.splitNode(sourceNode, direction, newPane); err != nil {71 if _, err := wm.splitNode(sourceNode, direction, newPane); err != nil {
71- log.Printf("[workspace] failed to insert related popup pane: %v", err)72+ logging.Error(fmt.Sprintf("[workspace] failed to insert related popup pane: %v", err))
72 return73 return
73 }74 }
74 node := wm.viewToNode[related]75 node := wm.viewToNode[related]
@@ -99,7 +100,7 @@ func (wm *WorkspaceManager) configureRelatedPopup(sourceNode *paneNode, webView
99 sourceNode.activePopupChildren = make([]string, 0)100 sourceNode.activePopupChildren = make([]string, 0)
100 }101 }
101 sourceNode.activePopupChildren = append(sourceNode.activePopupChildren, popupWebViewID)102 sourceNode.activePopupChildren = append(sourceNode.activePopupChildren, popupWebViewID)
102- log.Printf("[workspace] Added popup %s to parent's activePopupChildren (count: %d)", popupWebViewID, len(sourceNode.activePopupChildren))103+ logging.Debug(fmt.Sprintf("[workspace] Added popup %s to parent's activePopupChildren (count: %d)", popupWebViewID, len(sourceNode.activePopupChildren)))
103 104
104 // Register navigation policy handler for parent to block script-initiated navigations105 // Register navigation policy handler for parent to block script-initiated navigations
105 // when it has active popup children (prevents OAuth from hijacking parent pane)106 // when it has active popup children (prevents OAuth from hijacking parent pane)
@@ -112,27 +113,27 @@ func (wm *WorkspaceManager) configureRelatedPopup(sourceNode *paneNode, webView
112 113
113 // Block script-initiated navigations if this pane has active popup children114 // Block script-initiated navigations if this pane has active popup children
114 if len(sourceNode.activePopupChildren) > 0 {115 if len(sourceNode.activePopupChildren) > 0 {
115- log.Printf("[workspace] BLOCKED script-initiated navigation in parent pane (has %d active popups): %s", len(sourceNode.activePopupChildren), url)116+ logging.Info(fmt.Sprintf("[workspace] BLOCKED script-initiated navigation in parent pane (has %d active popups): %s", len(sourceNode.activePopupChildren), url))
116 return false117 return false
117 }118 }
118 119
119 // Allow if no active popups120 // Allow if no active popups
120 return true121 return true
121 })122 })
122- log.Printf("[workspace] Registered navigation policy handler for parent pane")123+ logging.Debug(fmt.Sprintf("[workspace] Registered navigation policy handler for parent pane"))
123 }124 }
124 }125 }
125 126
126 // Pipe into existing auto-close flow only for popups (confirmed by detection)127 // Pipe into existing auto-close flow only for popups (confirmed by detection)
127 related.RegisterCloseHandler(func() {128 related.RegisterCloseHandler(func() {
128- log.Printf("[workspace] Popup requesting close via window.close()")129+ logging.Info(fmt.Sprintf("[workspace] Popup requesting close via window.close()"))
129 130
130 // Remove this popup from parent's activePopupChildren131 // Remove this popup from parent's activePopupChildren
131 if sourceNode != nil && popupWebViewID != "" {132 if sourceNode != nil && popupWebViewID != "" {
132 for i, childID := range sourceNode.activePopupChildren {133 for i, childID := range sourceNode.activePopupChildren {
133 if childID == popupWebViewID {134 if childID == popupWebViewID {
134 sourceNode.activePopupChildren = append(sourceNode.activePopupChildren[:i], sourceNode.activePopupChildren[i+1:]...)135 sourceNode.activePopupChildren = append(sourceNode.activePopupChildren[:i], sourceNode.activePopupChildren[i+1:]...)
135- log.Printf("[workspace] Removed popup %s from parent's activePopupChildren (remaining: %d)", popupWebViewID, len(sourceNode.activePopupChildren))136+ logging.Debug(fmt.Sprintf("[workspace] Removed popup %s from parent's activePopupChildren (remaining: %d)", popupWebViewID, len(sourceNode.activePopupChildren)))
136 break137 break
137 }138 }
138 }139 }
@@ -145,7 +146,7 @@ func (wm *WorkspaceManager) configureRelatedPopup(sourceNode *paneNode, webView
145 return false146 return false
146 }147 }
147 if err := wm.ClosePane(n); err != nil {148 if err := wm.ClosePane(n); err != nil {
148- log.Printf("[workspace] Failed to close popup pane: %v", err)149+ logging.Error(fmt.Sprintf("[workspace] Failed to close popup pane: %v", err))
149 }150 }
150 return false151 return false
151 }, n)152 }, n)
@@ -179,32 +180,32 @@ func (wm *WorkspaceManager) applyPopupSizeConstraints(view *webkit.WebView, widt
179 if container := view.RootWidget(); container != nil {180 if container := view.RootWidget(); container != nil {
180 webkit.WidgetSetSizeRequest(container, minWidth, minHeight)181 webkit.WidgetSetSizeRequest(container, minWidth, minHeight)
181 webkit.WidgetQueueResize(container)182 webkit.WidgetQueueResize(container)
182- log.Printf("[workspace] Applied minimum size %dx%d to popup container=%p", minWidth, minHeight, container)183+ logging.Debug(fmt.Sprintf("[workspace] Applied minimum size %dx%d to popup container=%p", minWidth, minHeight, container))
183 }184 }
184 185
185 // Apply to WebView widget186 // Apply to WebView widget
186 if webViewWidget := view.Widget(); webViewWidget != nil {187 if webViewWidget := view.Widget(); webViewWidget != nil {
187 webkit.WidgetSetSizeRequest(webViewWidget, minWidth, minHeight)188 webkit.WidgetSetSizeRequest(webViewWidget, minWidth, minHeight)
188 webkit.WidgetQueueResize(webViewWidget)189 webkit.WidgetQueueResize(webViewWidget)
189- log.Printf("[workspace] Applied minimum size %dx%d to popup webview=%p", minWidth, minHeight, webViewWidget)190+ logging.Debug(fmt.Sprintf("[workspace] Applied minimum size %dx%d to popup webview=%p", minWidth, minHeight, webViewWidget))
190 }191 }
191 }192 }
192 193
193 // shouldAutoClose checks simple OAuth-like URL patterns and config flag194 // shouldAutoClose checks simple OAuth-like URL patterns and config flag
194 func (wm *WorkspaceManager) shouldAutoClose(url string) bool {195 func (wm *WorkspaceManager) shouldAutoClose(url string) bool {
195- log.Printf("[workspace] shouldAutoClose called for URL: %s", url)196+ logging.Info(fmt.Sprintf("[workspace] shouldAutoClose called for URL: %s", url))
196 197
197 if wm == nil || wm.app == nil || wm.app.config == nil {198 if wm == nil || wm.app == nil || wm.app.config == nil {
198- log.Printf("[workspace] shouldAutoClose: missing config, returning true")199+ logging.Error(fmt.Sprintf("[workspace] shouldAutoClose: missing config, returning true"))
199 return true200 return true
200 }201 }
201 if !wm.app.config.Workspace.Popups.OAuthAutoClose {202 if !wm.app.config.Workspace.Popups.OAuthAutoClose {
202- log.Printf("[workspace] shouldAutoClose: OAuthAutoClose disabled in config, returning false")203+ logging.Info(fmt.Sprintf("[workspace] shouldAutoClose: OAuthAutoClose disabled in config, returning false"))
203 return false204 return false
204 }205 }
205 206
206 u := strings.ToLower(url)207 u := strings.ToLower(url)
207- log.Printf("[workspace] shouldAutoClose: checking lowercase URL: %s", u)208+ logging.Debug(fmt.Sprintf("[workspace] shouldAutoClose: checking lowercase URL: %s", u))
208 209
209 // RFC 6749 compliant OAuth 2.0 URL patterns210 // RFC 6749 compliant OAuth 2.0 URL patterns
210 oauthPatterns := []string{211 oauthPatterns := []string{
@@ -225,12 +226,12 @@ func (wm *WorkspaceManager) shouldAutoClose(url string) bool {
225 // Check for OAuth patterns in URL226 // Check for OAuth patterns in URL
226 for _, pattern := range oauthPatterns {227 for _, pattern := range oauthPatterns {
227 if strings.Contains(u, pattern) {228 if strings.Contains(u, pattern) {
228- log.Printf("[workspace] shouldAutoClose: MATCHED pattern '%s' in URL, returning true", pattern)229+ logging.Info(fmt.Sprintf("[workspace] shouldAutoClose: MATCHED pattern '%s' in URL, returning true", pattern))
229 return true230 return true
230 }231 }
231 }232 }
232 233
233- log.Printf("[workspace] shouldAutoClose: no OAuth patterns matched, returning false")234+ logging.Info(fmt.Sprintf("[workspace] shouldAutoClose: no OAuth patterns matched, returning false"))
234 return false235 return false
235 }236 }
236 237
@@ -247,14 +248,14 @@ func (wm *WorkspaceManager) handleDetectedWindowType(sourceNode *paneNode, webVi
247 return // Already placed248 return // Already placed
248 }249 }
249 250
250- log.Printf("[workspace] Smart detection result: type=%d url=%s", windowType, url)251+ logging.Info(fmt.Sprintf("[workspace] Smart detection result: type=%d url=%s", windowType, url))
251 252
252 switch windowType {253 switch windowType {
253 case webkit.WindowTypeTab:254 case webkit.WindowTypeTab:
254 // For tabs, create a NEW independent WebView (can't use the related one)255 // For tabs, create a NEW independent WebView (can't use the related one)
255 webkitCfg, err := wm.app.buildWebkitConfig()256 webkitCfg, err := wm.app.buildWebkitConfig()
256 if err != nil {257 if err != nil {
257- log.Printf("[workspace] failed to build webkit config for tab: %v", err)258+ logging.Error(fmt.Sprintf("[workspace] failed to build webkit config for tab: %v", err))
258 return259 return
259 }260 }
260 // Tabs are embedded in workspace paned containers - no separate window needed261 // Tabs are embedded in workspace paned containers - no separate window needed
@@ -263,7 +264,7 @@ func (wm *WorkspaceManager) handleDetectedWindowType(sourceNode *paneNode, webVi
263 // Create independent WebView matching our standard tab insertion path264 // Create independent WebView matching our standard tab insertion path
264 independentView, err := webkit.NewWebView(webkitCfg)265 independentView, err := webkit.NewWebView(webkitCfg)
265 if err != nil {266 if err != nil {
266- log.Printf("[workspace] failed to create independent tab WebView: %v", err)267+ logging.Error(fmt.Sprintf("[workspace] failed to create independent tab WebView: %v", err))
267 return268 return
268 }269 }
269 270
@@ -273,7 +274,7 @@ func (wm *WorkspaceManager) handleDetectedWindowType(sourceNode *paneNode, webVi
273 // The related webView was just for detection - we don't use it for tabs274 // The related webView was just for detection - we don't use it for tabs
274 // Insert the new independent view as a tab275 // Insert the new independent view as a tab
275 if err := wm.insertIndependentPane(sourceNode, independentView, url); err != nil {276 if err := wm.insertIndependentPane(sourceNode, independentView, url); err != nil {
276- log.Printf("[workspace] Failed to insert independent pane: %v", err)277+ logging.Error(fmt.Sprintf("[workspace] Failed to insert independent pane: %v", err))
277 }278 }
278 279
279 case webkit.WindowTypePopup:280 case webkit.WindowTypePopup:
@@ -296,16 +297,16 @@ func (wm *WorkspaceManager) setupPopupHandling(webView *webkit.WebView, parentNo
296 // Get URL from navigation action297 // Get URL from navigation action
297 req := navAction.Request()298 req := navAction.Request()
298 if req == nil {299 if req == nil {
299- log.Printf("[workspace] create signal: no request in navigation action")300+ logging.Info(fmt.Sprintf("[workspace] create signal: no request in navigation action"))
300 return nil301 return nil
301 }302 }
302 url := req.URI()303 url := req.URI()
303 304
304- log.Printf("[workspace] create signal received for URL: %s", url)305+ logging.Info(fmt.Sprintf("[workspace] create signal received for URL: %s", url))
305 306
306 // Check if popups are enabled in config307 // Check if popups are enabled in config
307 if wm.app.config != nil && !wm.app.config.Workspace.Popups.OpenInNewPane {308 if wm.app.config != nil && !wm.app.config.Workspace.Popups.OpenInNewPane {
308- log.Printf("[workspace] Popup creation disabled in config - blocking popup")309+ logging.Info(fmt.Sprintf("[workspace] Popup creation disabled in config - blocking popup"))
309 return nil310 return nil
310 }311 }
311 312
@@ -313,59 +314,59 @@ func (wm *WorkspaceManager) setupPopupHandling(webView *webkit.WebView, parentNo
313 // WebKit requires the related-view property to be set during construction for proper lifecycle314 // WebKit requires the related-view property to be set during construction for proper lifecycle
314 // This ensures session/cookie sharing and proper WindowFeatures initialization315 // This ensures session/cookie sharing and proper WindowFeatures initialization
315 // All initialization (wrapping, container, scripts) happens in ready-to-show316 // All initialization (wrapping, container, scripts) happens in ready-to-show
316- log.Printf("[workspace] Step 1: Creating bare gotk4 WebView with related-view property")317+ logging.Debug(fmt.Sprintf("[workspace] Step 1: Creating bare gotk4 WebView with related-view property"))
317 barePopupView := webkit.NewBareRelatedWebView(webView.GtkWebView())318 barePopupView := webkit.NewBareRelatedWebView(webView.GtkWebView())
318 if barePopupView == nil {319 if barePopupView == nil {
319- log.Printf("[workspace] Failed to create bare related popup WebView - blocking popup")320+ logging.Error(fmt.Sprintf("[workspace] Failed to create bare related popup WebView - blocking popup"))
320 return nil321 return nil
321 }322 }
322- log.Printf("[workspace] Step 1 complete: bare related gotk4 WebView created=%p (parent=%p)", barePopupView, webView.GtkWebView())323+ logging.Debug(fmt.Sprintf("[workspace] Step 1 complete: bare related gotk4 WebView created=%p (parent=%p)", barePopupView, webView.GtkWebView()))
323 324
324 // Wrap the bare view immediately to get a proper ID325 // Wrap the bare view immediately to get a proper ID
325 // BUT don't initialize it yet - that happens in ready-to-show326 // BUT don't initialize it yet - that happens in ready-to-show
326- log.Printf("[workspace] Step 2: Wrapping bare view (no initialization)")327+ logging.Info(fmt.Sprintf("[workspace] Step 2: Wrapping bare view (no initialization)"))
327 wrappedView := webkit.WrapBareWebView(barePopupView)328 wrappedView := webkit.WrapBareWebView(barePopupView)
328 if wrappedView == nil {329 if wrappedView == nil {
329- log.Printf("[workspace] Failed to wrap bare popup WebView - blocking popup")330+ logging.Error(fmt.Sprintf("[workspace] Failed to wrap bare popup WebView - blocking popup"))
330 return nil331 return nil
331 }332 }
332 333
333 // Use the wrapper's ID for tracking334 // Use the wrapper's ID for tracking
334 popupID := wrappedView.ID()335 popupID := wrappedView.ID()
335- log.Printf("[workspace] Step 2 complete: wrapped view created with ID=%d", popupID)336+ logging.Debug(fmt.Sprintf("[workspace] Step 2 complete: wrapped view created with ID=%d", popupID))
336 337
337 // Store wrapped view (not initialized) for initialization in ready-to-show338 // Store wrapped view (not initialized) for initialization in ready-to-show
338- log.Printf("[workspace] Step 3: Storing in pendingPopups map")339+ logging.Info(fmt.Sprintf("[workspace] Step 3: Storing in pendingPopups map"))
339 wm.pendingPopups[popupID] = &pendingPopup{340 wm.pendingPopups[popupID] = &pendingPopup{
340 wrappedView: wrappedView,341 wrappedView: wrappedView,
341 parentView: webView,342 parentView: webView,
342 parentNode: parentNode,343 parentNode: parentNode,
343 url: url,344 url: url,
344 }345 }
345- log.Printf("[workspace] Step 3 complete: stored in pendingPopups")346+ logging.Info(fmt.Sprintf("[workspace] Step 3 complete: stored in pendingPopups"))
346 347
347 // Connect signals immediately BEFORE returning to WebKit348 // Connect signals immediately BEFORE returning to WebKit
348 // This is safe because we're only connecting signal handlers, not accessing properties349 // This is safe because we're only connecting signal handlers, not accessing properties
349 // WebKit will configure WindowFeatures AFTER this callback returns, which is fine350 // WebKit will configure WindowFeatures AFTER this callback returns, which is fine
350- log.Printf("[workspace] Step 4: Connecting signals immediately before returning to WebKit")351+ logging.Info(fmt.Sprintf("[workspace] Step 4: Connecting signals immediately before returning to WebKit"))
351 352
352 // Connect ready-to-show signal353 // Connect ready-to-show signal
353 barePopupView.ConnectReadyToShow(func() {354 barePopupView.ConnectReadyToShow(func() {
354- log.Printf("[workspace] ready-to-show signal received for popup ID=%d", popupID)355+ logging.Info(fmt.Sprintf("[workspace] ready-to-show signal received for popup ID=%d", popupID))
355 wm.handlePopupReadyToShow(popupID)356 wm.handlePopupReadyToShow(popupID)
356 })357 })
357- log.Printf("[workspace] Connected ready-to-show signal for popup ID=%d", popupID)358+ logging.Debug(fmt.Sprintf("[workspace] Connected ready-to-show signal for popup ID=%d", popupID))
358 359
359 // Connect close handler360 // Connect close handler
360 barePopupView.ConnectClose(func() {361 barePopupView.ConnectClose(func() {
361- log.Printf("[workspace] close signal received for popup ID=%d", popupID)362+ logging.Info(fmt.Sprintf("[workspace] close signal received for popup ID=%d", popupID))
362 wm.handlePopupClose(popupID)363 wm.handlePopupClose(popupID)
363 })364 })
364- log.Printf("[workspace] Connected close signal for popup ID=%d", popupID)365+ logging.Debug(fmt.Sprintf("[workspace] Connected close signal for popup ID=%d", popupID))
365 366
366- log.Printf("[workspace] Step 4 complete: signals connected")367+ logging.Debug(fmt.Sprintf("[workspace] Step 4 complete: signals connected"))
367 368
368- log.Printf("[workspace] Step 5: Returning wrapped view to WebKit (will extract bare view in ConnectCreate)")369+ logging.Debug(fmt.Sprintf("[workspace] Step 5: Returning wrapped view to WebKit (will extract bare view in ConnectCreate)"))
369 370
370 // Return the wrapped view - WebKit will extract the underlying gotk4 view and configure it371 // Return the wrapped view - WebKit will extract the underlying gotk4 view and configure it
371 return wrappedView372 return wrappedView
@@ -376,12 +377,12 @@ func (wm *WorkspaceManager) setupPopupHandling(webView *webkit.WebView, parentNo
376 func (wm *WorkspaceManager) handlePopupReadyToShow(popupID uint64) {377 func (wm *WorkspaceManager) handlePopupReadyToShow(popupID uint64) {
377 info, ok := wm.pendingPopups[popupID]378 info, ok := wm.pendingPopups[popupID]
378 if !ok {379 if !ok {
379- log.Printf("[workspace] ready-to-show for unknown popup ID: %d", popupID)380+ logging.Warn(fmt.Sprintf("[workspace] ready-to-show for unknown popup ID: %d", popupID))
380 return381 return
381 }382 }
382 delete(wm.pendingPopups, popupID)383 delete(wm.pendingPopups, popupID)
383 384
384- log.Printf("[workspace] ready-to-show received for popup ID=%d, starting full initialization", popupID)385+ logging.Info(fmt.Sprintf("[workspace] ready-to-show received for popup ID=%d, starting full initialization", popupID))
385 386
386 // NOW it's safe to initialize (WebKit has configured WindowProperties)387 // NOW it's safe to initialize (WebKit has configured WindowProperties)
387 // We already have the wrapped view from setupPopupHandling388 // We already have the wrapped view from setupPopupHandling
@@ -390,30 +391,30 @@ func (wm *WorkspaceManager) handlePopupReadyToShow(popupID uint64) {
390 // Build webkit config for initialization391 // Build webkit config for initialization
391 webkitCfg, err := wm.app.buildWebkitConfig()392 webkitCfg, err := wm.app.buildWebkitConfig()
392 if err != nil {393 if err != nil {
393- log.Printf("[workspace] Failed to build webkit config for popup initialization: %v", err)394+ logging.Error(fmt.Sprintf("[workspace] Failed to build webkit config for popup initialization: %v", err))
394 return395 return
395 }396 }
396 webkitCfg.CreateWindow = false // Embedded in workspace397 webkitCfg.CreateWindow = false // Embedded in workspace
397 398
398 // Initialize the wrapped view with full configuration399 // Initialize the wrapped view with full configuration
399 if err := wrappedView.InitializeFromBare(webkitCfg); err != nil {400 if err := wrappedView.InitializeFromBare(webkitCfg); err != nil {
400- log.Printf("[workspace] Failed to initialize popup WebView: %v", err)401+ logging.Error(fmt.Sprintf("[workspace] Failed to initialize popup WebView: %v", err))
401 return402 return
402 }403 }
403 404
404 // Register with content blocking service for network/cosmetic filtering405 // Register with content blocking service for network/cosmetic filtering
405 wm.app.RegisterWebViewForFiltering(wrappedView)406 wm.app.RegisterWebViewForFiltering(wrappedView)
406 407
407- log.Printf("[workspace] Successfully initialized popup WebView ID %d", wrappedView.ID())408+ logging.Debug(fmt.Sprintf("[workspace] Successfully initialized popup WebView ID %d", wrappedView.ID()))
408 409
409 // Create pane for the fully initialized WebView410 // Create pane for the fully initialized WebView
410 popupPane, err := wm.createPane(wrappedView)411 popupPane, err := wm.createPane(wrappedView)
411 if err != nil {412 if err != nil {
412- log.Printf("[workspace] Failed to create popup pane: %v", err)413+ logging.Error(fmt.Sprintf("[workspace] Failed to create popup pane: %v", err))
413 return414 return
414 }415 }
415 416
416- log.Printf("[workspace] Created popup pane, now inserting into workspace")417+ logging.Info(fmt.Sprintf("[workspace] Created popup pane, now inserting into workspace"))
417 418
418 // Determine window type from node (set during setupPopupHandling)419 // Determine window type from node (set during setupPopupHandling)
419 node := wm.viewToNode[wrappedView]420 node := wm.viewToNode[wrappedView]
@@ -428,10 +429,10 @@ func (wm *WorkspaceManager) handlePopupReadyToShow(popupID uint64) {
428 var behaviorStr string429 var behaviorStr string
429 if windowType == webkit.WindowTypeTab {430 if windowType == webkit.WindowTypeTab {
430 behaviorStr = wm.app.config.Workspace.Popups.BlankTargetBehavior431 behaviorStr = wm.app.config.Workspace.Popups.BlankTargetBehavior
431- log.Printf("[workspace] WindowTypeTab (_blank link) detected, using BlankTargetBehavior: %s", behaviorStr)432+ logging.Debug(fmt.Sprintf("[workspace] WindowTypeTab (_blank link) detected, using BlankTargetBehavior: %s", behaviorStr))
432 } else {433 } else {
433 behaviorStr = string(wm.app.config.Workspace.Popups.Behavior)434 behaviorStr = string(wm.app.config.Workspace.Popups.Behavior)
434- log.Printf("[workspace] WindowTypePopup detected, using Behavior: %s", behaviorStr)435+ logging.Debug(fmt.Sprintf("[workspace] WindowTypePopup detected, using Behavior: %s", behaviorStr))
435 }436 }
436 437
437 // Map string to PopupBehavior enum438 // Map string to PopupBehavior enum
@@ -447,7 +448,7 @@ func (wm *WorkspaceManager) handlePopupReadyToShow(popupID uint64) {
447 behavior = config.PopupBehaviorSplit // Fallback448 behavior = config.PopupBehaviorSplit // Fallback
448 }449 }
449 450
450- log.Printf("[workspace] Final behavior: %s", behavior)451+ logging.Info(fmt.Sprintf("[workspace] Final behavior: %s", behavior))
451 452
452 switch behavior {453 switch behavior {
453 case config.PopupBehaviorSplit:454 case config.PopupBehaviorSplit:
@@ -457,7 +458,7 @@ func (wm *WorkspaceManager) handlePopupReadyToShow(popupID uint64) {
457 direction = DirectionRight458 direction = DirectionRight
458 }459 }
459 if _, err := wm.splitNode(info.parentNode, direction, popupPane); err != nil {460 if _, err := wm.splitNode(info.parentNode, direction, popupPane); err != nil {
460- log.Printf("[workspace] Failed to insert split popup pane: %v", err)461+ logging.Error(fmt.Sprintf("[workspace] Failed to insert split popup pane: %v", err))
461 return462 return
462 }463 }
463 464
@@ -465,7 +466,7 @@ func (wm *WorkspaceManager) handlePopupReadyToShow(popupID uint64) {
465 // Stacked pane behavior466 // Stacked pane behavior
466 _, err := wm.stackedPaneManager.StackPane(info.parentNode)467 _, err := wm.stackedPaneManager.StackPane(info.parentNode)
467 if err != nil {468 if err != nil {
468- log.Printf("[workspace] Failed to stack existing pane: %v", err)469+ logging.Error(fmt.Sprintf("[workspace] Failed to stack existing pane: %v", err))
469 return470 return
470 }471 }
471 // Insert the popup pane in the same container - use splitNode with existing popup pane472 // Insert the popup pane in the same container - use splitNode with existing popup pane
@@ -474,44 +475,44 @@ func (wm *WorkspaceManager) handlePopupReadyToShow(popupID uint64) {
474 direction = DirectionRight475 direction = DirectionRight
475 }476 }
476 if _, err := wm.splitNode(info.parentNode, direction, popupPane); err != nil {477 if _, err := wm.splitNode(info.parentNode, direction, popupPane); err != nil {
477- log.Printf("[workspace] Failed to insert stacked popup pane: %v", err)478+ logging.Error(fmt.Sprintf("[workspace] Failed to insert stacked popup pane: %v", err))
478 return479 return
479 }480 }
480 481
481 case config.PopupBehaviorTabbed:482 case config.PopupBehaviorTabbed:
482 // Tabbed behavior - TODO: implement proper tabbed interface483 // Tabbed behavior - TODO: implement proper tabbed interface
483- log.Printf("[workspace] WARNING: Tabbed popup behavior not yet implemented, falling back to split")484+ logging.Warn(fmt.Sprintf("[workspace] WARNING: Tabbed popup behavior not yet implemented, falling back to split"))
484 direction := strings.ToLower(wm.app.config.Workspace.Popups.Placement)485 direction := strings.ToLower(wm.app.config.Workspace.Popups.Placement)
485 if direction == "" {486 if direction == "" {
486 direction = DirectionRight487 direction = DirectionRight
487 }488 }
488 if _, err := wm.splitNode(info.parentNode, direction, popupPane); err != nil {489 if _, err := wm.splitNode(info.parentNode, direction, popupPane); err != nil {
489- log.Printf("[workspace] Failed to insert tabbed popup pane: %v", err)490+ logging.Error(fmt.Sprintf("[workspace] Failed to insert tabbed popup pane: %v", err))
490 return491 return
491 }492 }
492 493
493 case config.PopupBehaviorWindowed:494 case config.PopupBehaviorWindowed:
494 // Windowed behavior - opens in new workspace/window495 // Windowed behavior - opens in new workspace/window
495 // TODO: implement new window creation with GTK496 // TODO: implement new window creation with GTK
496- log.Printf("[workspace] WARNING: Windowed popup behavior not yet implemented, falling back to split")497+ logging.Warn(fmt.Sprintf("[workspace] WARNING: Windowed popup behavior not yet implemented, falling back to split"))
497 direction := strings.ToLower(wm.app.config.Workspace.Popups.Placement)498 direction := strings.ToLower(wm.app.config.Workspace.Popups.Placement)
498 if direction == "" {499 if direction == "" {
499 direction = DirectionRight500 direction = DirectionRight
500 }501 }
501 if _, err := wm.splitNode(info.parentNode, direction, popupPane); err != nil {502 if _, err := wm.splitNode(info.parentNode, direction, popupPane); err != nil {
502- log.Printf("[workspace] Failed to insert windowed popup pane: %v", err)503+ logging.Error(fmt.Sprintf("[workspace] Failed to insert windowed popup pane: %v", err))
503 return504 return
504 }505 }
505 506
506 default:507 default:
507 // Fallback to split if unknown behavior508 // Fallback to split if unknown behavior
508- log.Printf("[workspace] Unknown popup behavior '%s', falling back to split", behavior)509+ logging.Warn(fmt.Sprintf("[workspace] Unknown popup behavior '%s', falling back to split", behavior))
509 direction := strings.ToLower(wm.app.config.Workspace.Popups.Placement)510 direction := strings.ToLower(wm.app.config.Workspace.Popups.Placement)
510 if direction == "" {511 if direction == "" {
511 direction = DirectionRight512 direction = DirectionRight
512 }513 }
513 if _, err := wm.splitNode(info.parentNode, direction, popupPane); err != nil {514 if _, err := wm.splitNode(info.parentNode, direction, popupPane); err != nil {
514- log.Printf("[workspace] Failed to insert popup pane: %v", err)515+ logging.Error(fmt.Sprintf("[workspace] Failed to insert popup pane: %v", err))
515 return516 return
516 }517 }
517 }518 }
@@ -534,8 +535,8 @@ func (wm *WorkspaceManager) handlePopupReadyToShow(popupID uint64) {
534 info.parentNode.activePopupChildren = make([]string, 0)535 info.parentNode.activePopupChildren = make([]string, 0)
535 }536 }
536 info.parentNode.activePopupChildren = append(info.parentNode.activePopupChildren, popupWebViewID)537 info.parentNode.activePopupChildren = append(info.parentNode.activePopupChildren, popupWebViewID)
537- log.Printf("[workspace] Added popup %s to parent's activePopupChildren (count: %d)",538+ logging.Debug(fmt.Sprintf("[workspace] Added popup %s to parent's activePopupChildren (count: %d)",
538- popupWebViewID, len(info.parentNode.activePopupChildren))539+ popupWebViewID, len(info.parentNode.activePopupChildren)))
539 540
540 // Register navigation policy handler for parent541 // Register navigation policy handler for parent
541 if info.parentNode.pane != nil && info.parentNode.pane.WebView() != nil {542 if info.parentNode.pane != nil && info.parentNode.pane.WebView() != nil {
@@ -544,8 +545,8 @@ func (wm *WorkspaceManager) handlePopupReadyToShow(popupID uint64) {
544 return true545 return true
545 }546 }
546 if len(info.parentNode.activePopupChildren) > 0 {547 if len(info.parentNode.activePopupChildren) > 0 {
547- log.Printf("[workspace] BLOCKED script-initiated navigation in parent pane (has %d active popups): %s",548+ logging.Info(fmt.Sprintf("[workspace] BLOCKED script-initiated navigation in parent pane (has %d active popups): %s",
548- len(info.parentNode.activePopupChildren), url)549+ len(info.parentNode.activePopupChildren), url))
549 return false550 return false
550 }551 }
551 return true552 return true
@@ -564,17 +565,17 @@ func (wm *WorkspaceManager) handlePopupReadyToShow(popupID uint64) {
564 // Widgets are already shown by splitNode - no need to show again565 // Widgets are already shown by splitNode - no need to show again
565 // GTK4 handles visibility automatically during reparenting566 // GTK4 handles visibility automatically during reparenting
566 567
567- log.Printf("[workspace] Popup pane inserted and visible for URL: %s", info.url)568+ logging.Info(fmt.Sprintf("[workspace] Popup pane inserted and visible for URL: %s", info.url))
568 }569 }
569 570
570 // handlePopupClose responds to WebKit's close signal571 // handlePopupClose responds to WebKit's close signal
571 func (wm *WorkspaceManager) handlePopupClose(popupID uint64) {572 func (wm *WorkspaceManager) handlePopupClose(popupID uint64) {
572- log.Printf("[workspace] close signal received for popup native=%d", popupID)573+ logging.Info(fmt.Sprintf("[workspace] close signal received for popup native=%d", popupID))
573 574
574 // Check if it's in pending (not yet shown)575 // Check if it's in pending (not yet shown)
575 if info, ok := wm.pendingPopups[popupID]; ok {576 if info, ok := wm.pendingPopups[popupID]; ok {
576 delete(wm.pendingPopups, popupID)577 delete(wm.pendingPopups, popupID)
577- log.Printf("[workspace] Popup closed before ready-to-show, cleaning up")578+ logging.Info(fmt.Sprintf("[workspace] Popup closed before ready-to-show, cleaning up"))
578 // Just remove from pending, WebKit will handle cleanup579 // Just remove from pending, WebKit will handle cleanup
579 _ = info // avoid unused variable580 _ = info // avoid unused variable
580 return581 return
@@ -590,7 +591,7 @@ func (wm *WorkspaceManager) handlePopupClose(popupID uint64) {
590 }591 }
591 592
592 if targetNode == nil {593 if targetNode == nil {
593- log.Printf("[workspace] close signal for unknown popup ID=%d, already closed?", popupID)594+ logging.Warn(fmt.Sprintf("[workspace] close signal for unknown popup ID=%d, already closed?", popupID))
594 return595 return
595 }596 }
596 597
@@ -610,16 +611,16 @@ func (wm *WorkspaceManager) handlePopupClose(popupID uint64) {
610 targetNode.parentPane.activePopupChildren = append(611 targetNode.parentPane.activePopupChildren = append(
611 targetNode.parentPane.activePopupChildren[:i],612 targetNode.parentPane.activePopupChildren[:i],
612 targetNode.parentPane.activePopupChildren[i+1:]...)613 targetNode.parentPane.activePopupChildren[i+1:]...)
613- log.Printf("[workspace] Removed popup from parent's activePopupChildren (remaining: %d)",614+ logging.Debug(fmt.Sprintf("[workspace] Removed popup from parent's activePopupChildren (remaining: %d)",
614- len(targetNode.parentPane.activePopupChildren))615+ len(targetNode.parentPane.activePopupChildren)))
615 break616 break
616 }617 }
617 }618 }
618 }619 }
619 620
620 // Clean close via existing ClosePane (WebKit already stopped)621 // Clean close via existing ClosePane (WebKit already stopped)
621- log.Printf("[workspace] Closing popup pane via ClosePane")622+ logging.Info(fmt.Sprintf("[workspace] Closing popup pane via ClosePane"))
622 if err := wm.ClosePane(targetNode); err != nil {623 if err := wm.ClosePane(targetNode); err != nil {
623- log.Printf("[workspace] Error closing popup pane: %v", err)624+ logging.Error(fmt.Sprintf("[workspace] Error closing popup pane: %v", err))
624 }625 }
625 }626 }
M internal/app/browser/workspace_stack_lifecycle.go +6 -6
@@ -4,7 +4,7 @@ package browser
4 import (4 import (
5 "errors"5 "errors"
6 "fmt"6 "fmt"
7- "log"7+ "github.com/bnema/dumber/internal/logging"
8 )8 )
9 9
10 // StackLifecycleManager manages the lifecycle hooks around stacked pane operations.10 // StackLifecycleManager manages the lifecycle hooks around stacked pane operations.
@@ -34,8 +34,8 @@ func (slm *StackLifecycleManager) CloseStackedPaneWithLifecycle(node *paneNode)
34 return errors.New("node is not part of a stacked pane")34 return errors.New("node is not part of a stacked pane")
35 }35 }
36 36
37- log.Printf("[stack-lifecycle] Closing stacked pane: node=%p stack=%p stackSize=%d",37+ logging.Info(fmt.Sprintf("[stack-lifecycle] Closing stacked pane: node=%p stack=%p stackSize=%d",
38- node, stackNode, len(stackNode.stackedPanes))38+ node, stackNode, len(stackNode.stackedPanes)))
39 39
40 if err := slm.validateStackState(stackNode, "before close"); err != nil {40 if err := slm.validateStackState(stackNode, "before close"); err != nil {
41 return fmt.Errorf("invalid stack state before close: %w", err)41 return fmt.Errorf("invalid stack state before close: %w", err)
@@ -51,7 +51,7 @@ func (slm *StackLifecycleManager) CloseStackedPaneWithLifecycle(node *paneNode)
51 51
52 if slm.treeValidator != nil {52 if slm.treeValidator != nil {
53 if err := slm.treeValidator.ValidateTree(slm.wm.root, "after stack close"); err != nil {53 if err := slm.treeValidator.ValidateTree(slm.wm.root, "after stack close"); err != nil {
54- log.Printf("[stack-lifecycle] Tree validation failed after stack close: %v", err)54+ logging.Error(fmt.Sprintf("[stack-lifecycle] Tree validation failed after stack close: %v", err))
55 }55 }
56 }56 }
57 57
@@ -86,7 +86,7 @@ func (slm *StackLifecycleManager) validateStackState(stackNode *paneNode, operat
86 }86 }
87 }87 }
88 88
89- log.Printf("[stack-lifecycle] Stack state valid for operation '%s': size=%d activeIndex=%d",89+ logging.Info(fmt.Sprintf("[stack-lifecycle] Stack state valid for operation '%s': size=%d activeIndex=%d",
90- operation, len(stackNode.stackedPanes), stackNode.activeStackIndex)90+ operation, len(stackNode.stackedPanes), stackNode.activeStackIndex))
91 return nil91 return nil
92 }92 }
M internal/app/browser/workspace_utils.go +3 -4
@@ -3,8 +3,7 @@ package browser
3 3
4 import (4 import (
5 "fmt"5 "fmt"
6- "log"6+ "github.com/bnema/dumber/internal/logging"
7-
8 "github.com/bnema/dumber/pkg/webkit"7 "github.com/bnema/dumber/pkg/webkit"
9 "github.com/diamondburned/gotk4/pkg/glib/v2"8 "github.com/diamondburned/gotk4/pkg/glib/v2"
10 "github.com/diamondburned/gotk4/pkg/gtk/v4"9 "github.com/diamondburned/gotk4/pkg/gtk/v4"
@@ -182,7 +181,7 @@ func (wm *WorkspaceManager) applyActivePaneBorder(node *paneNode) {
182 }181 }
183 182
184 webkit.WidgetSetVisible(node.borderOverlay, true)183 webkit.WidgetSetVisible(node.borderOverlay, true)
185- log.Printf("[workspace] Showing border overlay for pane %p", node)184+ logging.Debug(fmt.Sprintf("[workspace] Showing border overlay for pane %p", node))
186 }185 }
187 186
188 // removeActivePaneBorder hides the border overlay for a pane.187 // removeActivePaneBorder hides the border overlay for a pane.
@@ -192,7 +191,7 @@ func (wm *WorkspaceManager) removeActivePaneBorder(node *paneNode) {
192 }191 }
193 192
194 webkit.WidgetSetVisible(node.borderOverlay, false)193 webkit.WidgetSetVisible(node.borderOverlay, false)
195- log.Printf("[workspace] Hiding border overlay for pane %p", node)194+ logging.Debug(fmt.Sprintf("[workspace] Hiding border overlay for pane %p", node))
196 }195 }
197 196
198 // Widget cleanup utilities197 // Widget cleanup utilities
M internal/app/control/clipboard.go +7 -6
@@ -1,8 +1,9 @@
1 package control1 package control
2 2
3 import (3 import (
4- "log"4+ "fmt"
5 5
6+ "github.com/bnema/dumber/internal/logging"
6 "github.com/bnema/dumber/pkg/clipboard"7 "github.com/bnema/dumber/pkg/clipboard"
7 "github.com/bnema/dumber/pkg/webkit"8 "github.com/bnema/dumber/pkg/webkit"
8 )9 )
@@ -21,19 +22,19 @@ func NewClipboardController(webView *webkit.WebView) *ClipboardController {
21 22
22 // CopyCurrentURL copies the current WebView URL to the clipboard23 // CopyCurrentURL copies the current WebView URL to the clipboard
23 func (c *ClipboardController) CopyCurrentURL() {24 func (c *ClipboardController) CopyCurrentURL() {
24- log.Printf("Shortcut: Copy URL")25+ logging.Debug(fmt.Sprintf("Shortcut: Copy URL"))
25 currentURL := c.webView.GetCurrentURL()26 currentURL := c.webView.GetCurrentURL()
26 if currentURL == "" {27 if currentURL == "" {
27- log.Printf("No URL to copy")28+ logging.Warn(fmt.Sprintf("No URL to copy"))
28 c.showToast("No URL to copy", "error")29 c.showToast("No URL to copy", "error")
29 return30 return
30 }31 }
31 32
32 if err := clipboard.CopyToClipboard(currentURL); err != nil {33 if err := clipboard.CopyToClipboard(currentURL); err != nil {
33- log.Printf("Failed to copy URL to clipboard: %v", err)34+ logging.Error(fmt.Sprintf("Failed to copy URL to clipboard: %v", err))
34 c.showToast("Failed to copy URL", "error")35 c.showToast("Failed to copy URL", "error")
35 } else {36 } else {
36- log.Printf("URL copied to clipboard: %s", currentURL)37+ logging.Debug(fmt.Sprintf("URL copied to clipboard: %s", currentURL))
37 c.showToast("URL copied to clipboard", "success")38 c.showToast("URL copied to clipboard", "success")
38 }39 }
39 }40 }
@@ -52,7 +53,7 @@ func (c *ClipboardController) showToast(message, toastType string) {
52 }53 }
53 54
54 if err := c.webView.DispatchCustomEvent("dumber:toast", detail); err != nil {55 if err := c.webView.DispatchCustomEvent("dumber:toast", detail); err != nil {
55- log.Printf("Failed to dispatch toast event: %v", err)56+ logging.Error(fmt.Sprintf("Failed to dispatch toast event: %v", err))
56 }57 }
57 }58 }
58 59
M internal/app/control/navigation.go +14 -13
@@ -2,9 +2,10 @@ package control
2 2
3 import (3 import (
4 "context"4 "context"
5- "log"5+ "fmt"
6 "os"6 "os"
7 7
8+ "github.com/bnema/dumber/internal/logging"
8 "github.com/bnema/dumber/internal/services"9 "github.com/bnema/dumber/internal/services"
9 "github.com/bnema/dumber/pkg/webkit"10 "github.com/bnema/dumber/pkg/webkit"
10 )11 )
@@ -42,7 +43,7 @@ func (n *NavigationController) NavigateToURL(input string) error {
42 43
43 // Record navigation in browser service44 // Record navigation in browser service
44 if _, navErr := n.browserService.Navigate(ctx, result.URL); navErr != nil {45 if _, navErr := n.browserService.Navigate(ctx, result.URL); navErr != nil {
45- log.Printf("Warning: failed to navigate to %s: %v", result.URL, navErr)46+ logging.Warn(fmt.Sprintf("Warning: failed to navigate to %s: %v", result.URL, navErr))
46 }47 }
47 48
48 var (49 var (
@@ -56,18 +57,18 @@ func (n *NavigationController) NavigateToURL(input string) error {
56 if n.webView != nil {57 if n.webView != nil {
57 if n.webView.UsesDomZoom() {58 if n.webView.UsesDomZoom() {
58 if err := n.webView.SeedDomZoom(zoomLevel); err != nil {59 if err := n.webView.SeedDomZoom(zoomLevel); err != nil {
59- log.Printf("Warning: failed to seed DOM zoom for %s: %v", result.URL, err)60+ logging.Warn(fmt.Sprintf("Warning: failed to seed DOM zoom for %s: %v", result.URL, err))
60 }61 }
61 } else {62 } else {
62 n.webView.RunOnMainThread(func() {63 n.webView.RunOnMainThread(func() {
63 if err := n.webView.SetZoom(zoomLevel); err != nil {64 if err := n.webView.SetZoom(zoomLevel); err != nil {
64- log.Printf("Warning: failed to prime native zoom for %s: %v", result.URL, err)65+ logging.Warn(fmt.Sprintf("Warning: failed to prime native zoom for %s: %v", result.URL, err))
65 }66 }
66 })67 })
67 }68 }
68 }69 }
69 } else {70 } else {
70- log.Printf("Warning: failed to lookup zoom for %s: %v", result.URL, err)71+ logging.Warn(fmt.Sprintf("Warning: failed to lookup zoom for %s: %v", result.URL, err))
71 }72 }
72 }73 }
73 74
@@ -91,16 +92,16 @@ func (n *NavigationController) NavigateToURL(input string) error {
91 // HandleBrowseCommand processes the browse command line argument92 // HandleBrowseCommand processes the browse command line argument
92 func (n *NavigationController) HandleBrowseCommand() {93 func (n *NavigationController) HandleBrowseCommand() {
93 if len(os.Args) >= 3 && os.Args[1] == "browse" {94 if len(os.Args) >= 3 && os.Args[1] == "browse" {
94- log.Printf("Browse command detected: %s", os.Args[2])95+ logging.Debug(fmt.Sprintf("Browse command detected: %s", os.Args[2]))
95 ctx := context.Background()96 ctx := context.Background()
96 result, err := n.parserService.ParseInput(ctx, os.Args[2])97 result, err := n.parserService.ParseInput(ctx, os.Args[2])
97 if err == nil {98 if err == nil {
98- log.Printf("Parsed input → URL: %s", result.URL)99+ logging.Debug(fmt.Sprintf("Parsed input → URL: %s", result.URL))
99 if _, navErr := n.browserService.Navigate(ctx, result.URL); navErr != nil {100 if _, navErr := n.browserService.Navigate(ctx, result.URL); navErr != nil {
100- log.Printf("Warning: failed to navigate to %s: %v", result.URL, navErr)101+ logging.Warn(fmt.Sprintf("Warning: failed to navigate to %s: %v", result.URL, navErr))
101 }102 }
102 if n.webView != nil {103 if n.webView != nil {
103- log.Printf("Loading URL in WebView: %s", result.URL)104+ logging.Debug(fmt.Sprintf("Loading URL in WebView: %s", result.URL))
104 var (105 var (
105 zoomLevel float64106 zoomLevel float64
106 haveZoom bool107 haveZoom bool
@@ -111,21 +112,21 @@ func (n *NavigationController) HandleBrowseCommand() {
111 haveZoom = true112 haveZoom = true
112 if n.webView.UsesDomZoom() {113 if n.webView.UsesDomZoom() {
113 if err := n.webView.SeedDomZoom(zoomLevel); err != nil {114 if err := n.webView.SeedDomZoom(zoomLevel); err != nil {
114- log.Printf("Warning: failed to seed DOM zoom for %s: %v", result.URL, err)115+ logging.Warn(fmt.Sprintf("Warning: failed to seed DOM zoom for %s: %v", result.URL, err))
115 }116 }
116 } else {117 } else {
117 n.webView.RunOnMainThread(func() {118 n.webView.RunOnMainThread(func() {
118 if err := n.webView.SetZoom(zoomLevel); err != nil {119 if err := n.webView.SetZoom(zoomLevel); err != nil {
119- log.Printf("Warning: failed to prime native zoom for %s: %v", result.URL, err)120+ logging.Warn(fmt.Sprintf("Warning: failed to prime native zoom for %s: %v", result.URL, err))
120 }121 }
121 })122 })
122 }123 }
123 } else {124 } else {
124- log.Printf("Warning: failed to lookup zoom for %s: %v", result.URL, err)125+ logging.Warn(fmt.Sprintf("Warning: failed to lookup zoom for %s: %v", result.URL, err))
125 }126 }
126 }127 }
127 if err := n.webView.LoadURL(result.URL); err != nil {128 if err := n.webView.LoadURL(result.URL); err != nil {
128- log.Printf("Warning: failed to load URL: %v", err)129+ logging.Warn(fmt.Sprintf("Warning: failed to load URL: %v", err))
129 }130 }
130 if n.zoomController != nil {131 if n.zoomController != nil {
131 if haveZoom {132 if haveZoom {
M internal/app/control/zoom.go +9 -8
@@ -2,9 +2,10 @@ package control
2 2
3 import (3 import (
4 "context"4 "context"
5- "log"5+ "fmt"
6 "time"6 "time"
7 7
8+ "github.com/bnema/dumber/internal/logging"
8 "github.com/bnema/dumber/internal/services"9 "github.com/bnema/dumber/internal/services"
9 "github.com/bnema/dumber/pkg/webkit"10 "github.com/bnema/dumber/pkg/webkit"
10 )11 )
@@ -74,11 +75,11 @@ func (z *ZoomController) handleZoomChange(level float64) {
74 go func(url string, level float64) {75 go func(url string, level float64) {
75 ctx := context.Background()76 ctx := context.Background()
76 if err := z.browserService.SetZoomLevel(ctx, url, level); err != nil {77 if err := z.browserService.SetZoomLevel(ctx, url, level); err != nil {
77- log.Printf("[zoom] failed to save level %.2f for %s: %v", level, url, err)78+ logging.Error(fmt.Sprintf("[zoom] failed to save level %.2f for %s: %v", level, url, err))
78 return79 return
79 }80 }
80 key := services.ZoomKeyForLog(url)81 key := services.ZoomKeyForLog(url)
81- log.Printf("[zoom] saved %.2f for %s", level, key)82+ logging.Debug(fmt.Sprintf("[zoom] saved %.2f for %s", level, key))
82 }(url, level)83 }(url, level)
83 84
84 // Only show toast for user-initiated zoom changes, not during init or programmatic changes85 // Only show toast for user-initiated zoom changes, not during init or programmatic changes
@@ -89,15 +90,15 @@ func (z *ZoomController) handleZoomChange(level float64) {
89 90
90 // showZoomToast displays a zoom level notification using TypeScript toast system91 // showZoomToast displays a zoom level notification using TypeScript toast system
91 func (z *ZoomController) showZoomToast(level float64) {92 func (z *ZoomController) showZoomToast(level float64) {
92- log.Printf("[zoom] Attempting to show zoom toast for level %.2f", level)93+ logging.Debug(fmt.Sprintf("[zoom] Attempting to show zoom toast for level %.2f", level))
93 94
94 if z.webView == nil {95 if z.webView == nil {
95- log.Printf("[zoom] webview unavailable for zoom toast")96+ logging.Debug(fmt.Sprintf("[zoom] webview unavailable for zoom toast"))
96 return97 return
97 }98 }
98 99
99 if err := z.webView.DispatchCustomEvent("dumber:toast:zoom", map[string]any{"level": level}); err != nil {100 if err := z.webView.DispatchCustomEvent("dumber:toast:zoom", map[string]any{"level": level}); err != nil {
100- log.Printf("[zoom] failed to dispatch zoom toast: %v", err)101+ logging.Error(fmt.Sprintf("[zoom] failed to dispatch zoom toast: %v", err))
101 }102 }
102 }103 }
103 104
@@ -172,11 +173,11 @@ func (z *ZoomController) applyZoomLevel(url, domain string, zoomLevel float64, a
172 })173 })
173 174
174 if err := z.webView.SetZoom(zoomLevel); err != nil {175 if err := z.webView.SetZoom(zoomLevel); err != nil {
175- log.Printf("Warning: failed to set zoom: %v", err)176+ logging.Warn(fmt.Sprintf("Warning: failed to set zoom: %v", err))
176 return177 return
177 }178 }
178 179
179- log.Printf("[zoom] loaded %.2f for %s", zoomLevel, domain)180+ logging.Debug(fmt.Sprintf("[zoom] loaded %.2f for %s", zoomLevel, domain))
180 if allowToast && domain != z.lastZoomDomain && z.lastZoomDomain != "" {181 if allowToast && domain != z.lastZoomDomain && z.lastZoomDomain != "" {
181 z.showZoomToast(zoomLevel)182 z.showZoomToast(zoomLevel)
182 }183 }
M internal/app/environment/codec.go +17 -16
@@ -1,18 +1,19 @@
1 package environment1 package environment
2 2
3 import (3 import (
4- "log"4+ "fmt"
5 "os"5 "os"
6 "strconv"6 "strconv"
7 "strings"7 "strings"
8 8
9 "github.com/bnema/dumber/internal/config"9 "github.com/bnema/dumber/internal/config"
10+ "github.com/bnema/dumber/internal/logging"
10 )11 )
11 12
12 // ApplyCodecConfiguration applies codec preferences to environment variables and GStreamer settings13 // ApplyCodecConfiguration applies codec preferences to environment variables and GStreamer settings
13 func ApplyCodecConfiguration(codecPrefs config.CodecConfig) {14 func ApplyCodecConfiguration(codecPrefs config.CodecConfig) {
14- log.Printf("[codec] Applying codec preferences: preferred=%s, force_av1=%t, block_vp9=%t",15+ logging.Info(fmt.Sprintf("[codec] Applying codec preferences: preferred=%s, force_av1=%t, block_vp9=%t",
15- codecPrefs.PreferredCodecs, codecPrefs.ForceAV1, codecPrefs.BlockVP9)16+ codecPrefs.PreferredCodecs, codecPrefs.ForceAV1, codecPrefs.BlockVP9))
16 17
17 // Build comprehensive GStreamer plugin rankings18 // Build comprehensive GStreamer plugin rankings
18 var rankSettings []string19 var rankSettings []string
@@ -32,7 +33,7 @@ func ApplyCodecConfiguration(codecPrefs config.CodecConfig) {
32 "avdec_av1:256", // libav AV1 decoder33 "avdec_av1:256", // libav AV1 decoder
33 }34 }
34 rankSettings = append(rankSettings, av1Ranks...)35 rankSettings = append(rankSettings, av1Ranks...)
35- log.Printf("[codec] Promoted AV1 decoders for maximum priority")36+ logging.Info(fmt.Sprintf("[codec] Promoted AV1 decoders for maximum priority"))
36 }37 }
37 38
38 // H.264 decoder management39 // H.264 decoder management
@@ -58,7 +59,7 @@ func ApplyCodecConfiguration(codecPrefs config.CodecConfig) {
58 vp9Demotions = append(vp9Demotions, "vp9dec:64", "avdec_vp9:64")59 vp9Demotions = append(vp9Demotions, "vp9dec:64", "avdec_vp9:64")
59 }60 }
60 rankSettings = append(rankSettings, vp9Demotions...)61 rankSettings = append(rankSettings, vp9Demotions...)
61- log.Printf("[codec] Demoted/blocked VP9 decoders")62+ logging.Info(fmt.Sprintf("[codec] Demoted/blocked VP9 decoders"))
62 }63 }
63 64
64 // VP8 decoder blocking65 // VP8 decoder blocking
@@ -69,16 +70,16 @@ func ApplyCodecConfiguration(codecPrefs config.CodecConfig) {
69 "avdec_vp8:0",70 "avdec_vp8:0",
70 }71 }
71 rankSettings = append(rankSettings, vp8Demotions...)72 rankSettings = append(rankSettings, vp8Demotions...)
72- log.Printf("[codec] Blocked VP8 decoders")73+ logging.Info(fmt.Sprintf("[codec] Blocked VP8 decoders"))
73 }74 }
74 75
75 // Apply the complete ranking system76 // Apply the complete ranking system
76 if len(rankSettings) > 0 {77 if len(rankSettings) > 0 {
77 finalRank := strings.Join(rankSettings, ",")78 finalRank := strings.Join(rankSettings, ",")
78 if err := os.Setenv("GST_PLUGIN_FEATURE_RANK", finalRank); err != nil {79 if err := os.Setenv("GST_PLUGIN_FEATURE_RANK", finalRank); err != nil {
79- log.Printf("[codec] Warning: failed to set GST_PLUGIN_FEATURE_RANK: %v", err)80+ logging.Warn(fmt.Sprintf("[codec] Warning: failed to set GST_PLUGIN_FEATURE_RANK: %v", err))
80 } else {81 } else {
81- log.Printf("[codec] Set comprehensive GST_PLUGIN_FEATURE_RANK: %s", finalRank)82+ logging.Info(fmt.Sprintf("[codec] Set comprehensive GST_PLUGIN_FEATURE_RANK: %s", finalRank))
82 }83 }
83 }84 }
84 85
@@ -87,14 +88,14 @@ func ApplyCodecConfiguration(codecPrefs config.CodecConfig) {
87 bufferSize := strconv.Itoa(codecPrefs.VideoBufferSizeMB * 1024 * 1024)88 bufferSize := strconv.Itoa(codecPrefs.VideoBufferSizeMB * 1024 * 1024)
88 89
89 if err := os.Setenv("GST_BUFFER_SIZE", bufferSize); err != nil {90 if err := os.Setenv("GST_BUFFER_SIZE", bufferSize); err != nil {
90- log.Printf("[codec] Warning: failed to set GST_BUFFER_SIZE: %v", err)91+ logging.Warn(fmt.Sprintf("[codec] Warning: failed to set GST_BUFFER_SIZE: %v", err))
91 }92 }
92 93
93 if err := os.Setenv("GST_QUEUE2_MAX_SIZE_BYTES", bufferSize); err != nil {94 if err := os.Setenv("GST_QUEUE2_MAX_SIZE_BYTES", bufferSize); err != nil {
94- log.Printf("[codec] Warning: failed to set GST_QUEUE2_MAX_SIZE_BYTES: %v", err)95+ logging.Warn(fmt.Sprintf("[codec] Warning: failed to set GST_QUEUE2_MAX_SIZE_BYTES: %v", err))
95 }96 }
96 97
97- log.Printf("[codec] Set video buffer size to %dMB", codecPrefs.VideoBufferSizeMB)98+ logging.Info(fmt.Sprintf("[codec] Set video buffer size to %dMB", codecPrefs.VideoBufferSizeMB))
98 }99 }
99 100
100 // Set queue buffer time if specified101 // Set queue buffer time if specified
@@ -103,9 +104,9 @@ func ApplyCodecConfiguration(codecPrefs config.CodecConfig) {
103 bufferTime := strconv.Itoa(codecPrefs.QueueBufferTimeSec * 1000000000)104 bufferTime := strconv.Itoa(codecPrefs.QueueBufferTimeSec * 1000000000)
104 105
105 if err := os.Setenv("GST_QUEUE2_MAX_SIZE_TIME", bufferTime); err != nil {106 if err := os.Setenv("GST_QUEUE2_MAX_SIZE_TIME", bufferTime); err != nil {
106- log.Printf("[codec] Warning: failed to set GST_QUEUE2_MAX_SIZE_TIME: %v", err)107+ logging.Warn(fmt.Sprintf("[codec] Warning: failed to set GST_QUEUE2_MAX_SIZE_TIME: %v", err))
107 } else {108 } else {
108- log.Printf("[codec] Set queue buffer time to %ds", codecPrefs.QueueBufferTimeSec)109+ logging.Info(fmt.Sprintf("[codec] Set queue buffer time to %ds", codecPrefs.QueueBufferTimeSec))
109 }110 }
110 }111 }
111 112
@@ -113,16 +114,16 @@ func ApplyCodecConfiguration(codecPrefs config.CodecConfig) {
113 if codecPrefs.ForceAV1 {114 if codecPrefs.ForceAV1 {
114 // Enable AV1 hardware decoding if available115 // Enable AV1 hardware decoding if available
115 if err := os.Setenv("GST_AV1_DECODER_ENABLE_HW", "1"); err != nil {116 if err := os.Setenv("GST_AV1_DECODER_ENABLE_HW", "1"); err != nil {
116- log.Printf("[codec] Warning: failed to set GST_AV1_DECODER_ENABLE_HW: %v", err)117+ logging.Warn(fmt.Sprintf("[codec] Warning: failed to set GST_AV1_DECODER_ENABLE_HW: %v", err))
117 }118 }
118 }119 }
119 120
120 // Disable video post-processing to avoid VA-API issues121 // Disable video post-processing to avoid VA-API issues
121 if err := os.Setenv("GST_VAAPI_DISABLE_VPP", "1"); err != nil {122 if err := os.Setenv("GST_VAAPI_DISABLE_VPP", "1"); err != nil {
122- log.Printf("[codec] Warning: failed to set GST_VAAPI_DISABLE_VPP: %v", err)123+ logging.Warn(fmt.Sprintf("[codec] Warning: failed to set GST_VAAPI_DISABLE_VPP: %v", err))
123 }124 }
124 125
125- log.Printf("[codec] Codec configuration applied successfully")126+ logging.Info(fmt.Sprintf("[codec] Codec configuration applied successfully"))
126 }127 }
127 128
128 // BuildBlockedCodecsList converts config codec blocking preferences to a string slice129 // BuildBlockedCodecsList converts config codec blocking preferences to a string slice
M internal/app/environment/locale.go +8 -6
@@ -1,10 +1,12 @@
1 package environment1 package environment
2 2
3 import (3 import (
4- "log"4+ "fmt"
5 "os"5 "os"
6 "os/exec"6 "os/exec"
7 "strings"7 "strings"
8+
9+ "github.com/bnema/dumber/internal/logging"
8 )10 )
9 11
10 // DetectAndSetKeyboardLocale attempts to determine the current keyboard layout12 // DetectAndSetKeyboardLocale attempts to determine the current keyboard layout
@@ -38,7 +40,7 @@ func DetectAndSetKeyboardLocale() {
38 cand := strings.TrimSpace(parts[1])40 cand := strings.TrimSpace(parts[1])
39 if cand != "" {41 if cand != "" {
40 locale = cand42 locale = cand
41- log.Printf("[locale] keyboard layout from localectl: %s", locale)43+ logging.Debug(fmt.Sprintf("[locale] keyboard layout from localectl: %s", locale))
42 }44 }
43 }45 }
44 break46 break
@@ -57,7 +59,7 @@ func DetectAndSetKeyboardLocale() {
57 cand := strings.TrimSpace(parts[1])59 cand := strings.TrimSpace(parts[1])
58 if cand != "" {60 if cand != "" {
59 locale = cand61 locale = cand
60- log.Printf("[locale] keyboard layout from setxkbmap: %s", locale)62+ logging.Debug(fmt.Sprintf("[locale] keyboard layout from setxkbmap: %s", locale))
61 }63 }
62 }64 }
63 break65 break
@@ -87,13 +89,13 @@ func DetectAndSetKeyboardLocale() {
87 if i := strings.IndexByte(locale, '_'); i > 0 {89 if i := strings.IndexByte(locale, '_'); i > 0 {
88 locale = locale[:i]90 locale = locale[:i]
89 }91 }
90- log.Printf("[locale] keyboard layout from system locale: %s", locale)92+ logging.Debug(fmt.Sprintf("[locale] keyboard layout from system locale: %s", locale))
91 }93 }
92 94
93 if locale == "" {95 if locale == "" {
94 locale = "en"96 locale = "en"
95- log.Printf("[locale] keyboard layout defaulted to: %s", locale)97+ logging.Debug(fmt.Sprintf("[locale] keyboard layout defaulted to: %s", locale))
96 }98 }
97 99
98- log.Printf("[locale] keyboard locale detected: %s", locale)100+ logging.Info(fmt.Sprintf("[locale] keyboard locale detected: %s", locale))
99 }101 }
M internal/app/environment/media.go +6 -4
@@ -1,8 +1,10 @@
1 package environment1 package environment
2 2
3 import (3 import (
4- "log"4+ "fmt"
5 "os"5 "os"
6+
7+ "github.com/bnema/dumber/internal/logging"
6 )8 )
7 9
8 // SetupMediaPipeline proactively hardens media pipeline to avoid WebProcess crashes from buggy VAAPI/plugins.10 // SetupMediaPipeline proactively hardens media pipeline to avoid WebProcess crashes from buggy VAAPI/plugins.
@@ -12,15 +14,15 @@ func SetupMediaPipeline() {
12 // Demote VAAPI-related elements so they won't be auto-picked14 // Demote VAAPI-related elements so they won't be auto-picked
13 if os.Getenv("GST_PLUGIN_FEATURE_RANK") == "" {15 if os.Getenv("GST_PLUGIN_FEATURE_RANK") == "" {
14 if err := os.Setenv("GST_PLUGIN_FEATURE_RANK", "vaapisink:0,vaapidecodebin:0,vaapih264dec:0,vaapivideoconvert:0"); err != nil {16 if err := os.Setenv("GST_PLUGIN_FEATURE_RANK", "vaapisink:0,vaapidecodebin:0,vaapih264dec:0,vaapivideoconvert:0"); err != nil {
15- log.Printf("Warning: failed to set GST_PLUGIN_FEATURE_RANK: %v", err)17+ logging.Warn(fmt.Sprintf("Warning: failed to set GST_PLUGIN_FEATURE_RANK: %v", err))
16 }18 }
17 }19 }
18 // Optionally disable media stream features that can engage complex pipelines (webcam/mic)20 // Optionally disable media stream features that can engage complex pipelines (webcam/mic)
19 if os.Getenv("WEBKIT_DISABLE_MEDIA_STREAM") == "" {21 if os.Getenv("WEBKIT_DISABLE_MEDIA_STREAM") == "" {
20 if err := os.Setenv("WEBKIT_DISABLE_MEDIA_STREAM", "1"); err != nil {22 if err := os.Setenv("WEBKIT_DISABLE_MEDIA_STREAM", "1"); err != nil {
21- log.Printf("Warning: failed to set WEBKIT_DISABLE_MEDIA_STREAM: %v", err)23+ logging.Warn(fmt.Sprintf("Warning: failed to set WEBKIT_DISABLE_MEDIA_STREAM: %v", err))
22 }24 }
23 }25 }
24- log.Printf("[media] Safe mode enabled: VAAPI demoted, webcam/mic disabled (override with DUMBER_MEDIA_SAFE=0)")26+ logging.Info(fmt.Sprintf("[media] Safe mode enabled: VAAPI demoted, webcam/mic disabled (override with DUMBER_MEDIA_SAFE=0)"))
25 }27 }
26 }28 }
M internal/app/messaging/handler.go +55 -56
@@ -5,7 +5,6 @@ import (
5 "context"5 "context"
6 "encoding/json"6 "encoding/json"
7 "fmt"7 "fmt"
8- "log"
9 "strconv"8 "strconv"
10 "time"9 "time"
11 10
@@ -79,7 +78,7 @@ func (h *Handler) SetWorkspaceObserver(observer WorkspaceObserver) {
79 func (h *Handler) Handle(payload string) {78 func (h *Handler) Handle(payload string) {
80 msg, err := parseIncomingMessage(payload)79 msg, err := parseIncomingMessage(payload)
81 if err != nil {80 if err != nil {
82- log.Printf("[ERROR] Failed to unmarshal message: %v", err)81+ logging.Error(fmt.Sprintf("[ERROR] Failed to unmarshal message: %v", err))
83 return82 return
84 }83 }
85 84
@@ -202,7 +201,7 @@ func (h *Handler) SetWebView(webView *webkit.WebView) {
202 func (h *Handler) handleNavigation(msg Message) {201 func (h *Handler) handleNavigation(msg Message) {
203 if h.navigationController != nil {202 if h.navigationController != nil {
204 if err := h.navigationController.NavigateToURL(msg.URL); err != nil {203 if err := h.navigationController.NavigateToURL(msg.URL); err != nil {
205- log.Printf("[messaging] Navigation controller failed for input %q: %v", msg.URL, err)204+ logging.Error(fmt.Sprintf("[messaging] Navigation controller failed for input %q: %v", msg.URL, err))
206 h.legacyNavigate(msg)205 h.legacyNavigate(msg)
207 }206 }
208 return207 return
@@ -215,12 +214,12 @@ func (h *Handler) legacyNavigate(msg Message) {
215 ctx := context.Background()214 ctx := context.Background()
216 res, err := h.parserService.ParseInput(ctx, msg.URL)215 res, err := h.parserService.ParseInput(ctx, msg.URL)
217 if err != nil {216 if err != nil {
218- log.Printf("[messaging] Legacy navigation parse failed for %q: %v", msg.URL, err)217+ logging.Error(fmt.Sprintf("[messaging] Legacy navigation parse failed for %q: %v", msg.URL, err))
219 return218 return
220 }219 }
221 220
222 if _, navErr := h.browserService.Navigate(ctx, res.URL); navErr != nil {221 if _, navErr := h.browserService.Navigate(ctx, res.URL); navErr != nil {
223- log.Printf("Warning: failed to navigate to %s: %v", res.URL, navErr)222+ logging.Warn(fmt.Sprintf("Warning: failed to navigate to %s: %v", res.URL, navErr))
224 }223 }
225 224
226 if h.webView == nil {225 if h.webView == nil {
@@ -228,12 +227,12 @@ func (h *Handler) legacyNavigate(msg Message) {
228 }227 }
229 228
230 if err := h.webView.LoadURL(res.URL); err != nil {229 if err := h.webView.LoadURL(res.URL); err != nil {
231- log.Printf("[messaging] Legacy LoadURL failed for %s: %v", res.URL, err)230+ logging.Error(fmt.Sprintf("[messaging] Legacy LoadURL failed for %s: %v", res.URL, err))
232 }231 }
233 232
234 if z, zerr := h.browserService.GetZoomLevel(ctx, res.URL); zerr == nil {233 if z, zerr := h.browserService.GetZoomLevel(ctx, res.URL); zerr == nil {
235 if err := h.webView.SetZoom(z); err != nil {234 if err := h.webView.SetZoom(z); err != nil {
236- log.Printf("[messaging] Legacy SetZoom failed for %s: %v", res.URL, err)235+ logging.Error(fmt.Sprintf("[messaging] Legacy SetZoom failed for %s: %v", res.URL, err))
237 }236 }
238 }237 }
239 }238 }
@@ -242,26 +241,26 @@ func (h *Handler) legacyNavigate(msg Message) {
242 // handleQuery computes omnibox suggestions natively and returns them to the GUI without fetch241 // handleQuery computes omnibox suggestions natively and returns them to the GUI without fetch
243 func (h *Handler) handleWorkspace(msg Message) {242 func (h *Handler) handleWorkspace(msg Message) {
244 if h.workspaceObserver == nil {243 if h.workspaceObserver == nil {
245- log.Printf("[workspace] Received workspace event %q but no observer registered", msg.Event)244+ logging.Warn(fmt.Sprintf("[workspace] Received workspace event %q but no observer registered", msg.Event))
246 return245 return
247 }246 }
248 if h.webView == nil {247 if h.webView == nil {
249- log.Printf("[workspace] Ignoring workspace event %q: webview not attached", msg.Event)248+ logging.Debug(fmt.Sprintf("[workspace] Ignoring workspace event %q: webview not attached", msg.Event))
250 return249 return
251 }250 }
252- log.Printf("[workspace] Forwarding workspace event: event=%s direction=%s action=%s webviewId=%s", msg.Event, msg.Direction, msg.Action, msg.WebViewID)251+ logging.Debug(fmt.Sprintf("[workspace] Forwarding workspace event: event=%s direction=%s action=%s webviewId=%s", msg.Event, msg.Direction, msg.Action, msg.WebViewID))
253 h.workspaceObserver.OnWorkspaceMessage(h.webView, msg)252 h.workspaceObserver.OnWorkspaceMessage(h.webView, msg)
254 }253 }
255 254
256 func (h *Handler) handleClosePopup(msg Message) {255 func (h *Handler) handleClosePopup(msg Message) {
257- log.Printf("[messaging] Received close-popup request: webviewId=%s reason=%s", msg.WebViewID, msg.Reason)256+ logging.Debug(fmt.Sprintf("[messaging] Received close-popup request: webviewId=%s reason=%s", msg.WebViewID, msg.Reason))
258 257
259 if h.workspaceObserver == nil {258 if h.workspaceObserver == nil {
260- log.Printf("[messaging] No workspace observer registered for close-popup request")259+ logging.Warn(fmt.Sprintf("[messaging] No workspace observer registered for close-popup request"))
261 return260 return
262 }261 }
263 if h.webView == nil {262 if h.webView == nil {
264- log.Printf("[messaging] No webview attached for close-popup request")263+ logging.Debug(fmt.Sprintf("[messaging] No webview attached for close-popup request"))
265 return264 return
266 }265 }
267 266
@@ -273,7 +272,7 @@ func (h *Handler) handleClosePopup(msg Message) {
273 Reason: msg.Reason,272 Reason: msg.Reason,
274 }273 }
275 274
276- log.Printf("[messaging] Forwarding close-popup to workspace: webviewId=%s reason=%s", msg.WebViewID, msg.Reason)275+ logging.Debug(fmt.Sprintf("[messaging] Forwarding close-popup to workspace: webviewId=%s reason=%s", msg.WebViewID, msg.Reason))
277 h.workspaceObserver.OnWorkspaceMessage(h.webView, closeMsg)276 h.workspaceObserver.OnWorkspaceMessage(h.webView, closeMsg)
278 }277 }
279 278
@@ -342,7 +341,7 @@ func (h *Handler) handleQuery(msg Message) {
342 // Prefer unified page-world API; fallback to legacy global function341 // Prefer unified page-world API; fallback to legacy global function
343 script := "(window.__dumber?.omnibox?.suggestions ? window.__dumber.omnibox.suggestions(" + string(b) + ") : (window.__dumber_omnibox_suggestions && window.__dumber_omnibox_suggestions(" + string(b) + ")))"342 script := "(window.__dumber?.omnibox?.suggestions ? window.__dumber.omnibox.suggestions(" + string(b) + ") : (window.__dumber_omnibox_suggestions && window.__dumber_omnibox_suggestions(" + string(b) + ")))"
344 if err := h.webView.InjectScript(script); err != nil {343 if err := h.webView.InjectScript(script); err != nil {
345- log.Printf("[ERROR] Failed to inject omnibox suggestions: %v", err)344+ logging.Error(fmt.Sprintf("[ERROR] Failed to inject omnibox suggestions: %v", err))
346 }345 }
347 }346 }
348 }347 }
@@ -362,7 +361,7 @@ func (h *Handler) handleOmniboxInitialHistory(msg Message) {
362 // Get config to determine behavior361 // Get config to determine behavior
363 cfg, err := h.browserService.GetConfig(ctx)362 cfg, err := h.browserService.GetConfig(ctx)
364 if err != nil {363 if err != nil {
365- log.Printf("[ERROR] Failed to get config: %v", err)364+ logging.Error(fmt.Sprintf("[ERROR] Failed to get config: %v", err))
366 return365 return
367 }366 }
368 behavior := cfg.Omnibox.InitialBehavior367 behavior := cfg.Omnibox.InitialBehavior
@@ -396,7 +395,7 @@ func (h *Handler) handleOmniboxInitialHistory(msg Message) {
396 }395 }
397 396
398 if err != nil {397 if err != nil {
399- log.Printf("[ERROR] Failed to fetch initial history: %v", err)398+ logging.Error(fmt.Sprintf("[ERROR] Failed to fetch initial history: %v", err))
400 return399 return
401 }400 }
402 401
@@ -437,7 +436,7 @@ func (h *Handler) handleOmniboxInitialHistory(msg Message) {
437 if b, err := json.Marshal(results); err == nil {436 if b, err := json.Marshal(results); err == nil {
438 script := "(window.__dumber?.omnibox?.suggestions ? window.__dumber.omnibox.suggestions(" + string(b) + ") : (window.__dumber_omnibox_suggestions && window.__dumber_omnibox_suggestions(" + string(b) + ")))"437 script := "(window.__dumber?.omnibox?.suggestions ? window.__dumber.omnibox.suggestions(" + string(b) + ") : (window.__dumber_omnibox_suggestions && window.__dumber_omnibox_suggestions(" + string(b) + ")))"
439 if err := h.webView.InjectScript(script); err != nil {438 if err := h.webView.InjectScript(script); err != nil {
440- log.Printf("[ERROR] Failed to inject initial history: %v", err)439+ logging.Error(fmt.Sprintf("[ERROR] Failed to inject initial history: %v", err))
441 }440 }
442 }441 }
443 }442 }
@@ -495,7 +494,7 @@ func (h *Handler) handleWailsBridge(msg Message) {
495 // handleTheme processes theme-related messages494 // handleTheme processes theme-related messages
496 func (h *Handler) handleTheme(msg Message) {495 func (h *Handler) handleTheme(msg Message) {
497 if msg.Value != "" && msg.Value != h.lastTheme {496 if msg.Value != "" && msg.Value != h.lastTheme {
498- log.Printf("[theme] color-scheme changed: %s", msg.Value)497+ logging.Debug(fmt.Sprintf("[theme] color-scheme changed: %s", msg.Value))
499 h.lastTheme = msg.Value498 h.lastTheme = msg.Value
500 }499 }
501 }500 }
@@ -541,7 +540,7 @@ func (h *Handler) handleHistoryRecent(msg Message) {
541 }540 }
542 541
543 if err != nil {542 if err != nil {
544- log.Printf("[messaging] Failed to get recent history: %v", err)543+ logging.Error(fmt.Sprintf("[messaging] Failed to get recent history: %v", err))
545 requestIdParam := ""544 requestIdParam := ""
546 if msg.RequestID != "" {545 if msg.RequestID != "" {
547 requestIdParam = ", '" + msg.RequestID + "'"546 requestIdParam = ", '" + msg.RequestID + "'"
@@ -567,7 +566,7 @@ func (h *Handler) handleHistoryStats(_ Message) {
567 ctx := context.Background()566 ctx := context.Background()
568 stats, err := h.browserService.GetHistoryStats(ctx)567 stats, err := h.browserService.GetHistoryStats(ctx)
569 if err != nil {568 if err != nil {
570- log.Printf("[messaging] Failed to get history stats: %v", err)569+ logging.Error(fmt.Sprintf("[messaging] Failed to get history stats: %v", err))
571 _ = h.webView.InjectScript("window.__dumber_history_error && window.__dumber_history_error('Failed to load history stats')")570 _ = h.webView.InjectScript("window.__dumber_history_error && window.__dumber_history_error('Failed to load history stats')")
572 return571 return
573 }572 }
@@ -591,7 +590,7 @@ func (h *Handler) handleHistorySearch(msg Message) {
591 590
592 entries, err := h.browserService.SearchHistory(ctx, query, limit)591 entries, err := h.browserService.SearchHistory(ctx, query, limit)
593 if err != nil {592 if err != nil {
594- log.Printf("[messaging] Failed to search history: %v", err)593+ logging.Error(fmt.Sprintf("[messaging] Failed to search history: %v", err))
595 _ = h.webView.InjectScript("window.__dumber_history_error && window.__dumber_history_error('Failed to search history')")594 _ = h.webView.InjectScript("window.__dumber_history_error && window.__dumber_history_error('Failed to search history')")
596 return595 return
597 }596 }
@@ -607,7 +606,7 @@ func (h *Handler) handleHistoryDelete(msg Message) {
607 }606 }
608 607
609 if msg.HistoryID == "" {608 if msg.HistoryID == "" {
610- log.Printf("[messaging] History delete: missing historyId")609+ logging.Warn(fmt.Sprintf("[messaging] History delete: missing historyId"))
611 _ = h.webView.InjectScript("window.__dumber_history_error && window.__dumber_history_error('Missing history ID')")610 _ = h.webView.InjectScript("window.__dumber_history_error && window.__dumber_history_error('Missing history ID')")
612 return611 return
613 }612 }
@@ -615,7 +614,7 @@ func (h *Handler) handleHistoryDelete(msg Message) {
615 // Convert string ID to int64614 // Convert string ID to int64
616 id, err := strconv.ParseInt(msg.HistoryID, 10, 64)615 id, err := strconv.ParseInt(msg.HistoryID, 10, 64)
617 if err != nil {616 if err != nil {
618- log.Printf("[messaging] History delete: invalid ID format: %v", err)617+ logging.Error(fmt.Sprintf("[messaging] History delete: invalid ID format: %v", err))
619 _ = h.webView.InjectScript("window.__dumber_history_error && window.__dumber_history_error('Invalid history ID format')")618 _ = h.webView.InjectScript("window.__dumber_history_error && window.__dumber_history_error('Invalid history ID format')")
620 return619 return
621 }620 }
@@ -623,7 +622,7 @@ func (h *Handler) handleHistoryDelete(msg Message) {
623 ctx := context.Background()622 ctx := context.Background()
624 err = h.browserService.DeleteHistoryEntry(ctx, id)623 err = h.browserService.DeleteHistoryEntry(ctx, id)
625 if err != nil {624 if err != nil {
626- log.Printf("[messaging] Failed to delete history entry: %v", err)625+ logging.Error(fmt.Sprintf("[messaging] Failed to delete history entry: %v", err))
627 _ = h.webView.InjectScript("window.__dumber_history_error && window.__dumber_history_error('Failed to delete history entry')")626 _ = h.webView.InjectScript("window.__dumber_history_error && window.__dumber_history_error('Failed to delete history entry')")
628 return627 return
629 }628 }
@@ -650,7 +649,7 @@ func (h *Handler) handleConsoleMessage(msg Message) {
650 }649 }
651 650
652 if err := json.Unmarshal(msg.Payload, &consolePayload); err != nil {651 if err := json.Unmarshal(msg.Payload, &consolePayload); err != nil {
653- log.Printf("[messaging] Failed to unmarshal console-message: %v", err)652+ logging.Error(fmt.Sprintf("[messaging] Failed to unmarshal console-message: %v", err))
654 return653 return
655 }654 }
656 655
@@ -661,44 +660,44 @@ func (h *Handler) handleConsoleMessage(msg Message) {
661 // handleWebViewIDRequest responds to JavaScript requests for the webview ID660 // handleWebViewIDRequest responds to JavaScript requests for the webview ID
662 func (h *Handler) handleWebViewIDRequest(msg Message) {661 func (h *Handler) handleWebViewIDRequest(msg Message) {
663 if h.webView == nil {662 if h.webView == nil {
664- log.Printf("[messaging] Cannot provide webview ID - no webview available")663+ logging.Warn(fmt.Sprintf("[messaging] Cannot provide webview ID - no webview available"))
665 return664 return
666 }665 }
667 666
668 // Check if WebView is destroyed before attempting any operations667 // Check if WebView is destroyed before attempting any operations
669 if h.webView.IsDestroyed() {668 if h.webView.IsDestroyed() {
670- log.Printf("[messaging] Cannot provide webview ID - webview is destroyed")669+ logging.Warn(fmt.Sprintf("[messaging] Cannot provide webview ID - webview is destroyed"))
671 return670 return
672 }671 }
673 672
674 webViewID := h.webView.ID()673 webViewID := h.webView.ID()
675- log.Printf("[messaging] Sending webview ID %d to JavaScript", webViewID)674+ logging.Debug(fmt.Sprintf("[messaging] Sending webview ID %d to JavaScript", webViewID))
676 675
677 // Send the webview ID back to JavaScript via custom event676 // Send the webview ID back to JavaScript via custom event
678 if err := h.webView.DispatchCustomEvent("dumber:webview-id", map[string]any{677 if err := h.webView.DispatchCustomEvent("dumber:webview-id", map[string]any{
679 "webviewId": webViewID,678 "webviewId": webViewID,
680 "timestamp": time.Now().UnixMilli(),679 "timestamp": time.Now().UnixMilli(),
681 }); err != nil {680 }); err != nil {
682- log.Printf("[messaging] Failed to send webview ID: %v", err)681+ logging.Error(fmt.Sprintf("[messaging] Failed to send webview ID: %v", err))
683 }682 }
684 }683 }
685 684
686 // handleGetSearchShortcuts sends search shortcuts configuration to JavaScript685 // handleGetSearchShortcuts sends search shortcuts configuration to JavaScript
687 func (h *Handler) handleGetSearchShortcuts(msg Message) {686 func (h *Handler) handleGetSearchShortcuts(msg Message) {
688 if h.webView == nil {687 if h.webView == nil {
689- log.Printf("[messaging] Cannot provide search shortcuts - no webview available")688+ logging.Warn(fmt.Sprintf("[messaging] Cannot provide search shortcuts - no webview available"))
690 return689 return
691 }690 }
692 691
693 if h.webView.IsDestroyed() {692 if h.webView.IsDestroyed() {
694- log.Printf("[messaging] Cannot provide search shortcuts - webview is destroyed")693+ logging.Warn(fmt.Sprintf("[messaging] Cannot provide search shortcuts - webview is destroyed"))
695 return694 return
696 }695 }
697 696
698 // Get search shortcuts from browser service config697 // Get search shortcuts from browser service config
699 shortcuts, err := h.browserService.GetSearchShortcuts(context.Background())698 shortcuts, err := h.browserService.GetSearchShortcuts(context.Background())
700 if err != nil {699 if err != nil {
701- log.Printf("[messaging] Failed to get search shortcuts: %v", err)700+ logging.Error(fmt.Sprintf("[messaging] Failed to get search shortcuts: %v", err))
702 _ = h.webView.InjectScript("window.__dumber_search_shortcuts_error && window.__dumber_search_shortcuts_error('Failed to get search shortcuts')")701 _ = h.webView.InjectScript("window.__dumber_search_shortcuts_error && window.__dumber_search_shortcuts_error('Failed to get search shortcuts')")
703 return702 return
704 }703 }
@@ -706,25 +705,25 @@ func (h *Handler) handleGetSearchShortcuts(msg Message) {
706 // Marshal to JSON705 // Marshal to JSON
707 b, err := json.Marshal(shortcuts)706 b, err := json.Marshal(shortcuts)
708 if err != nil {707 if err != nil {
709- log.Printf("[messaging] Failed to marshal search shortcuts: %v", err)708+ logging.Error(fmt.Sprintf("[messaging] Failed to marshal search shortcuts: %v", err))
710 _ = h.webView.InjectScript("window.__dumber_search_shortcuts_error && window.__dumber_search_shortcuts_error('Failed to load search shortcuts')")709 _ = h.webView.InjectScript("window.__dumber_search_shortcuts_error && window.__dumber_search_shortcuts_error('Failed to load search shortcuts')")
711 return710 return
712 }711 }
713 712
714 // Inject the search shortcuts into the page713 // Inject the search shortcuts into the page
715- log.Printf("[messaging] Sending search shortcuts to JavaScript")714+ logging.Debug(fmt.Sprintf("[messaging] Sending search shortcuts to JavaScript"))
716 _ = h.webView.InjectScript("window.__dumber_search_shortcuts && window.__dumber_search_shortcuts(" + string(b) + ")")715 _ = h.webView.InjectScript("window.__dumber_search_shortcuts && window.__dumber_search_shortcuts(" + string(b) + ")")
717 }716 }
718 717
719 // handleGetColorPalettes sends color palettes configuration to JavaScript718 // handleGetColorPalettes sends color palettes configuration to JavaScript
720 func (h *Handler) handleGetColorPalettes(msg Message) {719 func (h *Handler) handleGetColorPalettes(msg Message) {
721 if h.webView == nil {720 if h.webView == nil {
722- log.Printf("[messaging] Cannot provide color palettes - no webview available")721+ logging.Warn(fmt.Sprintf("[messaging] Cannot provide color palettes - no webview available"))
723 return722 return
724 }723 }
725 724
726 if h.webView.IsDestroyed() {725 if h.webView.IsDestroyed() {
727- log.Printf("[messaging] Cannot provide color palettes - webview is destroyed")726+ logging.Warn(fmt.Sprintf("[messaging] Cannot provide color palettes - webview is destroyed"))
728 return727 return
729 }728 }
730 729
@@ -734,32 +733,32 @@ func (h *Handler) handleGetColorPalettes(msg Message) {
734 // Marshal to JSON733 // Marshal to JSON
735 b, err := json.Marshal(palettes)734 b, err := json.Marshal(palettes)
736 if err != nil {735 if err != nil {
737- log.Printf("[messaging] Failed to marshal color palettes: %v", err)736+ logging.Error(fmt.Sprintf("[messaging] Failed to marshal color palettes: %v", err))
738 _ = h.webView.InjectScript("window.__dumber_color_palettes_error && window.__dumber_color_palettes_error('Failed to load color palettes')")737 _ = h.webView.InjectScript("window.__dumber_color_palettes_error && window.__dumber_color_palettes_error('Failed to load color palettes')")
739 return738 return
740 }739 }
741 740
742 // Inject the color palettes into the page741 // Inject the color palettes into the page
743- log.Printf("[messaging] Sending color palettes to JavaScript")742+ logging.Debug(fmt.Sprintf("[messaging] Sending color palettes to JavaScript"))
744 _ = h.webView.InjectScript("window.__dumber_color_palettes && window.__dumber_color_palettes(" + string(b) + ")")743 _ = h.webView.InjectScript("window.__dumber_color_palettes && window.__dumber_color_palettes(" + string(b) + ")")
745 }744 }
746 745
747 // handleGetFavorites sends all favorites to JavaScript746 // handleGetFavorites sends all favorites to JavaScript
748 func (h *Handler) handleGetFavorites(msg Message) {747 func (h *Handler) handleGetFavorites(msg Message) {
749 if h.webView == nil {748 if h.webView == nil {
750- log.Printf("[messaging] Cannot provide favorites - no webview available")749+ logging.Warn(fmt.Sprintf("[messaging] Cannot provide favorites - no webview available"))
751 return750 return
752 }751 }
753 752
754 if h.webView.IsDestroyed() {753 if h.webView.IsDestroyed() {
755- log.Printf("[messaging] Cannot provide favorites - webview is destroyed")754+ logging.Warn(fmt.Sprintf("[messaging] Cannot provide favorites - webview is destroyed"))
756 return755 return
757 }756 }
758 757
759 // Get favorites from browser service758 // Get favorites from browser service
760 favorites, err := h.browserService.GetFavorites(context.Background())759 favorites, err := h.browserService.GetFavorites(context.Background())
761 if err != nil {760 if err != nil {
762- log.Printf("[messaging] Failed to get favorites: %v", err)761+ logging.Error(fmt.Sprintf("[messaging] Failed to get favorites: %v", err))
763 _ = h.webView.InjectScript("window.__dumber_favorites_error && window.__dumber_favorites_error('Failed to get favorites')")762 _ = h.webView.InjectScript("window.__dumber_favorites_error && window.__dumber_favorites_error('Failed to get favorites')")
764 return763 return
765 }764 }
@@ -767,37 +766,37 @@ func (h *Handler) handleGetFavorites(msg Message) {
767 // Marshal to JSON766 // Marshal to JSON
768 b, err := json.Marshal(favorites)767 b, err := json.Marshal(favorites)
769 if err != nil {768 if err != nil {
770- log.Printf("[messaging] Failed to marshal favorites: %v", err)769+ logging.Error(fmt.Sprintf("[messaging] Failed to marshal favorites: %v", err))
771 _ = h.webView.InjectScript("window.__dumber_favorites_error && window.__dumber_favorites_error('Failed to load favorites')")770 _ = h.webView.InjectScript("window.__dumber_favorites_error && window.__dumber_favorites_error('Failed to load favorites')")
772 return771 return
773 }772 }
774 773
775 // Inject the favorites into the page774 // Inject the favorites into the page
776- log.Printf("[messaging] Sending %d favorites to JavaScript", len(favorites))775+ logging.Debug(fmt.Sprintf("[messaging] Sending %d favorites to JavaScript", len(favorites)))
777 _ = h.webView.InjectScript("window.__dumber_favorites && window.__dumber_favorites(" + string(b) + ")")776 _ = h.webView.InjectScript("window.__dumber_favorites && window.__dumber_favorites(" + string(b) + ")")
778 }777 }
779 778
780 // handleToggleFavorite adds or removes a URL from favorites779 // handleToggleFavorite adds or removes a URL from favorites
781 func (h *Handler) handleToggleFavorite(msg Message) {780 func (h *Handler) handleToggleFavorite(msg Message) {
782 if h.webView == nil {781 if h.webView == nil {
783- log.Printf("[messaging] Cannot toggle favorite - no webview available")782+ logging.Warn(fmt.Sprintf("[messaging] Cannot toggle favorite - no webview available"))
784 return783 return
785 }784 }
786 785
787 if h.webView.IsDestroyed() {786 if h.webView.IsDestroyed() {
788- log.Printf("[messaging] Cannot toggle favorite - webview is destroyed")787+ logging.Warn(fmt.Sprintf("[messaging] Cannot toggle favorite - webview is destroyed"))
789 return788 return
790 }789 }
791 790
792 if msg.URL == "" {791 if msg.URL == "" {
793- log.Printf("[messaging] Cannot toggle favorite - URL is empty")792+ logging.Warn(fmt.Sprintf("[messaging] Cannot toggle favorite - URL is empty"))
794 return793 return
795 }794 }
796 795
797 // Toggle the favorite796 // Toggle the favorite
798 added, err := h.browserService.ToggleFavorite(context.Background(), msg.URL, msg.Title, msg.FaviconURL)797 added, err := h.browserService.ToggleFavorite(context.Background(), msg.URL, msg.Title, msg.FaviconURL)
799 if err != nil {798 if err != nil {
800- log.Printf("[messaging] Failed to toggle favorite for %s: %v", msg.URL, err)799+ logging.Error(fmt.Sprintf("[messaging] Failed to toggle favorite for %s: %v", msg.URL, err))
801 _ = h.webView.InjectScript("window.__dumber_favorite_toggled_error && window.__dumber_favorite_toggled_error('Failed to toggle favorite')")800 _ = h.webView.InjectScript("window.__dumber_favorite_toggled_error && window.__dumber_favorite_toggled_error('Failed to toggle favorite')")
802 return801 return
803 }802 }
@@ -809,35 +808,35 @@ func (h *Handler) handleToggleFavorite(msg Message) {
809 }808 }
810 b, err := json.Marshal(result)809 b, err := json.Marshal(result)
811 if err != nil {810 if err != nil {
812- log.Printf("[messaging] Failed to marshal toggle result: %v", err)811+ logging.Error(fmt.Sprintf("[messaging] Failed to marshal toggle result: %v", err))
813 return812 return
814 }813 }
815 814
816- log.Printf("[messaging] Favorite toggled for %s (added: %v)", msg.URL, added)815+ logging.Debug(fmt.Sprintf("[messaging] Favorite toggled for %s (added: %v)", msg.URL, added))
817 _ = h.webView.InjectScript("window.__dumber_favorite_toggled && window.__dumber_favorite_toggled(" + string(b) + ")")816 _ = h.webView.InjectScript("window.__dumber_favorite_toggled && window.__dumber_favorite_toggled(" + string(b) + ")")
818 }817 }
819 818
820 // handleIsFavorite checks if a URL is favorited819 // handleIsFavorite checks if a URL is favorited
821 func (h *Handler) handleIsFavorite(msg Message) {820 func (h *Handler) handleIsFavorite(msg Message) {
822 if h.webView == nil {821 if h.webView == nil {
823- log.Printf("[messaging] Cannot check favorite - no webview available")822+ logging.Warn(fmt.Sprintf("[messaging] Cannot check favorite - no webview available"))
824 return823 return
825 }824 }
826 825
827 if h.webView.IsDestroyed() {826 if h.webView.IsDestroyed() {
828- log.Printf("[messaging] Cannot check favorite - webview is destroyed")827+ logging.Warn(fmt.Sprintf("[messaging] Cannot check favorite - webview is destroyed"))
829 return828 return
830 }829 }
831 830
832 if msg.URL == "" {831 if msg.URL == "" {
833- log.Printf("[messaging] Cannot check favorite - URL is empty")832+ logging.Warn(fmt.Sprintf("[messaging] Cannot check favorite - URL is empty"))
834 return833 return
835 }834 }
836 835
837 // Check if the URL is favorited836 // Check if the URL is favorited
838 isFavorite, err := h.browserService.IsFavorite(context.Background(), msg.URL)837 isFavorite, err := h.browserService.IsFavorite(context.Background(), msg.URL)
839 if err != nil {838 if err != nil {
840- log.Printf("[messaging] Failed to check if favorite for %s: %v", msg.URL, err)839+ logging.Error(fmt.Sprintf("[messaging] Failed to check if favorite for %s: %v", msg.URL, err))
841 return840 return
842 }841 }
843 842
@@ -848,7 +847,7 @@ func (h *Handler) handleIsFavorite(msg Message) {
848 }847 }
849 b, err := json.Marshal(result)848 b, err := json.Marshal(result)
850 if err != nil {849 if err != nil {
851- log.Printf("[messaging] Failed to marshal is_favorite result: %v", err)850+ logging.Error(fmt.Sprintf("[messaging] Failed to marshal is_favorite result: %v", err))
852 return851 return
853 }852 }
854 853
@@ -869,6 +868,6 @@ func (h *Handler) handleKeyboardBlocking(msg Message) {
869 }868 }
870 869
871 if err := h.webView.InjectScript(script); err != nil {870 if err := h.webView.InjectScript(script); err != nil {
872- log.Printf("[ERROR] Failed to inject keyboard blocking script: %v", err)871+ logging.Error(fmt.Sprintf("[ERROR] Failed to inject keyboard blocking script: %v", err))
873 }872 }
874 }873 }
M internal/cache/binary_format.go +2 -3
@@ -4,7 +4,6 @@ package cache
4 import (4 import (
5 "encoding/binary"5 "encoding/binary"
6 "fmt"6 "fmt"
7- "log"
8 "os"7 "os"
9 "syscall"8 "syscall"
10 "unsafe"9 "unsafe"
@@ -214,7 +213,7 @@ func (c *DmenuFuzzyCache) writeHeader(data []byte, offset int, totalSize int) in
214 logging.Warn(fmt.Sprintf("too many entries for binary format: %d, truncating", len(c.entries)))213 logging.Warn(fmt.Sprintf("too many entries for binary format: %d, truncating", len(c.entries)))
215 }214 }
216 if indexOffset < 0 {215 if indexOffset < 0 {
217- log.Printf("Warning: negative index offset: %d, using 0", indexOffset)216+ logging.Warn(fmt.Sprintf("Warning: negative index offset: %d, using 0", indexOffset))
218 indexOffset = 0217 indexOffset = 0
219 }218 }
220 219
@@ -446,7 +445,7 @@ func IsValidCacheFile(filename string) bool {
446 }445 }
447 defer func() {446 defer func() {
448 if closeErr := file.Close(); closeErr != nil {447 if closeErr := file.Close(); closeErr != nil {
449- log.Printf("Warning: failed to close cache validation file: %v", closeErr)448+ logging.Warn(fmt.Sprintf("Warning: failed to close cache validation file: %v", closeErr))
450 }449 }
451 }()450 }()
452 451
M internal/cache/filter_cache.go +3 -4
@@ -4,7 +4,6 @@ import (
4 "encoding/binary"4 "encoding/binary"
5 "fmt"5 "fmt"
6 "io"6 "io"
7- "log"
8 "os"7 "os"
9 "syscall"8 "syscall"
10 "time"9 "time"
@@ -116,7 +115,7 @@ func (fc *FilterCache) LoadMapped() (*FilterData, error) {
116 header, err := fc.parseHeader()115 header, err := fc.parseHeader()
117 if err != nil {116 if err != nil {
118 if unmapErr := fc.Unmap(); unmapErr != nil {117 if unmapErr := fc.Unmap(); unmapErr != nil {
119- log.Printf("[filter-cache] failed to unmap after header parse error: %v", unmapErr)118+ logging.Warn(fmt.Sprintf("[filter-cache] failed to unmap after header parse error: %v", unmapErr))
120 }119 }
121 return nil, fmt.Errorf("invalid cache header: %w", err)120 return nil, fmt.Errorf("invalid cache header: %w", err)
122 }121 }
@@ -124,14 +123,14 @@ func (fc *FilterCache) LoadMapped() (*FilterData, error) {
124 // Validate magic and version123 // Validate magic and version
125 if header.Magic != cacheMagic {124 if header.Magic != cacheMagic {
126 if unmapErr := fc.Unmap(); unmapErr != nil {125 if unmapErr := fc.Unmap(); unmapErr != nil {
127- log.Printf("[filter-cache] failed to unmap after magic validation: %v", unmapErr)126+ logging.Warn(fmt.Sprintf("[filter-cache] failed to unmap after magic validation: %v", unmapErr))
128 }127 }
129 return nil, fmt.Errorf("invalid cache magic bytes")128 return nil, fmt.Errorf("invalid cache magic bytes")
130 }129 }
131 130
132 if header.Version != cacheFormatVersion {131 if header.Version != cacheFormatVersion {
133 if unmapErr := fc.Unmap(); unmapErr != nil {132 if unmapErr := fc.Unmap(); unmapErr != nil {
134- log.Printf("[filter-cache] failed to unmap after version validation: %v", unmapErr)133+ logging.Warn(fmt.Sprintf("[filter-cache] failed to unmap after version validation: %v", unmapErr))
135 }134 }
136 return nil, fmt.Errorf("unsupported cache version: %d", header.Version)135 return nil, fmt.Errorf("unsupported cache version: %d", header.Version)
137 }136 }
M internal/cache/generic/cache.go +5 -3
@@ -2,8 +2,10 @@ package generic
2 2
3 import (3 import (
4 "context"4 "context"
5- "log"5+ "fmt"
6 "sync"6 "sync"
7+
8+ "github.com/bnema/dumber/internal/logging"
7 )9 )
8 10
9 // Cache provides a generic interface for caching any type of data.11 // Cache provides a generic interface for caching any type of data.
@@ -109,7 +111,7 @@ func (c *GenericCache[K, V]) Set(key K, value V) error {
109 111
110 ctx := context.Background()112 ctx := context.Background()
111 if err := c.dbOps.Persist(ctx, key, value); err != nil {113 if err := c.dbOps.Persist(ctx, key, value); err != nil {
112- log.Printf("Warning: async persist failed for key %v: %v", key, err)114+ logging.Warn(fmt.Sprintf("Warning: async persist failed for key %v: %v", key, err))
113 }115 }
114 }()116 }()
115 117
@@ -129,7 +131,7 @@ func (c *GenericCache[K, V]) Delete(key K) error {
129 131
130 ctx := context.Background()132 ctx := context.Background()
131 if err := c.dbOps.Delete(ctx, key); err != nil {133 if err := c.dbOps.Delete(ctx, key); err != nil {
132- log.Printf("Warning: async delete failed for key %v: %v", key, err)134+ logging.Warn(fmt.Sprintf("Warning: async delete failed for key %v: %v", key, err))
133 }135 }
134 }()136 }()
135 137
M internal/db/init.go +5 -5
@@ -3,10 +3,10 @@ package db
3 import (3 import (
4 "database/sql"4 "database/sql"
5 "fmt"5 "fmt"
6- "log"
7 "os"6 "os"
8 "path/filepath"7 "path/filepath"
9 8
9+ "github.com/bnema/dumber/internal/logging"
10 "github.com/bnema/dumber/internal/migrations"10 "github.com/bnema/dumber/internal/migrations"
11 _ "github.com/ncruces/go-sqlite3/driver" // SQLite driver11 _ "github.com/ncruces/go-sqlite3/driver" // SQLite driver
12 _ "github.com/ncruces/go-sqlite3/embed" // Embed SQLite12 _ "github.com/ncruces/go-sqlite3/embed" // Embed SQLite
@@ -32,7 +32,7 @@ func InitDB(dbPath string) (*sql.DB, error) {
32 // Test the connection32 // Test the connection
33 if err := database.Ping(); err != nil {33 if err := database.Ping(); err != nil {
34 if err := database.Close(); err != nil {34 if err := database.Close(); err != nil {
35- log.Printf("Warning: failed to close database: %v", err)35+ logging.Warn(fmt.Sprintf("Warning: failed to close database: %v", err))
36 }36 }
37 return nil, fmt.Errorf("failed to connect to database: %w", err)37 return nil, fmt.Errorf("failed to connect to database: %w", err)
38 }38 }
@@ -50,19 +50,19 @@ func InitDB(dbPath string) (*sql.DB, error) {
50 for _, pragma := range pragmas {50 for _, pragma := range pragmas {
51 if _, err := database.Exec(pragma); err != nil {51 if _, err := database.Exec(pragma); err != nil {
52 if err := database.Close(); err != nil {52 if err := database.Close(); err != nil {
53- log.Printf("Warning: failed to close database: %v", err)53+ logging.Warn(fmt.Sprintf("Warning: failed to close database: %v", err))
54 }54 }
55 return nil, fmt.Errorf("failed to set pragma %q: %w", pragma, err)55 return nil, fmt.Errorf("failed to set pragma %q: %w", pragma, err)
56 }56 }
57 }57 }
58 58
59- log.Printf("SQLite configured with WAL mode and performance optimizations")59+ logging.Info(fmt.Sprintf("SQLite configured with WAL mode and performance optimizations"))
60 60
61 // Run embedded migrations - single source of truth for schema initialization61 // Run embedded migrations - single source of truth for schema initialization
62 // RunEmbeddedMigrations is idempotent and checks if migrations are already applied62 // RunEmbeddedMigrations is idempotent and checks if migrations are already applied
63 if err := migrations.RunEmbeddedMigrations(database); err != nil {63 if err := migrations.RunEmbeddedMigrations(database); err != nil {
64 if err := database.Close(); err != nil {64 if err := database.Close(); err != nil {
65- log.Printf("Warning: failed to close database: %v", err)65+ logging.Warn(fmt.Sprintf("Warning: failed to close database: %v", err))
66 }66 }
67 return nil, fmt.Errorf("failed to run database migrations: %w", err)67 return nil, fmt.Errorf("failed to run database migrations: %w", err)
68 }68 }
M internal/filtering/updater.go +2 -3
@@ -4,7 +4,6 @@ import (
4 "context"4 "context"
5 "crypto/sha256"5 "crypto/sha256"
6 "fmt"6 "fmt"
7- "log"
8 "net/http"7 "net/http"
9 "strings"8 "strings"
10 "sync"9 "sync"
@@ -141,7 +140,7 @@ func (fu *FilterUpdater) checkSingleUpdate(ctx context.Context, url string) (*Fi
141 }140 }
142 defer func() {141 defer func() {
143 if err := resp.Body.Close(); err != nil {142 if err := resp.Body.Close(); err != nil {
144- log.Printf("warning: failed to close response body: %v", err)143+ logging.Warn(fmt.Sprintf("warning: failed to close response body: %v", err))
145 }144 }
146 }()145 }()
147 146
@@ -190,7 +189,7 @@ func (fu *FilterUpdater) downloadUpdate(ctx context.Context, url, lastModified,
190 }189 }
191 defer func() {190 defer func() {
192 if err := resp.Body.Close(); err != nil {191 if err := resp.Body.Close(); err != nil {
193- log.Printf("warning: failed to close response body: %v", err)192+ logging.Warn(fmt.Sprintf("warning: failed to close response body: %v", err))
194 }193 }
195 }()194 }()
196 195
M internal/migrations/migrations.go +11 -10
@@ -4,10 +4,11 @@ import (
4 "database/sql"4 "database/sql"
5 "embed"5 "embed"
6 "fmt"6 "fmt"
7- "log"
8 "sort"7 "sort"
9 "strconv"8 "strconv"
10 "strings"9 "strings"
10+
11+ "github.com/bnema/dumber/internal/logging"
11 )12 )
12 13
13 //go:embed *.sql14 //go:embed *.sql
@@ -36,14 +37,14 @@ func GetMigrations() ([]Migration, error) {
36 // Parse version from filename (e.g., "001_initial.sql" -> version 1)37 // Parse version from filename (e.g., "001_initial.sql" -> version 1)
37 parts := strings.SplitN(entry.Name(), "_", 2)38 parts := strings.SplitN(entry.Name(), "_", 2)
38 if len(parts) != 2 {39 if len(parts) != 2 {
39- log.Printf("Warning: skipping migration file with invalid name format: %s", entry.Name())40+ logging.Warn(fmt.Sprintf("Warning: skipping migration file with invalid name format: %s", entry.Name()))
40 continue41 continue
41 }42 }
42 43
43 versionStr := parts[0]44 versionStr := parts[0]
44 version, err := strconv.Atoi(versionStr)45 version, err := strconv.Atoi(versionStr)
45 if err != nil {46 if err != nil {
46- log.Printf("Warning: skipping migration file with invalid version: %s", entry.Name())47+ logging.Warn(fmt.Sprintf("Warning: skipping migration file with invalid version: %s", entry.Name()))
47 continue48 continue
48 }49 }
49 50
@@ -123,7 +124,7 @@ func applyMigration(db *sql.DB, migration Migration) error {
123 return nil124 return nil
124 }125 }
125 126
126- log.Printf("Applying migration %03d: %s", migration.Version, migration.Name)127+ logging.Info(fmt.Sprintf("Applying migration %03d: %s", migration.Version, migration.Name))
127 128
128 // Use transaction for safety129 // Use transaction for safety
129 tx, err := db.Begin()130 tx, err := db.Begin()
@@ -133,7 +134,7 @@ func applyMigration(db *sql.DB, migration Migration) error {
133 defer func() {134 defer func() {
134 if err != nil {135 if err != nil {
135 if rollbackErr := tx.Rollback(); rollbackErr != nil {136 if rollbackErr := tx.Rollback(); rollbackErr != nil {
136- log.Printf("failed to rollback migration transaction: %v", rollbackErr)137+ logging.Error(fmt.Sprintf("failed to rollback migration transaction: %v", rollbackErr))
137 }138 }
138 }139 }
139 }()140 }()
@@ -155,7 +156,7 @@ func applyMigration(db *sql.DB, migration Migration) error {
155 return fmt.Errorf("failed to commit migration transaction: %w", err)156 return fmt.Errorf("failed to commit migration transaction: %w", err)
156 }157 }
157 158
158- log.Printf("Successfully applied migration %03d: %s", migration.Version, migration.Name)159+ logging.Info(fmt.Sprintf("Successfully applied migration %03d: %s", migration.Version, migration.Name))
159 return nil160 return nil
160 }161 }
161 162
@@ -179,7 +180,7 @@ func GetAppliedMigrations(db *sql.DB) ([]int, error) {
179 }180 }
180 defer func() {181 defer func() {
181 if err := rows.Close(); err != nil {182 if err := rows.Close(); err != nil {
182- log.Printf("failed to close migration query rows: %v", err)183+ logging.Warn(fmt.Sprintf("failed to close migration query rows: %v", err))
183 }184 }
184 }()185 }()
185 186
@@ -230,13 +231,13 @@ func VerifyAllMigrationsApplied(db *sql.DB) error {
230 231
231 // Report results232 // Report results
232 if len(missingMigrations) > 0 {233 if len(missingMigrations) > 0 {
233- log.Printf("WARNING: %d migrations are not applied:", len(missingMigrations))234+ logging.Warn(fmt.Sprintf("WARNING: %d migrations are not applied:", len(missingMigrations)))
234 for _, migration := range missingMigrations {235 for _, migration := range missingMigrations {
235- log.Printf(" - Migration %03d: %s", migration.Version, migration.Name)236+ logging.Warn(fmt.Sprintf(" - Migration %03d: %s", migration.Version, migration.Name))
236 }237 }
237 return fmt.Errorf("%d migrations are not applied", len(missingMigrations))238 return fmt.Errorf("%d migrations are not applied", len(missingMigrations))
238 }239 }
239 240
240- log.Printf("Migration verification: All %d migrations are applied", len(allMigrations))241+ logging.Info(fmt.Sprintf("Migration verification: All %d migrations are applied", len(allMigrations)))
241 return nil242 return nil
242 }243 }
M internal/services/browser.go +19 -19
@@ -6,7 +6,6 @@ import (
6 "database/sql"6 "database/sql"
7 "embed"7 "embed"
8 "fmt"8 "fmt"
9- "log"
10 "math"9 "math"
11 neturl "net/url"10 neturl "net/url"
12 "sync"11 "sync"
@@ -15,6 +14,7 @@ import (
15 "github.com/bnema/dumber/internal/cache"14 "github.com/bnema/dumber/internal/cache"
16 "github.com/bnema/dumber/internal/config"15 "github.com/bnema/dumber/internal/config"
17 "github.com/bnema/dumber/internal/db"16 "github.com/bnema/dumber/internal/db"
17+ "github.com/bnema/dumber/internal/logging"
18 "github.com/bnema/dumber/internal/parser"18 "github.com/bnema/dumber/internal/parser"
19 "github.com/bnema/dumber/pkg/webkit"19 "github.com/bnema/dumber/pkg/webkit"
20 )20 )
@@ -126,14 +126,14 @@ func (s *BrowserService) AttachWebView(view *webkit.WebView) {
126 126
127 // LoadGUIBundle loads the unified GUI bundle from assets127 // LoadGUIBundle loads the unified GUI bundle from assets
128 func (s *BrowserService) LoadGUIBundle(assets embed.FS) error {128 func (s *BrowserService) LoadGUIBundle(assets embed.FS) error {
129- log.Printf("[browser] Attempting to load GUI bundle from assets/gui/gui.min.js")129+ logging.Debug(fmt.Sprintf("[browser] Attempting to load GUI bundle from assets/gui/gui.min.js"))
130 bundleBytes, err := assets.ReadFile("assets/gui/gui.min.js")130 bundleBytes, err := assets.ReadFile("assets/gui/gui.min.js")
131 if err != nil {131 if err != nil {
132- log.Printf("[browser] ERROR: Failed to load GUI bundle: %v", err)132+ logging.Error(fmt.Sprintf("[browser] ERROR: Failed to load GUI bundle: %v", err))
133 return fmt.Errorf("failed to load GUI bundle: %w", err)133 return fmt.Errorf("failed to load GUI bundle: %w", err)
134 }134 }
135 s.guiBundle = string(bundleBytes)135 s.guiBundle = string(bundleBytes)
136- log.Printf("[browser] GUI bundle loaded successfully into browser service, size: %d bytes", len(bundleBytes))136+ logging.Info(fmt.Sprintf("[browser] GUI bundle loaded successfully into browser service, size: %d bytes", len(bundleBytes)))
137 return nil137 return nil
138 }138 }
139 139
@@ -438,7 +438,7 @@ func (s *BrowserService) recordHistory(ctx context.Context, url, title string) e
438 return nil438 return nil
439 default:439 default:
440 // Queue full, write directly (fallback)440 // Queue full, write directly (fallback)
441- log.Printf("Warning: history queue full, writing directly")441+ logging.Warn(fmt.Sprintf("Warning: history queue full, writing directly"))
442 return s.dbQueries.AddOrUpdateHistory(ctx, normalizedURL, titleNull)442 return s.dbQueries.AddOrUpdateHistory(ctx, normalizedURL, titleNull)
443 }443 }
444 }444 }
@@ -463,11 +463,11 @@ func (s *BrowserService) processHistoryQueue() {
463 // but we can still batch the operations to reduce overhead463 // but we can still batch the operations to reduce overhead
464 for _, update := range batch {464 for _, update := range batch {
465 if err := s.dbQueries.AddOrUpdateHistory(ctx, update.url, update.title); err != nil {465 if err := s.dbQueries.AddOrUpdateHistory(ctx, update.url, update.title); err != nil {
466- log.Printf("Warning: failed to write history for %s: %v", update.url, err)466+ logging.Warn(fmt.Sprintf("Warning: failed to write history for %s: %v", update.url, err))
467 }467 }
468 }468 }
469 469
470- log.Printf("Flushed %d history updates to database", len(batch))470+ logging.Debug(fmt.Sprintf("Flushed %d history updates to database", len(batch)))
471 471
472 // Track history entries for fuzzy cache refresh472 // Track history entries for fuzzy cache refresh
473 batchSize := len(batch)473 batchSize := len(batch)
@@ -481,8 +481,8 @@ func (s *BrowserService) processHistoryQueue() {
481 timeSinceRefresh := time.Since(s.lastCacheRefresh)481 timeSinceRefresh := time.Since(s.lastCacheRefresh)
482 482
483 if s.historySinceRefresh >= minEntries && timeSinceRefresh >= minInterval {483 if s.historySinceRefresh >= minEntries && timeSinceRefresh >= minInterval {
484- log.Printf("[cache] Triggering async fuzzy cache refresh (%d new entries, %v since last refresh)",484+ logging.Debug(fmt.Sprintf("[cache] Triggering async fuzzy cache refresh (%d new entries, %v since last refresh)",
485- s.historySinceRefresh, timeSinceRefresh)485+ s.historySinceRefresh, timeSinceRefresh))
486 486
487 // Async refresh in background (non-blocking)487 // Async refresh in background (non-blocking)
488 s.fuzzyCache.InvalidateAndRefresh(ctx)488 s.fuzzyCache.InvalidateAndRefresh(ctx)
@@ -524,10 +524,10 @@ func (s *BrowserService) FlushHistoryQueue(ctx context.Context) error {
524 // Wait for completion with context cancellation support524 // Wait for completion with context cancellation support
525 select {525 select {
526 case <-s.historyFlushDone:526 case <-s.historyFlushDone:
527- log.Printf("History queue flushed and closed")527+ logging.Info(fmt.Sprintf("History queue flushed and closed"))
528 case <-ctx.Done():528 case <-ctx.Done():
529 flushErr = fmt.Errorf("history queue flush cancelled: %w", ctx.Err())529 flushErr = fmt.Errorf("history queue flush cancelled: %w", ctx.Err())
530- log.Printf("Warning: %v", flushErr)530+ logging.Warn(fmt.Sprintf("Warning: %v", flushErr))
531 }531 }
532 })532 })
533 return flushErr533 return flushErr
@@ -581,7 +581,7 @@ func (s *BrowserService) LoadZoomCacheFromDB(ctx context.Context) error {
581 }581 }
582 582
583 loadedCount := len(s.zoomCache.List())583 loadedCount := len(s.zoomCache.List())
584- log.Printf("Loaded %d zoom levels into cache", loadedCount)584+ logging.Info(fmt.Sprintf("Loaded %d zoom levels into cache", loadedCount))
585 return nil585 return nil
586 }586 }
587 587
@@ -593,7 +593,7 @@ func (s *BrowserService) LoadCertCacheFromDB(ctx context.Context) error {
593 }593 }
594 594
595 loadedCount := len(s.certCache.List())595 loadedCount := len(s.certCache.List())
596- log.Printf("Loaded %d certificate validations into cache", loadedCount)596+ logging.Info(fmt.Sprintf("Loaded %d certificate validations into cache", loadedCount))
597 return nil597 return nil
598 }598 }
599 599
@@ -607,8 +607,8 @@ func (s *BrowserService) LoadFuzzyCacheFromDB(ctx context.Context) error {
607 }607 }
608 608
609 stats := s.fuzzyCache.Stats()609 stats := s.fuzzyCache.Stats()
610- log.Printf("Loaded fuzzy cache: %d entries, %d trigrams",610+ logging.Info(fmt.Sprintf("Loaded fuzzy cache: %d entries, %d trigrams",
611- stats.EntryCount, stats.TrigramCount)611+ stats.EntryCount, stats.TrigramCount))
612 return nil612 return nil
613 }613 }
614 614
@@ -620,7 +620,7 @@ func (s *BrowserService) LoadFavoritesCacheFromDB(ctx context.Context) error {
620 }620 }
621 621
622 loadedCount := len(s.favoritesCache.List())622 loadedCount := len(s.favoritesCache.List())
623- log.Printf("Loaded %d favorites into cache", loadedCount)623+ logging.Info(fmt.Sprintf("Loaded %d favorites into cache", loadedCount))
624 return nil624 return nil
625 }625 }
626 626
@@ -676,7 +676,7 @@ func (s *BrowserService) ToggleFavorite(ctx context.Context, url, title, favicon
676 if err := s.favoritesCache.Delete(url); err != nil {676 if err := s.favoritesCache.Delete(url); err != nil {
677 return false, fmt.Errorf("failed to remove favorite: %w", err)677 return false, fmt.Errorf("failed to remove favorite: %w", err)
678 }678 }
679- log.Printf("Removed favorite: %s", url)679+ logging.Info(fmt.Sprintf("Removed favorite: %s", url))
680 return false, nil680 return false, nil
681 }681 }
682 682
@@ -691,7 +691,7 @@ func (s *BrowserService) ToggleFavorite(ctx context.Context, url, title, favicon
691 return false, fmt.Errorf("failed to add favorite: %w", err)691 return false, fmt.Errorf("failed to add favorite: %w", err)
692 }692 }
693 693
694- log.Printf("Added favorite: %s", url)694+ logging.Info(fmt.Sprintf("Added favorite: %s", url))
695 return true, nil695 return true, nil
696 }696 }
697 697
@@ -721,7 +721,7 @@ func (s *BrowserService) FlushAllCaches(ctx context.Context) error {
721 return fmt.Errorf("failed to flush history queue: %w", err)721 return fmt.Errorf("failed to flush history queue: %w", err)
722 }722 }
723 723
724- log.Printf("All caches flushed successfully")724+ logging.Info(fmt.Sprintf("All caches flushed successfully"))
725 return nil725 return nil
726 }726 }
727 727
M internal/services/favicon_service.go +13 -13
@@ -6,13 +6,13 @@ import (
6 "database/sql"6 "database/sql"
7 "errors"7 "errors"
8 "fmt"8 "fmt"
9- "log"
10 "os"9 "os"
11 "path/filepath"10 "path/filepath"
12 "sync"11 "sync"
13 "time"12 "time"
14 13
15 "github.com/bnema/dumber/internal/db"14 "github.com/bnema/dumber/internal/db"
15+ "github.com/bnema/dumber/internal/logging"
16 "github.com/bnema/dumber/internal/parser"16 "github.com/bnema/dumber/internal/parser"
17 "github.com/bnema/dumber/pkg/webkit"17 "github.com/bnema/dumber/pkg/webkit"
18 "github.com/diamondburned/gotk4/pkg/gdk/v4"18 "github.com/diamondburned/gotk4/pkg/gdk/v4"
@@ -83,11 +83,11 @@ func NewFaviconService(faviconDB *webkit.FaviconDatabase, queries db.DatabaseQue
83 // The FaviconDatabase is shared across all WebViews (at NetworkSession level)83 // The FaviconDatabase is shared across all WebViews (at NetworkSession level)
84 faviconDB.ConnectFaviconChanged(func(pageURI, faviconURI string) {84 faviconDB.ConnectFaviconChanged(func(pageURI, faviconURI string) {
85 if err := fs.OnFaviconChanged(pageURI, faviconURI); err != nil {85 if err := fs.OnFaviconChanged(pageURI, faviconURI); err != nil {
86- log.Printf("[favicon] Handler error for %s: %v", pageURI, err)86+ logging.Error(fmt.Sprintf("[favicon] Handler error for %s: %v", pageURI, err))
87 }87 }
88 })88 })
89 89
90- log.Printf("[favicon] Registered single shared favicon handler at FaviconService level")90+ logging.Info(fmt.Sprintf("[favicon] Registered single shared favicon handler at FaviconService level"))
91 91
92 return fs, nil92 return fs, nil
93 }93 }
@@ -133,14 +133,14 @@ func (fs *FaviconService) OnFaviconChanged(pageURL, faviconURI string) error {
133 }133 }
134 134
135 // NEW favicon detected (first time or actual change)135 // NEW favicon detected (first time or actual change)
136- log.Printf("[favicon] Favicon changed for %s: %s", pageURL, faviconURI)136+ logging.Info(fmt.Sprintf("[favicon] Favicon changed for %s: %s", pageURL, faviconURI))
137 137
138 ctx := context.Background()138 ctx := context.Background()
139 139
140 // Update our history database with the favicon URI140 // Update our history database with the favicon URI
141 nullString := sql.NullString{String: faviconURI, Valid: faviconURI != ""}141 nullString := sql.NullString{String: faviconURI, Valid: faviconURI != ""}
142 if err := fs.dbQueries.UpdateHistoryFavicon(ctx, nullString, canonicalURL); err != nil {142 if err := fs.dbQueries.UpdateHistoryFavicon(ctx, nullString, canonicalURL); err != nil {
143- log.Printf("[favicon] Failed to update favicon URI in database for %s: %v", pageURL, err)143+ logging.Error(fmt.Sprintf("[favicon] Failed to update favicon URI in database for %s: %v", pageURL, err))
144 return fmt.Errorf("failed to update favicon in database: %w", err)144 return fmt.Errorf("failed to update favicon in database: %w", err)
145 }145 }
146 146
@@ -182,7 +182,7 @@ func (fs *FaviconService) propagateFaviconToDomain(pageURL, faviconURI string) {
182 domainPattern := sql.NullString{String: "%://" + parsedURL.Host + "%", Valid: true}182 domainPattern := sql.NullString{String: "%://" + parsedURL.Host + "%", Valid: true}
183 entries, err := fs.dbQueries.SearchHistory(ctx, domainPattern, sql.NullString{}, 1000)183 entries, err := fs.dbQueries.SearchHistory(ctx, domainPattern, sql.NullString{}, 1000)
184 if err != nil {184 if err != nil {
185- log.Printf("[favicon] Failed to search history for domain %s: %v", parsedURL.Host, err)185+ logging.Error(fmt.Sprintf("[favicon] Failed to search history for domain %s: %v", parsedURL.Host, err))
186 return186 return
187 }187 }
188 188
@@ -205,9 +205,9 @@ func (fs *FaviconService) propagateFaviconToDomain(pageURL, faviconURI string) {
205 for _, entry := range entries {205 for _, entry := range entries {
206 if entry.FaviconUrl.String == "" {206 if entry.FaviconUrl.String == "" {
207 if err := fs.dbQueries.UpdateHistoryFavicon(ctx, faviconNull, entry.Url); err != nil {207 if err := fs.dbQueries.UpdateHistoryFavicon(ctx, faviconNull, entry.Url); err != nil {
208- log.Printf("[favicon] Failed to propagate favicon to %s: %v", entry.Url, err)208+ logging.Error(fmt.Sprintf("[favicon] Failed to propagate favicon to %s: %v", entry.Url, err))
209 } else {209 } else {
210- log.Printf("[favicon] Propagated favicon to %s", entry.Url)210+ logging.Debug(fmt.Sprintf("[favicon] Propagated favicon to %s", entry.Url))
211 }211 }
212 }212 }
213 }213 }
@@ -222,12 +222,12 @@ func (fs *FaviconService) preloadFavicon(pageURL, canonicalURL, faviconURI strin
222 222
223 texture, err := fs.renderFaviconTexture(ctx, pageURL, faviconURI)223 texture, err := fs.renderFaviconTexture(ctx, pageURL, faviconURI)
224 if err != nil {224 if err != nil {
225- log.Printf("[favicon] Preload failed for %s: %v", pageURL, err)225+ logging.Warn(fmt.Sprintf("[favicon] Preload failed for %s: %v", pageURL, err))
226 return226 return
227 }227 }
228 228
229 fs.maybeExportTexture(canonicalURL, texture)229 fs.maybeExportTexture(canonicalURL, texture)
230- log.Printf("[favicon] Preloaded and exported favicon for %s", pageURL)230+ logging.Debug(fmt.Sprintf("[favicon] Preloaded and exported favicon for %s", pageURL))
231 }231 }
232 232
233 // GetFaviconTexture loads a favicon texture asynchronously and calls the callback with the result.233 // GetFaviconTexture loads a favicon texture asynchronously and calls the callback with the result.
@@ -374,7 +374,7 @@ func (fs *FaviconService) lookupFaviconURI(ctx context.Context, canonicalURL, or
374 continue374 continue
375 }375 }
376 if !errors.Is(err, sql.ErrNoRows) {376 if !errors.Is(err, sql.ErrNoRows) {
377- log.Printf("[favicon] failed to query history entry for %s: %v", candidate, err)377+ logging.Error(fmt.Sprintf("[favicon] failed to query history entry for %s: %v", candidate, err))
378 }378 }
379 }379 }
380 380
@@ -417,7 +417,7 @@ func (fs *FaviconService) maybeExportTexture(pageURL string, texture *gdk.Textur
417 417
418 _ = webkit.IdleAdd(func() bool {418 _ = webkit.IdleAdd(func() bool {
419 if err := webkit.SaveTextureAsPNG(texture, exportPath, fs.targetSize); err != nil {419 if err := webkit.SaveTextureAsPNG(texture, exportPath, fs.targetSize); err != nil {
420- log.Printf("[favicon] Failed to export favicon PNG for %s: %v", pageURL, err)420+ logging.Error(fmt.Sprintf("[favicon] Failed to export favicon PNG for %s: %v", pageURL, err))
421 }421 }
422 return false422 return false
423 })423 })
@@ -473,7 +473,7 @@ func (fs *FaviconService) getExportPath(pageURL string) string {
473 func (fs *FaviconService) shouldProcessURL(pageURL string) bool {473 func (fs *FaviconService) shouldProcessURL(pageURL string) bool {
474 parsedURL, err := webkit.ParseURL(pageURL)474 parsedURL, err := webkit.ParseURL(pageURL)
475 if err != nil {475 if err != nil {
476- log.Printf("[favicon] Invalid page URL: %s", pageURL)476+ logging.Warn(fmt.Sprintf("[favicon] Invalid page URL: %s", pageURL))
477 return false477 return false
478 }478 }
479 479
M main.go +6 -4
@@ -2,7 +2,7 @@ package main
2 2
3 import (3 import (
4 "embed"4 "embed"
5- "log"5+ "fmt"
6 "os"6 "os"
7 "runtime"7 "runtime"
8 8
@@ -35,7 +35,8 @@ func main() {
35 35
36 // Load config file first, then initialize logging with actual config36 // Load config file first, then initialize logging with actual config
37 if err := config.Init(); err != nil {37 if err := config.Init(); err != nil {
38- log.Printf("Warning: failed to load config: %v, using defaults", err)38+ // Use fmt.Printf for early errors before logging is initialized
39+ fmt.Printf("Warning: failed to load config: %v, using defaults\n", err)
39 }40 }
40 cfg := config.Get()41 cfg := config.Get()
41 42
@@ -49,10 +50,11 @@ func main() {
49 cfg.Logging.MaxAge,50 cfg.Logging.MaxAge,
50 cfg.Logging.Compress,51 cfg.Logging.Compress,
51 ); err != nil {52 ); err != nil {
52- log.Printf("Warning: failed to initialize logging: %v", err)53+ // Use fmt.Printf for early errors before logging is initialized
54+ fmt.Printf("Warning: failed to initialize logging: %v\n", err)
53 }55 }
54 56
55- log.Printf("Starting dumber browser v%s (commit: %s, built: %s)", version, commit, buildDate)57+ logging.Info(fmt.Sprintf("Starting dumber browser v%s (commit: %s, built: %s)", version, commit, buildDate))
56 58
57 // GTK requires all UI calls to run on the main OS thread59 // GTK requires all UI calls to run on the main OS thread
58 if webkit.IsNativeAvailable() {60 if webkit.IsNativeAvailable() {
M pkg/webkit/content_blocking.go +12 -12
@@ -3,10 +3,10 @@ package webkit
3 import (3 import (
4 "context"4 "context"
5 "fmt"5 "fmt"
6- "log"
7 "path/filepath"6 "path/filepath"
8 "sync"7 "sync"
9 8
9+ "github.com/bnema/dumber/internal/logging"
10 webkit "github.com/diamondburned/gotk4-webkitgtk/pkg/webkit/v6"10 webkit "github.com/diamondburned/gotk4-webkitgtk/pkg/webkit/v6"
11 "github.com/diamondburned/gotk4/pkg/gio/v2"11 "github.com/diamondburned/gotk4/pkg/gio/v2"
12 "github.com/diamondburned/gotk4/pkg/glib/v2"12 "github.com/diamondburned/gotk4/pkg/glib/v2"
@@ -31,7 +31,7 @@ func NewContentBlockingManager(storagePath string) (*ContentBlockingManager, err
31 return nil, fmt.Errorf("failed to create UserContentFilterStore")31 return nil, fmt.Errorf("failed to create UserContentFilterStore")
32 }32 }
33 33
34- log.Printf("[webkit] Created UserContentFilterStore at: %s", storagePath)34+ logging.Debug(fmt.Sprintf("[webkit] Created UserContentFilterStore at: %s", storagePath))
35 35
36 return &ContentBlockingManager{36 return &ContentBlockingManager{
37 filterStore: filterStore,37 filterStore: filterStore,
@@ -49,7 +49,7 @@ func (cbm *ContentBlockingManager) CompileFilters(identifier string, jsonRules [
49 return49 return
50 }50 }
51 51
52- log.Printf("[webkit] Compiling content filters (identifier: %s, rules size: %d bytes)", identifier, len(jsonRules))52+ logging.Debug(fmt.Sprintf("[webkit] Compiling content filters (identifier: %s, rules size: %d bytes)", identifier, len(jsonRules)))
53 53
54 // Store identifier for later use54 // Store identifier for later use
55 cbm.mu.Lock()55 cbm.mu.Lock()
@@ -64,7 +64,7 @@ func (cbm *ContentBlockingManager) CompileFilters(identifier string, jsonRules [
64 cbm.filterStore.Save(ctx, identifier, gBytes, func(result gio.AsyncResulter) {64 cbm.filterStore.Save(ctx, identifier, gBytes, func(result gio.AsyncResulter) {
65 filter, err := cbm.filterStore.SaveFinish(result)65 filter, err := cbm.filterStore.SaveFinish(result)
66 if err != nil {66 if err != nil {
67- log.Printf("[webkit] Failed to compile content filter: %v", err)67+ logging.Error(fmt.Sprintf("[webkit] Failed to compile content filter: %v", err))
68 if onComplete != nil {68 if onComplete != nil {
69 onComplete(fmt.Errorf("failed to compile filter: %w", err))69 onComplete(fmt.Errorf("failed to compile filter: %w", err))
70 }70 }
@@ -72,7 +72,7 @@ func (cbm *ContentBlockingManager) CompileFilters(identifier string, jsonRules [
72 }72 }
73 73
74 if filter == nil {74 if filter == nil {
75- log.Printf("[webkit] Filter compilation returned nil")75+ logging.Error(fmt.Sprintf("[webkit] Filter compilation returned nil"))
76 if onComplete != nil {76 if onComplete != nil {
77 onComplete(fmt.Errorf("filter compilation returned nil"))77 onComplete(fmt.Errorf("filter compilation returned nil"))
78 }78 }
@@ -84,7 +84,7 @@ func (cbm *ContentBlockingManager) CompileFilters(identifier string, jsonRules [
84 cbm.compiledFilter = filter84 cbm.compiledFilter = filter
85 cbm.mu.Unlock()85 cbm.mu.Unlock()
86 86
87- log.Printf("[webkit] Filter compilation complete: %s", identifier)87+ logging.Debug(fmt.Sprintf("[webkit] Filter compilation complete: %s", identifier))
88 if onComplete != nil {88 if onComplete != nil {
89 onComplete(nil)89 onComplete(nil)
90 }90 }
@@ -131,7 +131,7 @@ func (cbm *ContentBlockingManager) ApplyFiltersFromJSON(ucm *webkit.UserContentM
131 filter := cbm.compiledFilter131 filter := cbm.compiledFilter
132 cbm.mu.RUnlock()132 cbm.mu.RUnlock()
133 ucm.AddFilter(filter)133 ucm.AddFilter(filter)
134- log.Printf("[webkit] Applied cached content filter to UCM")134+ logging.Debug(fmt.Sprintf("[webkit] Applied cached content filter to UCM"))
135 return nil135 return nil
136 }136 }
137 cbm.mu.RUnlock()137 cbm.mu.RUnlock()
@@ -140,7 +140,7 @@ func (cbm *ContentBlockingManager) ApplyFiltersFromJSON(ucm *webkit.UserContentM
140 return fmt.Errorf("empty filter rules")140 return fmt.Errorf("empty filter rules")
141 }141 }
142 142
143- log.Printf("[webkit] Applying content filters (identifier: %s, rules size: %d bytes)", identifier, len(jsonRules))143+ logging.Debug(fmt.Sprintf("[webkit] Applying content filters (identifier: %s, rules size: %d bytes)", identifier, len(jsonRules)))
144 144
145 // Convert []byte to *glib.Bytes for WebKit API145 // Convert []byte to *glib.Bytes for WebKit API
146 gBytes := glib.NewBytesWithGo(jsonRules)146 gBytes := glib.NewBytesWithGo(jsonRules)
@@ -151,13 +151,13 @@ func (cbm *ContentBlockingManager) ApplyFiltersFromJSON(ucm *webkit.UserContentM
151 cbm.filterStore.Save(ctx, identifier, gBytes, func(result gio.AsyncResulter) {151 cbm.filterStore.Save(ctx, identifier, gBytes, func(result gio.AsyncResulter) {
152 filter, err := cbm.filterStore.SaveFinish(result)152 filter, err := cbm.filterStore.SaveFinish(result)
153 if err != nil {153 if err != nil {
154- log.Printf("[webkit] Failed to compile content filter: %v", err)154+ logging.Error(fmt.Sprintf("[webkit] Failed to compile content filter: %v", err))
155 done <- fmt.Errorf("failed to compile filter: %w", err)155 done <- fmt.Errorf("failed to compile filter: %w", err)
156 return156 return
157 }157 }
158 158
159 if filter == nil {159 if filter == nil {
160- log.Printf("[webkit] Filter compilation returned nil")160+ logging.Error(fmt.Sprintf("[webkit] Filter compilation returned nil"))
161 done <- fmt.Errorf("filter compilation returned nil")161 done <- fmt.Errorf("filter compilation returned nil")
162 return162 return
163 }163 }
@@ -170,7 +170,7 @@ func (cbm *ContentBlockingManager) ApplyFiltersFromJSON(ucm *webkit.UserContentM
170 170
171 RunOnMainThread(func() {171 RunOnMainThread(func() {
172 ucm.AddFilter(filter)172 ucm.AddFilter(filter)
173- log.Printf("[webkit] Successfully added content filter: %s", identifier)173+ logging.Debug(fmt.Sprintf("[webkit] Successfully added content filter: %s", identifier))
174 done <- nil174 done <- nil
175 })175 })
176 })176 })
@@ -182,7 +182,7 @@ func (cbm *ContentBlockingManager) ApplyFiltersFromJSON(ucm *webkit.UserContentM
182 func (cbm *ContentBlockingManager) RemoveAllFilters(ucm *webkit.UserContentManager) {182 func (cbm *ContentBlockingManager) RemoveAllFilters(ucm *webkit.UserContentManager) {
183 if ucm != nil {183 if ucm != nil {
184 ucm.RemoveAllFilters()184 ucm.RemoveAllFilters()
185- log.Printf("[webkit] Removed all content filters")185+ logging.Debug(fmt.Sprintf("[webkit] Removed all content filters"))
186 }186 }
187 }187 }
188 188
M pkg/webkit/keyboard_bridge.go +10 -10
@@ -3,8 +3,8 @@ package webkit
3 import (3 import (
4 "encoding/json"4 "encoding/json"
5 "fmt"5 "fmt"
6- "log"
7 6
7+ "github.com/bnema/dumber/internal/logging"
8 "github.com/diamondburned/gotk4/pkg/gdk/v4"8 "github.com/diamondburned/gotk4/pkg/gdk/v4"
9 "github.com/diamondburned/gotk4/pkg/gtk/v4"9 "github.com/diamondburned/gotk4/pkg/gtk/v4"
10 )10 )
@@ -221,7 +221,7 @@ func (w *WebView) AttachKeyboardBridge() {
221 handler := w.onPaneModeShortcut221 handler := w.onPaneModeShortcut
222 w.mu.RUnlock()222 w.mu.RUnlock()
223 if handler != nil && handler(actionEnter) {223 if handler != nil && handler(actionEnter) {
224- log.Printf("[keyboard-bridge] Pane mode: enter")224+ logging.Debug(fmt.Sprintf("[keyboard-bridge] Pane mode: enter"))
225 return true225 return true
226 }226 }
227 return true // Block even if no handler227 return true // Block even if no handler
@@ -233,7 +233,7 @@ func (w *WebView) AttachKeyboardBridge() {
233 handler := w.onPaneModeShortcut233 handler := w.onPaneModeShortcut
234 w.mu.RUnlock()234 w.mu.RUnlock()
235 if handler != nil && handler(actionExit) {235 if handler != nil && handler(actionExit) {
236- log.Printf("[keyboard-bridge] Escape: exited mode")236+ logging.Debug(fmt.Sprintf("[keyboard-bridge] Escape: exited mode"))
237 return true237 return true
238 }238 }
239 return false // Pass through to DOM if no active mode239 return false // Pass through to DOM if no active mode
@@ -247,7 +247,7 @@ func (w *WebView) AttachKeyboardBridge() {
247 handler := w.onWorkspaceNavigation247 handler := w.onWorkspaceNavigation
248 w.mu.RUnlock()248 w.mu.RUnlock()
249 if handler != nil && handler(action) {249 if handler != nil && handler(action) {
250- log.Printf("[keyboard-bridge] Navigation: %s", action)250+ logging.Debug(fmt.Sprintf("[keyboard-bridge] Navigation: %s", action))
251 return true251 return true
252 }252 }
253 return true // Block anyway to prevent scrolling253 return true // Block anyway to prevent scrolling
@@ -258,7 +258,7 @@ func (w *WebView) AttachKeyboardBridge() {
258 handler := w.onPaneModeShortcut258 handler := w.onPaneModeShortcut
259 w.mu.RUnlock()259 w.mu.RUnlock()
260 if handler != nil && handler(action) {260 if handler != nil && handler(action) {
261- log.Printf("[keyboard-bridge] Pane mode action: %s", action)261+ logging.Debug(fmt.Sprintf("[keyboard-bridge] Pane mode action: %s", action))
262 return true262 return true
263 }263 }
264 return false // Not in pane mode, pass through264 return false // Not in pane mode, pass through
@@ -266,26 +266,26 @@ func (w *WebView) AttachKeyboardBridge() {
266 266
267 // Pane mode active: block all other shortcuts267 // Pane mode active: block all other shortcuts
268 if isPaneModeActive {268 if isPaneModeActive {
269- log.Printf("[keyboard-bridge] Blocking '%s' during pane mode", shortcut)269+ logging.Debug(fmt.Sprintf("[keyboard-bridge] Blocking '%s' during pane mode", shortcut))
270 return true270 return true
271 }271 }
272 272
273 // Forward to JavaScript using proper JSON marshaling to prevent any potential injection273 // Forward to JavaScript using proper JSON marshaling to prevent any potential injection
274 shortcutJSON, err := json.Marshal(shortcut)274 shortcutJSON, err := json.Marshal(shortcut)
275 if err != nil {275 if err != nil {
276- log.Printf("[keyboard-bridge] Failed to marshal shortcut: %v", err)276+ logging.Error(fmt.Sprintf("[keyboard-bridge] Failed to marshal shortcut: %v", err))
277 return false277 return false
278 }278 }
279 script := fmt.Sprintf(`document.dispatchEvent(new CustomEvent('dumber:key',{detail:{shortcut:%s}}));`, shortcutJSON)279 script := fmt.Sprintf(`document.dispatchEvent(new CustomEvent('dumber:key',{detail:{shortcut:%s}}));`, shortcutJSON)
280 if err := w.InjectScript(script); err != nil {280 if err := w.InjectScript(script); err != nil {
281- log.Printf("[keyboard-bridge] Failed to dispatch: %v", err)281+ logging.Error(fmt.Sprintf("[keyboard-bridge] Failed to dispatch: %v", err))
282 }282 }
283 return false // Allow WebKit to handle283 return false // Allow WebKit to handle
284 }284 }
285 285
286 // Pane mode active: block ALL non-shortcut keys286 // Pane mode active: block ALL non-shortcut keys
287 if isPaneModeActive {287 if isPaneModeActive {
288- log.Printf("[keyboard-bridge] Blocking key during pane mode: keyval=%d", keyval)288+ logging.Debug(fmt.Sprintf("[keyboard-bridge] Blocking key during pane mode: keyval=%d", keyval))
289 return true // Block all other keys289 return true // Block all other keys
290 }290 }
291 291
@@ -295,5 +295,5 @@ func (w *WebView) AttachKeyboardBridge() {
295 295
296 // Attach controller to WebView296 // Attach controller to WebView
297 w.view.AddController(keyController)297 w.view.AddController(keyController)
298- log.Printf("[keyboard-bridge] EventControllerKey attached to WebView ID %d", w.id)298+ logging.Debug(fmt.Sprintf("[keyboard-bridge] EventControllerKey attached to WebView ID %d", w.id))
299 }299 }
M pkg/webkit/mouse_gestures.go +8 -7
@@ -1,8 +1,9 @@
1 package webkit1 package webkit
2 2
3 import (3 import (
4- "log"4+ "fmt"
5 5
6+ "github.com/bnema/dumber/internal/logging"
6 "github.com/diamondburned/gotk4/pkg/gtk/v4"7 "github.com/diamondburned/gotk4/pkg/gtk/v4"
7 )8 )
8 9
@@ -17,7 +18,7 @@ const (
17 // This enables hardware mouse button navigation (back/forward buttons)18 // This enables hardware mouse button navigation (back/forward buttons)
18 func (w *WebView) AttachMouseGestures() {19 func (w *WebView) AttachMouseGestures() {
19 if w.view == nil {20 if w.view == nil {
20- log.Printf("[mouse-gestures] Cannot attach: WebView is nil")21+ logging.Error(fmt.Sprintf("[mouse-gestures] Cannot attach: WebView is nil"))
21 return22 return
22 }23 }
23 24
@@ -43,18 +44,18 @@ func (w *WebView) AttachMouseGestures() {
43 switch button {44 switch button {
44 case mouseButtonBack:45 case mouseButtonBack:
45 if w.view.CanGoBack() {46 if w.view.CanGoBack() {
46- log.Printf("[mouse-gestures] Back button clicked - navigating backward")47+ logging.Debug(fmt.Sprintf("[mouse-gestures] Back button clicked - navigating backward"))
47 w.view.GoBack()48 w.view.GoBack()
48 } else {49 } else {
49- log.Printf("[mouse-gestures] Back button clicked but cannot go back")50+ logging.Debug(fmt.Sprintf("[mouse-gestures] Back button clicked but cannot go back"))
50 }51 }
51 52
52 case mouseButtonForward:53 case mouseButtonForward:
53 if w.view.CanGoForward() {54 if w.view.CanGoForward() {
54- log.Printf("[mouse-gestures] Forward button clicked - navigating forward")55+ logging.Debug(fmt.Sprintf("[mouse-gestures] Forward button clicked - navigating forward"))
55 w.view.GoForward()56 w.view.GoForward()
56 } else {57 } else {
57- log.Printf("[mouse-gestures] Forward button clicked but cannot go forward")58+ logging.Debug(fmt.Sprintf("[mouse-gestures] Forward button clicked but cannot go forward"))
58 }59 }
59 }60 }
60 })61 })
@@ -62,5 +63,5 @@ func (w *WebView) AttachMouseGestures() {
62 // Attach controller to WebView63 // Attach controller to WebView
63 w.view.AddController(gestureClick)64 w.view.AddController(gestureClick)
64 65
65- log.Printf("[mouse-gestures] Mouse gesture controller attached to WebView ID %d", w.id)66+ logging.Debug(fmt.Sprintf("[mouse-gestures] Mouse gesture controller attached to WebView ID %d", w.id))
66 }67 }
M pkg/webkit/network_session.go +7 -7
@@ -2,8 +2,8 @@ package webkit
2 2
3 import (3 import (
4 "fmt"4 "fmt"
5- "log"
6 5
6+ "github.com/bnema/dumber/internal/logging"
7 webkit "github.com/diamondburned/gotk4-webkitgtk/pkg/webkit/v6"7 webkit "github.com/diamondburned/gotk4-webkitgtk/pkg/webkit/v6"
8 )8 )
9 9
@@ -25,7 +25,7 @@ func InitPersistentSession(dataDir, cacheDir string) error {
25 // If session already exists, return success (idempotent)25 // If session already exists, return success (idempotent)
26 // This ensures we only create ONE session for the entire application26 // This ensures we only create ONE session for the entire application
27 if globalNetworkSession != nil {27 if globalNetworkSession != nil {
28- log.Printf("[webkit] Using existing persistent network session")28+ logging.Debug(fmt.Sprintf("[webkit] Using existing persistent network session"))
29 return nil29 return nil
30 }30 }
31 31
@@ -40,7 +40,7 @@ func InitPersistentSession(dataDir, cacheDir string) error {
40 // This is CRITICAL - if the session is GC'd, WebKit falls back to ephemeral storage40 // This is CRITICAL - if the session is GC'd, WebKit falls back to ephemeral storage
41 globalNetworkSession = session41 globalNetworkSession = session
42 42
43- log.Printf("[webkit] Persistent network session created: data=%s, cache=%s", dataDir, cacheDir)43+ logging.Debug(fmt.Sprintf("[webkit] Persistent network session created: data=%s, cache=%s", dataDir, cacheDir))
44 44
45 // Verify the session is not ephemeral45 // Verify the session is not ephemeral
46 if session.IsEphemeral() {46 if session.IsEphemeral() {
@@ -55,7 +55,7 @@ func InitPersistentSession(dataDir, cacheDir string) error {
55 if websiteDataManager.IsEphemeral() {55 if websiteDataManager.IsEphemeral() {
56 return fmt.Errorf("website data manager is ephemeral despite providing data directories")56 return fmt.Errorf("website data manager is ephemeral despite providing data directories")
57 }57 }
58- log.Printf("[webkit] WebsiteDataManager verified as non-ephemeral")58+ logging.Debug(fmt.Sprintf("[webkit] WebsiteDataManager verified as non-ephemeral"))
59 59
60 // Configure CookieManager for persistent cookie storage60 // Configure CookieManager for persistent cookie storage
61 // This is REQUIRED - without this, cookies are not persisted to disk61 // This is REQUIRED - without this, cookies are not persisted to disk
@@ -72,7 +72,7 @@ func InitPersistentSession(dataDir, cacheDir string) error {
72 // Set cookie accept policy to no third-party (default)72 // Set cookie accept policy to no third-party (default)
73 cookieManager.SetAcceptPolicy(webkit.CookiePolicyAcceptNoThirdParty)73 cookieManager.SetAcceptPolicy(webkit.CookiePolicyAcceptNoThirdParty)
74 74
75- log.Printf("[webkit] Cookie manager configured: storage=%s, policy=no-third-party", cookiePath)75+ logging.Debug(fmt.Sprintf("[webkit] Cookie manager configured: storage=%s, policy=no-third-party", cookiePath))
76 76
77 // Enable persistent credential storage (HTTP auth, etc.)77 // Enable persistent credential storage (HTTP auth, etc.)
78 session.SetPersistentCredentialStorageEnabled(true)78 session.SetPersistentCredentialStorageEnabled(true)
@@ -80,7 +80,7 @@ func InitPersistentSession(dataDir, cacheDir string) error {
80 // Configure TLS errors to emit signals instead of failing silently80 // Configure TLS errors to emit signals instead of failing silently
81 // This enables the load-failed-with-tls-errors signal81 // This enables the load-failed-with-tls-errors signal
82 session.SetTLSErrorsPolicy(webkit.TLSErrorsPolicyFail)82 session.SetTLSErrorsPolicy(webkit.TLSErrorsPolicyFail)
83- log.Printf("[webkit] TLS errors policy set to FAIL (signals enabled)")83+ logging.Debug(fmt.Sprintf("[webkit] TLS errors policy set to FAIL (signals enabled)"))
84 84
85 // Verify this is indeed the default session now85 // Verify this is indeed the default session now
86 defaultSession := webkit.NetworkSessionGetDefault()86 defaultSession := webkit.NetworkSessionGetDefault()
@@ -93,7 +93,7 @@ func InitPersistentSession(dataDir, cacheDir string) error {
93 return fmt.Errorf("default network session is ephemeral after creating persistent session")93 return fmt.Errorf("default network session is ephemeral after creating persistent session")
94 }94 }
95 95
96- log.Printf("[webkit] Network session verified as persistent and set as default")96+ logging.Debug(fmt.Sprintf("[webkit] Network session verified as persistent and set as default"))
97 return nil97 return nil
98 }98 }
99 99
M pkg/webkit/shortcuts.go +6 -5
@@ -1,9 +1,10 @@
1 package webkit1 package webkit
2 2
3 import (3 import (
4- "log"4+ "fmt"
5 "strings"5 "strings"
6 6
7+ "github.com/bnema/dumber/internal/logging"
7 "github.com/diamondburned/gotk4/pkg/gdk/v4"8 "github.com/diamondburned/gotk4/pkg/gdk/v4"
8 "github.com/diamondburned/gotk4/pkg/glib/v2"9 "github.com/diamondburned/gotk4/pkg/glib/v2"
9 "github.com/diamondburned/gotk4/pkg/gtk/v4"10 "github.com/diamondburned/gotk4/pkg/gtk/v4"
@@ -62,7 +63,7 @@ func (ws *WindowShortcuts) RegisterShortcut(accelerator string, handler func())
62 // Convert to GTK format (e.g., "ctrl+plus" -> "<Control>plus")63 // Convert to GTK format (e.g., "ctrl+plus" -> "<Control>plus")
63 gtkFormat := convertToGtkFormat(accelerator)64 gtkFormat := convertToGtkFormat(accelerator)
64 if gtkFormat == "" {65 if gtkFormat == "" {
65- log.Printf("[shortcuts] Invalid key format: %s", accelerator)66+ logging.Warn(fmt.Sprintf("[shortcuts] Invalid key format: %s", accelerator))
66 return67 return
67 }68 }
68 69
@@ -70,7 +71,7 @@ func (ws *WindowShortcuts) RegisterShortcut(accelerator string, handler func())
70 // ShortcutFunc signature: func(widget Widgetter, args *glib.Variant) (ok bool)71 // ShortcutFunc signature: func(widget Widgetter, args *glib.Variant) (ok bool)
71 action := gtk.NewCallbackAction(func(widget gtk.Widgetter, args *glib.Variant) bool {72 action := gtk.NewCallbackAction(func(widget gtk.Widgetter, args *glib.Variant) bool {
72 if handler != nil {73 if handler != nil {
73- log.Printf("[shortcuts] Shortcut triggered: %s", accelerator)74+ logging.Debug(fmt.Sprintf("[shortcuts] Shortcut triggered: %s", accelerator))
74 handler()75 handler()
75 }76 }
76 // Return false to allow event propagation to WebView77 // Return false to allow event propagation to WebView
@@ -82,7 +83,7 @@ func (ws *WindowShortcuts) RegisterShortcut(accelerator string, handler func())
82 // GTK4 uses gtk.ShortcutTrigger for key combinations83 // GTK4 uses gtk.ShortcutTrigger for key combinations
83 trigger := gtk.NewShortcutTriggerParseString(gtkFormat)84 trigger := gtk.NewShortcutTriggerParseString(gtkFormat)
84 if trigger == nil {85 if trigger == nil {
85- log.Printf("[shortcuts] Failed to parse trigger: %s", gtkFormat)86+ logging.Error(fmt.Sprintf("[shortcuts] Failed to parse trigger: %s", gtkFormat))
86 return87 return
87 }88 }
88 89
@@ -95,7 +96,7 @@ func (ws *WindowShortcuts) RegisterShortcut(accelerator string, handler func())
95 // Store the shortcut for potential cleanup96 // Store the shortcut for potential cleanup
96 ws.shortcuts[accelerator] = shortcut97 ws.shortcuts[accelerator] = shortcut
97 98
98- log.Printf("[shortcuts] Registered: %s -> %s", accelerator, gtkFormat)99+ logging.Debug(fmt.Sprintf("[shortcuts] Registered: %s -> %s", accelerator, gtkFormat))
99 }100 }
100 101
101 // convertToGtkFormat converts common key formats to GTK shortcut format102 // convertToGtkFormat converts common key formats to GTK shortcut format
M pkg/webkit/tls_handler.go +29 -29
@@ -5,13 +5,13 @@ import (
5 "database/sql"5 "database/sql"
6 "errors"6 "errors"
7 "fmt"7 "fmt"
8- "log"
9 "net/url"8 "net/url"
10 "strings"9 "strings"
11 "time"10 "time"
12 11
13 "github.com/bnema/dumber/internal/config"12 "github.com/bnema/dumber/internal/config"
14 "github.com/bnema/dumber/internal/db"13 "github.com/bnema/dumber/internal/db"
14+ "github.com/bnema/dumber/internal/logging"
15 "github.com/diamondburned/gotk4/pkg/gio/v2"15 "github.com/diamondburned/gotk4/pkg/gio/v2"
16 "github.com/diamondburned/gotk4/pkg/gtk/v4"16 "github.com/diamondburned/gotk4/pkg/gtk/v4"
17 )17 )
@@ -29,12 +29,12 @@ type TLSCertificateInfo struct {
29 // setupTLSErrorHandler sets up the TLS error handler for the WebView29 // setupTLSErrorHandler sets up the TLS error handler for the WebView
30 func (wv *WebView) setupTLSErrorHandler() {30 func (wv *WebView) setupTLSErrorHandler() {
31 wv.view.ConnectLoadFailedWithTLSErrors(func(failingUri string, certificate gio.TLSCertificater, errors gio.TLSCertificateFlags) bool {31 wv.view.ConnectLoadFailedWithTLSErrors(func(failingUri string, certificate gio.TLSCertificater, errors gio.TLSCertificateFlags) bool {
32- log.Printf("[tls] Load failed with TLS errors for: %s, errors: %v", failingUri, errors)32+ logging.Warn(fmt.Sprintf("[tls] Load failed with TLS errors for: %s, errors: %v", failingUri, errors))
33 33
34 // Extract hostname from URI34 // Extract hostname from URI
35 hostname := extractHostname(failingUri)35 hostname := extractHostname(failingUri)
36 if hostname == "" {36 if hostname == "" {
37- log.Printf("[tls] Failed to extract hostname from URI: %s", failingUri)37+ logging.Error(fmt.Sprintf("[tls] Failed to extract hostname from URI: %s", failingUri))
38 return false // Let WebKit handle it38 return false // Let WebKit handle it
39 }39 }
40 40
@@ -44,17 +44,17 @@ func (wv *WebView) setupTLSErrorHandler() {
44 decision, exists := checkStoredCertificateDecision(hostname)44 decision, exists := checkStoredCertificateDecision(hostname)
45 if exists {45 if exists {
46 if decision == "accepted" {46 if decision == "accepted" {
47- log.Printf("[tls] Certificate previously accepted for %s, allowing", hostname)47+ logging.Debug(fmt.Sprintf("[tls] Certificate previously accepted for %s, allowing", hostname))
48 wv.allowCertificateAndReload(certificate, hostname, failingUri)48 wv.allowCertificateAndReload(certificate, hostname, failingUri)
49 return true // Signal handled49 return true // Signal handled
50 } else {50 } else {
51- log.Printf("[tls] Certificate previously rejected for %s, blocking", hostname)51+ logging.Debug(fmt.Sprintf("[tls] Certificate previously rejected for %s, blocking", hostname))
52 return false // Let WebKit show error52 return false // Let WebKit show error
53 }53 }
54 }54 }
55 55
56 // No stored decision - show dialog to user56 // No stored decision - show dialog to user
57- log.Printf("[tls] No stored decision for %s, showing dialog", hostname)57+ logging.Debug(fmt.Sprintf("[tls] No stored decision for %s, showing dialog", hostname))
58 wv.showTLSErrorDialog(failingUri, hostname, certificate, errors)58 wv.showTLSErrorDialog(failingUri, hostname, certificate, errors)
59 59
60 return true // Signal handled - we're managing the error60 return true // Signal handled - we're managing the error
@@ -87,24 +87,24 @@ func extractHostname(uri string) string {
87 func checkStoredCertificateDecision(hostname string) (string, bool) {87 func checkStoredCertificateDecision(hostname string) (string, bool) {
88 cfg := config.Get()88 cfg := config.Get()
89 if cfg == nil {89 if cfg == nil {
90- log.Printf("[tls] Config not available, cannot check stored decisions")90+ logging.Error(fmt.Sprintf("[tls] Config not available, cannot check stored decisions"))
91 return "", false91 return "", false
92 }92 }
93 93
94 dbPath := cfg.Database.Path94 dbPath := cfg.Database.Path
95 if dbPath == "" {95 if dbPath == "" {
96- log.Printf("[tls] Database path not configured")96+ logging.Error(fmt.Sprintf("[tls] Database path not configured"))
97 return "", false97 return "", false
98 }98 }
99 99
100 database, err := sql.Open("sqlite3", dbPath)100 database, err := sql.Open("sqlite3", dbPath)
101 if err != nil {101 if err != nil {
102- log.Printf("[tls] Failed to open database: %v", err)102+ logging.Error(fmt.Sprintf("[tls] Failed to open database: %v", err))
103 return "", false103 return "", false
104 }104 }
105 defer func() {105 defer func() {
106 if err := database.Close(); err != nil {106 if err := database.Close(); err != nil {
107- log.Printf("[tls] warning: failed to close database: %v", err)107+ logging.Warn(fmt.Sprintf("[tls] warning: failed to close database: %v", err))
108 }108 }
109 }()109 }()
110 110
@@ -117,7 +117,7 @@ func checkStoredCertificateDecision(hostname string) (string, bool) {
117 if errors.Is(err, sql.ErrNoRows) {117 if errors.Is(err, sql.ErrNoRows) {
118 return "", false118 return "", false
119 }119 }
120- log.Printf("[tls] Error checking certificate validation: %v", err)120+ logging.Error(fmt.Sprintf("[tls] Error checking certificate validation: %v", err))
121 return "", false121 return "", false
122 }122 }
123 123
@@ -142,23 +142,23 @@ func storeCertificateDecision(hostname, decision string, expiresAt sql.NullTime)
142 }142 }
143 defer func() {143 defer func() {
144 if err := database.Close(); err != nil {144 if err := database.Close(); err != nil {
145- log.Printf("[tls] warning: failed to close database: %v", err)145+ logging.Warn(fmt.Sprintf("[tls] warning: failed to close database: %v", err))
146 }146 }
147 }()147 }()
148 148
149 queries := db.New(database)149 queries := db.New(database)
150 ctx := context.Background()150 ctx := context.Background()
151 151
152- log.Printf("[tls] Saving certificate decision to database: hostname=%s, decision=%s, expiresAt=%v",152+ logging.Debug(fmt.Sprintf("[tls] Saving certificate decision to database: hostname=%s, decision=%s, expiresAt=%v",
153- hostname, decision, expiresAt)153+ hostname, decision, expiresAt))
154 154
155 // Use empty string for cert hash since we're doing hostname-only matching155 // Use empty string for cert hash since we're doing hostname-only matching
156 if err := queries.StoreCertificateValidation(ctx, hostname, "", decision, expiresAt); err != nil {156 if err := queries.StoreCertificateValidation(ctx, hostname, "", decision, expiresAt); err != nil {
157- log.Printf("[tls] Failed to save certificate decision to database: %v", err)157+ logging.Error(fmt.Sprintf("[tls] Failed to save certificate decision to database: %v", err))
158 return err158 return err
159 }159 }
160 160
161- log.Printf("[tls] Certificate decision saved successfully in database")161+ logging.Debug(fmt.Sprintf("[tls] Certificate decision saved successfully in database"))
162 return nil162 return nil
163 }163 }
164 164
@@ -222,7 +222,7 @@ func (wv *WebView) showTLSErrorDialog(failingUri, hostname string, certificate g
222 // Create AlertDialog using CGO wrapper222 // Create AlertDialog using CGO wrapper
223 dialog := newAlertDialog(fmt.Sprintf("Certificate Error for %s", hostname))223 dialog := newAlertDialog(fmt.Sprintf("Certificate Error for %s", hostname))
224 if dialog == nil {224 if dialog == nil {
225- log.Printf("[tls] Failed to create alert dialog")225+ logging.Error(fmt.Sprintf("[tls] Failed to create alert dialog"))
226 return226 return
227 }227 }
228 228
@@ -245,32 +245,32 @@ func (wv *WebView) showTLSErrorDialog(failingUri, hostname string, certificate g
245 dialog.Choose(context.Background(), parentWindow, func(result gio.AsyncResulter) {245 dialog.Choose(context.Background(), parentWindow, func(result gio.AsyncResulter) {
246 buttonIndex, err := dialog.ChooseFinish(result)246 buttonIndex, err := dialog.ChooseFinish(result)
247 if err != nil {247 if err != nil {
248- log.Printf("[tls] Dialog error: %v", err)248+ logging.Error(fmt.Sprintf("[tls] Dialog error: %v", err))
249 return249 return
250 }250 }
251 251
252- log.Printf("[tls] User chose button %d for %s", buttonIndex, hostname)252+ logging.Debug(fmt.Sprintf("[tls] User chose button %d for %s", buttonIndex, hostname))
253 253
254 switch buttonIndex {254 switch buttonIndex {
255 case 0: // Go Back255 case 0: // Go Back
256- log.Printf("[tls] User chose to go back")256+ logging.Debug(fmt.Sprintf("[tls] User chose to go back"))
257 // Do nothing - let the load fail257 // Do nothing - let the load fail
258 258
259 case 1: // Proceed Once259 case 1: // Proceed Once
260- log.Printf("[tls] User chose to proceed once")260+ logging.Debug(fmt.Sprintf("[tls] User chose to proceed once"))
261 wv.allowCertificateAndReload(certificate, hostname, failingUri)261 wv.allowCertificateAndReload(certificate, hostname, failingUri)
262 262
263 case 2: // Always Accept This Site263 case 2: // Always Accept This Site
264- log.Printf("[tls] User chose to always accept for %s", hostname)264+ logging.Debug(fmt.Sprintf("[tls] User chose to always accept for %s", hostname))
265 // Store decision in database (expires in 30 days)265 // Store decision in database (expires in 30 days)
266 expiresAt := sql.NullTime{266 expiresAt := sql.NullTime{
267 Time: time.Now().Add(30 * 24 * time.Hour),267 Time: time.Now().Add(30 * 24 * time.Hour),
268 Valid: true,268 Valid: true,
269 }269 }
270 if err := storeCertificateDecision(hostname, "accepted", expiresAt); err != nil {270 if err := storeCertificateDecision(hostname, "accepted", expiresAt); err != nil {
271- log.Printf("[tls] Failed to store certificate decision: %v", err)271+ logging.Error(fmt.Sprintf("[tls] Failed to store certificate decision: %v", err))
272 } else {272 } else {
273- log.Printf("[tls] Stored certificate acceptance for %s (expires in 30 days)", hostname)273+ logging.Debug(fmt.Sprintf("[tls] Stored certificate acceptance for %s (expires in 30 days)", hostname))
274 }274 }
275 wv.allowCertificateAndReload(certificate, hostname, failingUri)275 wv.allowCertificateAndReload(certificate, hostname, failingUri)
276 }276 }
@@ -284,17 +284,17 @@ func (wv *WebView) allowCertificateAndReload(certificate gio.TLSCertificater, ho
284 // Get the network session284 // Get the network session
285 session := wv.view.NetworkSession()285 session := wv.view.NetworkSession()
286 if session == nil {286 if session == nil {
287- log.Printf("[tls] Failed to get network session")287+ logging.Error(fmt.Sprintf("[tls] Failed to get network session"))
288 return288 return
289 }289 }
290 290
291 // Allow the certificate for this host291 // Allow the certificate for this host
292 session.AllowTLSCertificateForHost(certificate, hostname)292 session.AllowTLSCertificateForHost(certificate, hostname)
293- log.Printf("[tls] Certificate exception added for host: %s", hostname)293+ logging.Debug(fmt.Sprintf("[tls] Certificate exception added for host: %s", hostname))
294 294
295 // Reload the page295 // Reload the page
296 wv.view.LoadURI(uri)296 wv.view.LoadURI(uri)
297- log.Printf("[tls] Reloading %s with certificate exception", uri)297+ logging.Debug(fmt.Sprintf("[tls] Reloading %s with certificate exception", uri))
298 })298 })
299 }299 }
300 300
@@ -317,7 +317,7 @@ func CleanupExpiredCertificateValidations() error {
317 }317 }
318 defer func() {318 defer func() {
319 if err := database.Close(); err != nil {319 if err := database.Close(); err != nil {
320- log.Printf("[tls] warning: failed to close database: %v", err)320+ logging.Warn(fmt.Sprintf("[tls] warning: failed to close database: %v", err))
321 }321 }
322 }()322 }()
323 323
@@ -328,6 +328,6 @@ func CleanupExpiredCertificateValidations() error {
328 return fmt.Errorf("failed to delete expired validations: %w", err)328 return fmt.Errorf("failed to delete expired validations: %w", err)
329 }329 }
330 330
331- log.Printf("[tls] Cleaned up expired certificate validations")331+ logging.Debug(fmt.Sprintf("[tls] Cleaned up expired certificate validations"))
332 return nil332 return nil
333 }333 }
M pkg/webkit/touchpad_gestures.go +10 -9
@@ -1,9 +1,10 @@
1 package webkit1 package webkit
2 2
3 import (3 import (
4- "log"4+ "fmt"
5 "math"5 "math"
6 6
7+ "github.com/bnema/dumber/internal/logging"
7 "github.com/diamondburned/gotk4/pkg/gtk/v4"8 "github.com/diamondburned/gotk4/pkg/gtk/v4"
8 )9 )
9 10
@@ -17,7 +18,7 @@ const (
17 // This enables two-finger swipe navigation (left/right for back/forward)18 // This enables two-finger swipe navigation (left/right for back/forward)
18 func (w *WebView) AttachTouchpadGestures() {19 func (w *WebView) AttachTouchpadGestures() {
19 if w.view == nil {20 if w.view == nil {
20- log.Printf("[touchpad-gestures] Cannot attach: WebView is nil")21+ logging.Error(fmt.Sprintf("[touchpad-gestures] Cannot attach: WebView is nil"))
21 return22 return
22 }23 }
23 24
@@ -40,13 +41,13 @@ func (w *WebView) AttachTouchpadGestures() {
40 41
41 // Require horizontal velocity to be dominant42 // Require horizontal velocity to be dominant
42 if absVelocityY > absVelocityX {43 if absVelocityY > absVelocityX {
43- log.Printf("[touchpad-gestures] Swipe is more vertical (X:%.1f Y:%.1f), ignoring", velocityX, velocityY)44+ logging.Debug(fmt.Sprintf("[touchpad-gestures] Swipe is more vertical (X:%.1f Y:%.1f), ignoring", velocityX, velocityY))
44 return45 return
45 }46 }
46 47
47 // Check if swipe velocity meets threshold48 // Check if swipe velocity meets threshold
48 if absVelocityX < swipeVelocityThreshold {49 if absVelocityX < swipeVelocityThreshold {
49- log.Printf("[touchpad-gestures] Swipe velocity too low (%.1f < %.1f), ignoring", absVelocityX, swipeVelocityThreshold)50+ logging.Debug(fmt.Sprintf("[touchpad-gestures] Swipe velocity too low (%.1f < %.1f), ignoring", absVelocityX, swipeVelocityThreshold))
50 return51 return
51 }52 }
52 53
@@ -54,23 +55,23 @@ func (w *WebView) AttachTouchpadGestures() {
54 if velocityX > 0 {55 if velocityX > 0 {
55 // Swipe right (positive velocity) - go back56 // Swipe right (positive velocity) - go back
56 if w.view.CanGoBack() {57 if w.view.CanGoBack() {
57- log.Printf("[touchpad-gestures] Swipe right detected (velocity: %.1f) - navigating backward", velocityX)58+ logging.Debug(fmt.Sprintf("[touchpad-gestures] Swipe right detected (velocity: %.1f) - navigating backward", velocityX))
58 w.view.GoBack()59 w.view.GoBack()
59 } else {60 } else {
60- log.Printf("[touchpad-gestures] Swipe right detected but cannot go back")61+ logging.Debug(fmt.Sprintf("[touchpad-gestures] Swipe right detected but cannot go back"))
61 }62 }
62 } else {63 } else {
63 // Swipe left (negative velocity) - go forward64 // Swipe left (negative velocity) - go forward
64 if w.view.CanGoForward() {65 if w.view.CanGoForward() {
65- log.Printf("[touchpad-gestures] Swipe left detected (velocity: %.1f) - navigating forward", velocityX)66+ logging.Debug(fmt.Sprintf("[touchpad-gestures] Swipe left detected (velocity: %.1f) - navigating forward", velocityX))
66 w.view.GoForward()67 w.view.GoForward()
67 } else {68 } else {
68- log.Printf("[touchpad-gestures] Swipe left detected but cannot go forward")69+ logging.Debug(fmt.Sprintf("[touchpad-gestures] Swipe left detected but cannot go forward"))
69 }70 }
70 }71 }
71 })72 })
72 73
73 // Attach controller to WebView74 // Attach controller to WebView
74 w.view.AddController(gestureSwipe)75 w.view.AddController(gestureSwipe)
75- log.Printf("[touchpad-gestures] Touchpad gesture controller attached to WebView ID %d", w.id)76+ logging.Debug(fmt.Sprintf("[touchpad-gestures] Touchpad gesture controller attached to WebView ID %d", w.id))
76 }77 }
M pkg/webkit/types.go +7 -7
@@ -70,13 +70,13 @@ type Config struct {
70 // GetDefaultConfig returns a Config with sensible defaults70 // GetDefaultConfig returns a Config with sensible defaults
71 func GetDefaultConfig() *Config {71 func GetDefaultConfig() *Config {
72 return &Config{72 return &Config{
73- UserAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15", // WebKitGTK default (same as Epiphany)73+ UserAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15", // WebKitGTK default (same as Epiphany)
74- EnableJavaScript: true,74+ EnableJavaScript: true,
75- EnableWebGL: true,75+ EnableWebGL: true,
76- EnableMediaStream: true,76+ EnableMediaStream: true,
77- HardwareAcceleration: true,77+ HardwareAcceleration: true,
78- DefaultFontSize: 16,78+ DefaultFontSize: 16,
79- MinimumFontSize: 8,79+ MinimumFontSize: 8,
80 EnablePageCache: true, // Instant back/forward navigation80 EnablePageCache: true, // Instant back/forward navigation
81 EnableSmoothScrolling: true, // Better UX81 EnableSmoothScrolling: true, // Better UX
82 CreateWindow: true, // Default to standalone window82 CreateWindow: true, // Default to standalone window
M pkg/webkit/uri_scheme.go +3 -3
@@ -2,8 +2,8 @@ package webkit
2 2
3 import (3 import (
4 "fmt"4 "fmt"
5- "log"
6 5
6+ "github.com/bnema/dumber/internal/logging"
7 webkit "github.com/diamondburned/gotk4-webkitgtk/pkg/webkit/v6"7 webkit "github.com/diamondburned/gotk4-webkitgtk/pkg/webkit/v6"
8 )8 )
9 9
@@ -25,7 +25,7 @@ var (
25 // In WebKitGTK 6.0, this must be called BEFORE creating any WebViews25 // In WebKitGTK 6.0, this must be called BEFORE creating any WebViews
26 // The actual registration happens in InitPersistentSession26 // The actual registration happens in InitPersistentSession
27 func RegisterURIScheme(scheme string, callback URISchemeRequestCallback) {27 func RegisterURIScheme(scheme string, callback URISchemeRequestCallback) {
28- log.Printf("[webkit] Registering URI scheme handler for: %s", scheme)28+ logging.Debug(fmt.Sprintf("[webkit] Registering URI scheme handler for: %s", scheme))
29 pendingURISchemeHandlers = append(pendingURISchemeHandlers, &URISchemeHandler{29 pendingURISchemeHandlers = append(pendingURISchemeHandlers, &URISchemeHandler{
30 scheme: scheme,30 scheme: scheme,
31 callback: callback,31 callback: callback,
@@ -45,7 +45,7 @@ func ApplyURISchemeHandlers(view *webkit.WebView) error {
45 }45 }
46 46
47 for _, handler := range pendingURISchemeHandlers {47 for _, handler := range pendingURISchemeHandlers {
48- log.Printf("[webkit] Registering URI scheme on WebContext: %s", handler.scheme)48+ logging.Debug(fmt.Sprintf("[webkit] Registering URI scheme on WebContext: %s", handler.scheme))
49 49
50 // Capture handler in closure50 // Capture handler in closure
51 callback := handler.callback51 callback := handler.callback
M pkg/webkit/user_content.go +14 -14
@@ -2,10 +2,10 @@ package webkit
2 2
3 import (3 import (
4 "fmt"4 "fmt"
5- "log"
6 5
7 "github.com/bnema/dumber/assets"6 "github.com/bnema/dumber/assets"
8 "github.com/bnema/dumber/internal/config"7 "github.com/bnema/dumber/internal/config"
8+ "github.com/bnema/dumber/internal/logging"
9 webkit "github.com/diamondburned/gotk4-webkitgtk/pkg/webkit/v6"9 webkit "github.com/diamondburned/gotk4-webkitgtk/pkg/webkit/v6"
10 )10 )
11 11
@@ -26,7 +26,7 @@ func SetupUserContentManager(view *webkit.WebView, appearanceConfigJSON string,
26 // Get the UserContentManager from the WebView26 // Get the UserContentManager from the WebView
27 ucm := view.UserContentManager()27 ucm := view.UserContentManager()
28 if ucm == nil {28 if ucm == nil {
29- log.Printf("[webkit] UserContentManager is nil, skipping script injection")29+ logging.Warn(fmt.Sprintf("[webkit] UserContentManager is nil, skipping script injection"))
30 return nil30 return nil
31 }31 }
32 32
@@ -56,7 +56,7 @@ func SetupUserContentManager(view *webkit.WebView, appearanceConfigJSON string,
56 nil,56 nil,
57 nil,57 nil,
58 ))58 ))
59- log.Printf("[webkit] Injected webview ID script for ID: %d (main + isolated world)", webviewID)59+ logging.Debug(fmt.Sprintf("[webkit] Injected webview ID script for ID: %d (main + isolated world)", webviewID))
60 60
61 // Inject GTK theme detection in BOTH worlds61 // Inject GTK theme detection in BOTH worlds
62 // The color-scheme.ts expects window.__dumber_gtk_prefers_dark to be set62 // The color-scheme.ts expects window.__dumber_gtk_prefers_dark to be set
@@ -66,14 +66,14 @@ func SetupUserContentManager(view *webkit.WebView, appearanceConfigJSON string,
66 switch cfg.Appearance.ColorScheme {66 switch cfg.Appearance.ColorScheme {
67 case "prefer-dark":67 case "prefer-dark":
68 prefersDark = true68 prefersDark = true
69- log.Printf("[webkit] Using config-forced dark theme (color_scheme: prefer-dark)")69+ logging.Debug(fmt.Sprintf("[webkit] Using config-forced dark theme (color_scheme: prefer-dark)"))
70 case "prefer-light":70 case "prefer-light":
71 prefersDark = false71 prefersDark = false
72- log.Printf("[webkit] Using config-forced light theme (color_scheme: prefer-light)")72+ logging.Debug(fmt.Sprintf("[webkit] Using config-forced light theme (color_scheme: prefer-light)"))
73 default:73 default:
74 // "default" or empty - follow system GTK preference74 // "default" or empty - follow system GTK preference
75 prefersDark = PrefersDarkTheme()75 prefersDark = PrefersDarkTheme()
76- log.Printf("[webkit] Using system GTK theme preference (color_scheme: %s)", cfg.Appearance.ColorScheme)76+ logging.Debug(fmt.Sprintf("[webkit] Using system GTK theme preference (color_scheme: %s)", cfg.Appearance.ColorScheme))
77 }77 }
78 78
79 gtkThemeScript := fmt.Sprintf(`window.__dumber_gtk_prefers_dark = %t;`, prefersDark)79 gtkThemeScript := fmt.Sprintf(`window.__dumber_gtk_prefers_dark = %t;`, prefersDark)
@@ -96,7 +96,7 @@ func SetupUserContentManager(view *webkit.WebView, appearanceConfigJSON string,
96 nil,96 nil,
97 nil,97 nil,
98 ))98 ))
99- log.Printf("[webkit] Injected theme preference: prefersDark=%t (main + isolated world)", prefersDark)99+ logging.Debug(fmt.Sprintf("[webkit] Injected theme preference: prefersDark=%t (main + isolated world)", prefersDark))
100 100
101 // Inject palette config in BOTH worlds101 // Inject palette config in BOTH worlds
102 // The GUI expects window.__dumber_palette = { "light": {...}, "dark": {...} }102 // The GUI expects window.__dumber_palette = { "light": {...}, "dark": {...} }
@@ -121,7 +121,7 @@ func SetupUserContentManager(view *webkit.WebView, appearanceConfigJSON string,
121 nil,121 nil,
122 nil,122 nil,
123 ))123 ))
124- log.Printf("[webkit] Injected palette config (%d bytes, main + isolated world)", len(paletteScript))124+ logging.Debug(fmt.Sprintf("[webkit] Injected palette config (%d bytes, main + isolated world)", len(paletteScript)))
125 }125 }
126 126
127 // Inject color-scheme script in BOTH worlds127 // Inject color-scheme script in BOTH worlds
@@ -146,7 +146,7 @@ func SetupUserContentManager(view *webkit.WebView, appearanceConfigJSON string,
146 nil,146 nil,
147 nil,147 nil,
148 ))148 ))
149- log.Printf("[webkit] Injected color-scheme script (%d bytes, main + isolated world)", len(assets.ColorSchemeScript))149+ logging.Debug(fmt.Sprintf("[webkit] Injected color-scheme script (%d bytes, main + isolated world)", len(assets.ColorSchemeScript)))
150 }150 }
151 151
152 // Inject main-world script in MAIN world152 // Inject main-world script in MAIN world
@@ -160,7 +160,7 @@ func SetupUserContentManager(view *webkit.WebView, appearanceConfigJSON string,
160 nil,160 nil,
161 nil,161 nil,
162 ))162 ))
163- log.Printf("[webkit] Injected main-world script (%d bytes, main world)", len(assets.MainWorldScript))163+ logging.Debug(fmt.Sprintf("[webkit] Injected main-world script (%d bytes, main world)", len(assets.MainWorldScript)))
164 }164 }
165 165
166 // Inject GUI controls script in ISOLATED world at document-start166 // Inject GUI controls script in ISOLATED world at document-start
@@ -175,7 +175,7 @@ func SetupUserContentManager(view *webkit.WebView, appearanceConfigJSON string,
175 nil,175 nil,
176 nil,176 nil,
177 ))177 ))
178- log.Printf("[webkit] Injected GUI controls script (%d bytes, isolated world, document-start)", len(assets.GUIScript))178+ logging.Debug(fmt.Sprintf("[webkit] Injected GUI controls script (%d bytes, isolated world, document-start)", len(assets.GUIScript)))
179 }179 }
180 180
181 // Inject component CSS styles as a JavaScript variable in ISOLATED world at document-start181 // Inject component CSS styles as a JavaScript variable in ISOLATED world at document-start
@@ -194,16 +194,16 @@ func SetupUserContentManager(view *webkit.WebView, appearanceConfigJSON string,
194 nil,194 nil,
195 nil,195 nil,
196 ))196 ))
197- log.Printf("[webkit] Injected component styles string (%d bytes, isolated world, document-start)", len(assets.ComponentStyles))197+ logging.Debug(fmt.Sprintf("[webkit] Injected component styles string (%d bytes, isolated world, document-start)", len(assets.ComponentStyles)))
198 }198 }
199 199
200 // Register script message handler "dumber" in the MAIN world200 // Register script message handler "dumber" in the MAIN world
201 // Isolated world GUI scripts dispatch CustomEvents to main world, which forwards to this handler201 // Isolated world GUI scripts dispatch CustomEvents to main world, which forwards to this handler
202 // This architecture is required because webkit.messageHandlers is only available in main world202 // This architecture is required because webkit.messageHandlers is only available in main world
203 if !ucm.RegisterScriptMessageHandler("dumber", "") {203 if !ucm.RegisterScriptMessageHandler("dumber", "") {
204- log.Printf("[webkit] Warning: failed to register 'dumber' script message handler in main world")204+ logging.Warn(fmt.Sprintf("[webkit] Warning: failed to register 'dumber' script message handler in main world"))
205 } else {205 } else {
206- log.Printf("[webkit] Registered 'dumber' script message handler in main world (default)")206+ logging.Debug(fmt.Sprintf("[webkit] Registered 'dumber' script message handler in main world (default)"))
207 }207 }
208 208
209 return nil209 return nil
M pkg/webkit/webview.go +21 -21
@@ -3,11 +3,11 @@ package webkit
3 import (3 import (
4 "encoding/json"4 "encoding/json"
5 "fmt"5 "fmt"
6- "log"
7 "strconv"6 "strconv"
8 "sync"7 "sync"
9 "sync/atomic"8 "sync/atomic"
10 9
10+ "github.com/bnema/dumber/internal/logging"
11 webkit "github.com/diamondburned/gotk4-webkitgtk/pkg/webkit/v6"11 webkit "github.com/diamondburned/gotk4-webkitgtk/pkg/webkit/v6"
12 "github.com/diamondburned/gotk4/pkg/gdk/v4"12 "github.com/diamondburned/gotk4/pkg/gdk/v4"
13 "github.com/diamondburned/gotk4/pkg/gtk/v4"13 "github.com/diamondburned/gotk4/pkg/gtk/v4"
@@ -61,7 +61,7 @@ func NewWebView(cfg *Config) (*WebView, error) {
61 61
62 // Generate unique ID immediately62 // Generate unique ID immediately
63 id := atomic.AddUint64(&viewIDCounter, 1)63 id := atomic.AddUint64(&viewIDCounter, 1)
64- log.Printf("[webkit] Generated WebView ID: %d (CreateWindow=%v)", id, cfg.CreateWindow)64+ logging.Debug(fmt.Sprintf("[webkit] Generated WebView ID: %d (CreateWindow=%v)", id, cfg.CreateWindow))
65 65
66 InitMainThread()66 InitMainThread()
67 67
@@ -166,9 +166,9 @@ func NewWebView(cfg *Config) (*WebView, error) {
166 wv.window = window166 wv.window = window
167 // Add container as child of window167 // Add container as child of window
168 window.SetChild(container)168 window.SetChild(container)
169- log.Printf("[webkit] Created standalone window for WebView ID %d", id)169+ logging.Debug(fmt.Sprintf("[webkit] Created standalone window for WebView ID %d", id))
170 } else {170 } else {
171- log.Printf("[webkit] Created WebView ID %d without window (for workspace embedding)", id)171+ logging.Debug(fmt.Sprintf("[webkit] Created WebView ID %d without window (for workspace embedding)", id))
172 }172 }
173 173
174 // Register in global registry174 // Register in global registry
@@ -279,22 +279,22 @@ func (w *WebView) setupEventHandlers() {
279 279
280 // Create signal - for popup lifecycle management280 // Create signal - for popup lifecycle management
281 w.view.ConnectCreate(func(navigationAction *webkit.NavigationAction) gtk.Widgetter {281 w.view.ConnectCreate(func(navigationAction *webkit.NavigationAction) gtk.Widgetter {
282- log.Printf("[webkit] ConnectCreate callback fired for parent WebView ID=%d", w.id)282+ logging.Debug(fmt.Sprintf("[webkit] ConnectCreate callback fired for parent WebView ID=%d", w.id))
283 if w.onPopupCreate != nil {283 if w.onPopupCreate != nil {
284- log.Printf("[webkit] Calling onPopupCreate handler")284+ logging.Debug(fmt.Sprintf("[webkit] Calling onPopupCreate handler"))
285 newWebView := w.onPopupCreate(navigationAction)285 newWebView := w.onPopupCreate(navigationAction)
286 if newWebView != nil {286 if newWebView != nil {
287- log.Printf("[webkit] onPopupCreate returned WebView wrapper ID=%d", newWebView.id)287+ logging.Debug(fmt.Sprintf("[webkit] onPopupCreate returned WebView wrapper ID=%d", newWebView.id))
288- log.Printf("[webkit] Extracting underlying gotk4 WebView from wrapper")288+ logging.Debug(fmt.Sprintf("[webkit] Extracting underlying gotk4 WebView from wrapper"))
289 underlyingView := newWebView.view289 underlyingView := newWebView.view
290- log.Printf("[webkit] Extracted gotk4 WebView=%p, about to return to WebKit", underlyingView)290+ logging.Debug(fmt.Sprintf("[webkit] Extracted gotk4 WebView=%p, about to return to WebKit", underlyingView))
291 // Return the underlying WebView widget to WebKit291 // Return the underlying WebView widget to WebKit
292 return underlyingView292 return underlyingView
293 }293 }
294- log.Printf("[webkit] onPopupCreate returned nil, blocking popup")294+ logging.Debug(fmt.Sprintf("[webkit] onPopupCreate returned nil, blocking popup"))
295 }295 }
296 // Return nil to cancel popup creation296 // Return nil to cancel popup creation
297- log.Printf("[webkit] No onPopupCreate handler, blocking popup")297+ logging.Debug(fmt.Sprintf("[webkit] No onPopupCreate handler, blocking popup"))
298 return nil298 return nil
299 })299 })
300 300
@@ -379,7 +379,7 @@ func (w *WebView) setupNavigationPolicyHandler() {
379 if isCtrlClick {379 if isCtrlClick {
380 clickType = "Ctrl+click"380 clickType = "Ctrl+click"
381 }381 }
382- log.Printf("[navigation-policy] Detected %s on link: %s", clickType, linkURL)382+ logging.Debug(fmt.Sprintf("[navigation-policy] Detected %s on link: %s", clickType, linkURL))
383 383
384 // Call the registered handler384 // Call the registered handler
385 w.mu.RLock()385 w.mu.RLock()
@@ -389,7 +389,7 @@ func (w *WebView) setupNavigationPolicyHandler() {
389 if handler != nil {389 if handler != nil {
390 handled := handler(linkURL)390 handled := handler(linkURL)
391 if handled {391 if handled {
392- log.Printf("[navigation-policy] %s handled by workspace, blocking navigation", clickType)392+ logging.Debug(fmt.Sprintf("[navigation-policy] %s handled by workspace, blocking navigation", clickType))
393 // Block the navigation by calling Ignore()393 // Block the navigation by calling Ignore()
394 navDecision.Ignore()394 navDecision.Ignore()
395 return true // Prevent default behavior395 return true // Prevent default behavior
@@ -399,7 +399,7 @@ func (w *WebView) setupNavigationPolicyHandler() {
399 return false // Let WebKit handle if not handled399 return false // Let WebKit handle if not handled
400 })400 })
401 401
402- log.Printf("[webkit] Navigation policy handler attached to WebView ID %d", w.id)402+ logging.Debug(fmt.Sprintf("[webkit] Navigation policy handler attached to WebView ID %d", w.id))
403 }403 }
404 404
405 // LoadURL loads the given URL in the WebView405 // LoadURL loads the given URL in the WebView
@@ -800,7 +800,7 @@ func (w *WebView) DispatchCustomEvent(eventName string, data interface{}) error
800 return fmt.Errorf("failed to marshal event data: %w", err)800 return fmt.Errorf("failed to marshal event data: %w", err)
801 }801 }
802 802
803- log.Printf("[webkit] Dispatching event '%s' to WebView ID %d with data: %s", eventName, w.id, string(jsonData))803+ logging.Debug(fmt.Sprintf("[webkit] Dispatching event '%s' to WebView ID %d with data: %s", eventName, w.id, string(jsonData)))
804 804
805 // Use document.dispatchEvent (not window) so events cross JavaScript world boundaries805 // Use document.dispatchEvent (not window) so events cross JavaScript world boundaries
806 // The GUI scripts run in an isolated world but share the same Document object806 // The GUI scripts run in an isolated world but share the same Document object
@@ -925,7 +925,7 @@ func (w *WebView) UpdateContentFilters(rules string) error {
925 925
926 // Note: Content filtering is handled via InitializeContentBlocking()926 // Note: Content filtering is handled via InitializeContentBlocking()
927 // This method is kept for backward compatibility but is deprecated927 // This method is kept for backward compatibility but is deprecated
928- log.Printf("[webkit] UpdateContentFilters called but is deprecated - use InitializeContentBlocking instead")928+ logging.Warn(fmt.Sprintf("[webkit] UpdateContentFilters called but is deprecated - use InitializeContentBlocking instead"))
929 return nil929 return nil
930 }930 }
931 931
@@ -979,21 +979,21 @@ func (w *WebView) setupFaviconHandlers() {
979 // Get the NetworkSession from the WebView979 // Get the NetworkSession from the WebView
980 session := w.view.NetworkSession()980 session := w.view.NetworkSession()
981 if session == nil {981 if session == nil {
982- log.Printf("[webkit] Warning: No NetworkSession available for favicon handling")982+ logging.Warn(fmt.Sprintf("[webkit] Warning: No NetworkSession available for favicon handling"))
983 return983 return
984 }984 }
985 985
986 // Get the WebsiteDataManager from the NetworkSession986 // Get the WebsiteDataManager from the NetworkSession
987 dataManager := session.WebsiteDataManager()987 dataManager := session.WebsiteDataManager()
988 if dataManager == nil {988 if dataManager == nil {
989- log.Printf("[webkit] Warning: No WebsiteDataManager available for favicon handling")989+ logging.Warn(fmt.Sprintf("[webkit] Warning: No WebsiteDataManager available for favicon handling"))
990 return990 return
991 }991 }
992 992
993 // Enable favicons if not already enabled993 // Enable favicons if not already enabled
994 if !dataManager.FaviconsEnabled() {994 if !dataManager.FaviconsEnabled() {
995 dataManager.SetFaviconsEnabled(true)995 dataManager.SetFaviconsEnabled(true)
996- log.Printf("[webkit] Enabled favicons for WebView ID %d", w.id)996+ logging.Debug(fmt.Sprintf("[webkit] Enabled favicons for WebView ID %d", w.id))
997 }997 }
998 998
999 }999 }
@@ -1016,7 +1016,7 @@ func WrapBareWebView(bareView *webkit.WebView) *WebView {
1016 1016
1017 // Generate unique ID1017 // Generate unique ID
1018 id := atomic.AddUint64(&viewIDCounter, 1)1018 id := atomic.AddUint64(&viewIDCounter, 1)
1019- log.Printf("[webkit] Created minimal wrapper for bare WebView (ID: %d)", id)1019+ logging.Debug(fmt.Sprintf("[webkit] Created minimal wrapper for bare WebView (ID: %d)", id))
1020 1020
1021 // Create minimal wrapper - NO initialization yet1021 // Create minimal wrapper - NO initialization yet
1022 wv := &WebView{1022 wv := &WebView{
@@ -1093,7 +1093,7 @@ func (w *WebView) InitializeFromBare(cfg *Config) error {
1093 // Attach touchpad gesture controls1093 // Attach touchpad gesture controls
1094 w.AttachTouchpadGestures()1094 w.AttachTouchpadGestures()
1095 1095
1096- log.Printf("[webkit] Completed initialization for WebView ID %d", w.id)1096+ logging.Debug(fmt.Sprintf("[webkit] Completed initialization for WebView ID %d", w.id))
1097 1097
1098 return nil1098 return nil
1099 }1099 }
M pkg/webkit/webview_related.go +18 -17
@@ -19,10 +19,11 @@ static inline WebKitWebView* create_related_web_view(WebKitWebView* parent) {
19 */19 */
20 import "C"20 import "C"
21 import (21 import (
22- "log"22+ "fmt"
23 "runtime"23 "runtime"
24 "unsafe"24 "unsafe"
25 25
26+ "github.com/bnema/dumber/internal/logging"
26 webkit "github.com/diamondburned/gotk4-webkitgtk/pkg/webkit/v6"27 webkit "github.com/diamondburned/gotk4-webkitgtk/pkg/webkit/v6"
27 coreglib "github.com/diamondburned/gotk4/pkg/core/glib"28 coreglib "github.com/diamondburned/gotk4/pkg/core/glib"
28 "github.com/diamondburned/gotk4/pkg/glib/v2"29 "github.com/diamondburned/gotk4/pkg/glib/v2"
@@ -37,39 +38,39 @@ import (
37 // we manually construct the WebView struct hierarchy using CGO.38 // we manually construct the WebView struct hierarchy using CGO.
38 func NewBareRelatedWebView(parentView *webkit.WebView) *webkit.WebView {39 func NewBareRelatedWebView(parentView *webkit.WebView) *webkit.WebView {
39 if parentView == nil {40 if parentView == nil {
40- log.Printf("[webkit] NewBareRelatedWebView: parent is nil, creating unrelated WebView")41+ logging.Warn(fmt.Sprintf("[webkit] NewBareRelatedWebView: parent is nil, creating unrelated WebView"))
41 return webkit.NewWebView()42 return webkit.NewWebView()
42 }43 }
43 44
44 // Get the native C pointer from the gotk4 WebView45 // Get the native C pointer from the gotk4 WebView
45 parentObj := glib.BaseObject(parentView)46 parentObj := glib.BaseObject(parentView)
46 if parentObj == nil {47 if parentObj == nil {
47- log.Printf("[webkit] NewBareRelatedWebView: failed to get parent object, creating unrelated WebView")48+ logging.Error(fmt.Sprintf("[webkit] NewBareRelatedWebView: failed to get parent object, creating unrelated WebView"))
48 return webkit.NewWebView()49 return webkit.NewWebView()
49 }50 }
50 parentNative := (*C.WebKitWebView)(unsafe.Pointer(parentObj.Native()))51 parentNative := (*C.WebKitWebView)(unsafe.Pointer(parentObj.Native()))
51 if parentNative == nil {52 if parentNative == nil {
52- log.Printf("[webkit] NewBareRelatedWebView: parent native pointer is nil, creating unrelated WebView")53+ logging.Error(fmt.Sprintf("[webkit] NewBareRelatedWebView: parent native pointer is nil, creating unrelated WebView"))
53 return webkit.NewWebView()54 return webkit.NewWebView()
54 }55 }
55 56
56- log.Printf("[webkit] Creating bare WebView with related-view property (parent=%p)", parentNative)57+ logging.Debug(fmt.Sprintf("[webkit] Creating bare WebView with related-view property (parent=%p)", parentNative))
57 58
58 // Create related WebView using CGO helper with g_object_new59 // Create related WebView using CGO helper with g_object_new
59- log.Printf("[webkit] Step 3: About to call C.create_related_web_view")60+ logging.Debug(fmt.Sprintf("[webkit] Step 3: About to call C.create_related_web_view"))
60 webViewNative := C.create_related_web_view(parentNative)61 webViewNative := C.create_related_web_view(parentNative)
61- log.Printf("[webkit] Step 4: C.create_related_web_view returned: %p", webViewNative)62+ logging.Debug(fmt.Sprintf("[webkit] Step 4: C.create_related_web_view returned: %p", webViewNative))
62 63
63 if webViewNative == nil {64 if webViewNative == nil {
64- log.Printf("[webkit] NewBareRelatedWebView: C.create_related_web_view returned nil")65+ logging.Error(fmt.Sprintf("[webkit] NewBareRelatedWebView: C.create_related_web_view returned nil"))
65 return nil66 return nil
66 }67 }
67 68
68 // Wrap the C object in a coreglib.Object69 // Wrap the C object in a coreglib.Object
69 // This takes ownership of the floating reference70 // This takes ownership of the floating reference
70- log.Printf("[webkit] Step 5: About to call coreglib.Take")71+ logging.Debug(fmt.Sprintf("[webkit] Step 5: About to call coreglib.Take"))
71 obj := coreglib.Take(unsafe.Pointer(webViewNative))72 obj := coreglib.Take(unsafe.Pointer(webViewNative))
72- log.Printf("[webkit] Step 6: coreglib.Take succeeded, obj=%p", obj)73+ logging.Debug(fmt.Sprintf("[webkit] Step 6: coreglib.Take succeeded, obj=%p", obj))
73 74
74 // Manually construct the WebView struct hierarchy75 // Manually construct the WebView struct hierarchy
75 // WebView -> WebViewBase -> gtk.Widget -> coreglib.InitiallyUnowned -> coreglib.Object76 // WebView -> WebViewBase -> gtk.Widget -> coreglib.InitiallyUnowned -> coreglib.Object
@@ -78,7 +79,7 @@ func NewBareRelatedWebView(parentView *webkit.WebView) *webkit.WebView {
78 // manually construct the full struct hierarchy using the same pattern79 // manually construct the full struct hierarchy using the same pattern
79 // that gotk4 uses internally.80 // that gotk4 uses internally.
80 81
81- log.Printf("[webkit] Step 7: About to create gtk.Widget base")82+ logging.Debug(fmt.Sprintf("[webkit] Step 7: About to create gtk.Widget base"))
82 // Create the gtk.Widget base83 // Create the gtk.Widget base
83 widget := gtk.Widget{84 widget := gtk.Widget{
84 InitiallyUnowned: coreglib.InitiallyUnowned{85 InitiallyUnowned: coreglib.InitiallyUnowned{
@@ -95,25 +96,25 @@ func NewBareRelatedWebView(parentView *webkit.WebView) *webkit.WebView {
95 Object: obj,96 Object: obj,
96 },97 },
97 }98 }
98- log.Printf("[webkit] Step 8: Created gtk.Widget base")99+ logging.Debug(fmt.Sprintf("[webkit] Step 8: Created gtk.Widget base"))
99 100
100 // Create the WebViewBase101 // Create the WebViewBase
101- log.Printf("[webkit] Step 9: About to create WebViewBase")102+ logging.Debug(fmt.Sprintf("[webkit] Step 9: About to create WebViewBase"))
102 webViewBase := webkit.WebViewBase{103 webViewBase := webkit.WebViewBase{
103 Widget: widget,104 Widget: widget,
104 }105 }
105- log.Printf("[webkit] Step 10: Created WebViewBase")106+ logging.Debug(fmt.Sprintf("[webkit] Step 10: Created WebViewBase"))
106 107
107 // Create the final WebView108 // Create the final WebView
108- log.Printf("[webkit] Step 11: About to create final WebView")109+ logging.Debug(fmt.Sprintf("[webkit] Step 11: About to create final WebView"))
109 relatedView := &webkit.WebView{110 relatedView := &webkit.WebView{
110 WebViewBase: webViewBase,111 WebViewBase: webViewBase,
111 }112 }
112- log.Printf("[webkit] Step 12: Created final WebView: %p", relatedView)113+ logging.Debug(fmt.Sprintf("[webkit] Step 12: Created final WebView: %p", relatedView))
113 114
114 // Ensure parentView is kept alive until after the CGO call completes115 // Ensure parentView is kept alive until after the CGO call completes
115 runtime.KeepAlive(parentView)116 runtime.KeepAlive(parentView)
116 117
117- log.Printf("[webkit] Created bare related WebView (parent=%p)", parentNative)118+ logging.Debug(fmt.Sprintf("[webkit] Created bare related WebView (parent=%p)", parentNative))
118 return relatedView119 return relatedView
119 }120 }
M pkg/webkit/window.go +3 -2
@@ -1,8 +1,9 @@
1 package webkit1 package webkit
2 2
3 import (3 import (
4- "log"4+ "fmt"
5 5
6+ "github.com/bnema/dumber/internal/logging"
6 "github.com/diamondburned/gotk4/pkg/gtk/v4"7 "github.com/diamondburned/gotk4/pkg/gtk/v4"
7 )8 )
8 9
@@ -26,7 +27,7 @@ func NewWindow(title string) (*Window, error) {
26 // Connect close-request signal to handle Cmd+Q and window close button27 // Connect close-request signal to handle Cmd+Q and window close button
27 // Returning true prevents the default close behavior and allows graceful shutdown28 // Returning true prevents the default close behavior and allows graceful shutdown
28 win.ConnectCloseRequest(func() bool {29 win.ConnectCloseRequest(func() bool {
29- log.Printf("[window] Close request received (Cmd+Q or close button) - initiating graceful shutdown")30+ logging.Info(fmt.Sprintf("[window] Close request received (Cmd+Q or close button) - initiating graceful shutdown"))
30 // Quit the main loop gracefully, which will trigger cleanup31 // Quit the main loop gracefully, which will trigger cleanup
31 QuitMainLoop()32 QuitMainLoop()
32 // Return true to prevent GTK from destroying the window immediately33 // Return true to prevent GTK from destroying the window immediately

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