fix(workspace): replace pane ID abstraction with persistent webview IDs for reliable active pane detection
- Remove complex pane ID injection and URI change handlers - Simplify focus tracking using webview IDs instead of pane IDs - Add webview ID request/response mechanism for stub builds - Fix omnibox responsiveness after navigation without mouse movement - Clean up dead code and TypeScript errors - Reduce complexity from 91 to 126 lines (+35 net, -85 deletions)
- commit
- b6be884200add120aaee591249c7e1d325996109
- author
- Brice · · 2025-09-22 20:40 UTC
- parent
- e09e4cf
- signature
- unsigned
10 files · +126 -91
M gui/package-lock.json +0 -15 generated file: collapsed
M gui/src/injected/gui.ts +37 -25
| @@ -58,7 +58,6 @@ declare global { | |||
| 58 | 58 | __dumber_keyboard?: KeyboardService; | |
| 59 | 59 | __dumber_workspace_config?: WorkspaceConfigPayload; | |
| 60 | 60 | __dumber_workspace?: WorkspaceRuntime; | |
| 61 | - | __dumber_pane?: { id: string; active: boolean }; | |
| 62 | 61 | __dumber_omnibox?: { | |
| 63 | 62 | setSuggestions: (suggestions: Suggestion[]) => void; | |
| 64 | 63 | toggle: () => void; | |
| @@ -85,9 +84,32 @@ if (!window.__dumber_gui_ready) { | |||
| 85 | 84 | window.__dumber_gui_ready = true; | |
| 86 | 85 | ||
| 87 | 86 | let workspaceHasFocus = true; // Assume focus initially, will be corrected by focus events | |
| 88 | - | let currentPaneId = window.__dumber_pane?.id || "unknown"; | |
| 87 | + | let currentWebViewId = window.__dumber_webview_id || "unknown"; | |
| 89 | 88 | let hasReceivedFocusEvent = false; // Track if we've received any workspace focus events | |
| 90 | 89 | ||
| 90 | + | // Request webview ID from Go if it's unknown (needed for stub builds) | |
| 91 | + | if (currentWebViewId === "unknown" || currentWebViewId === "__WEBVIEW_ID__") { | |
| 92 | + | console.log("[workspace] WebView ID unknown, requesting from Go backend"); | |
| 93 | + | if (window.webkit?.messageHandlers?.dumber) { | |
| 94 | + | window.webkit.messageHandlers.dumber.postMessage( | |
| 95 | + | JSON.stringify({ | |
| 96 | + | type: "request-webview-id", | |
| 97 | + | payload: { timestamp: Date.now() } | |
| 98 | + | }) | |
| 99 | + | ); | |
| 100 | + | } | |
| 101 | + | } | |
| 102 | + | ||
| 103 | + | // Listen for webview ID from Go backend | |
| 104 | + | document.addEventListener("dumber:webview-id", (event: Event) => { | |
| 105 | + | const detail = (event as CustomEvent).detail || {}; | |
| 106 | + | if (detail.webviewId) { | |
| 107 | + | currentWebViewId = detail.webviewId; | |
| 108 | + | window.__dumber_webview_id = detail.webviewId; | |
| 109 | + | console.log("[workspace] Received webview ID from Go:", detail.webviewId); | |
| 110 | + | } | |
| 111 | + | }); | |
| 112 | + | ||
| 91 | 113 | document.addEventListener("dumber:workspace-focus", (event: Event) => { | |
| 92 | 114 | const detail = (event as CustomEvent).detail || {}; | |
| 93 | 115 | console.log( | |
| @@ -106,21 +128,11 @@ if (!window.__dumber_gui_ready) { | |||
| 106 | 128 | workspaceHasFocus = detail !== false; | |
| 107 | 129 | } | |
| 108 | 130 | ||
| 109 | - | // Update pane tracking | |
| 110 | - | if (detail?.paneId) { | |
| 111 | - | currentPaneId = detail.paneId; | |
| 112 | - | } | |
| 113 | - | ||
| 114 | - | // Update global pane state | |
| 115 | - | if (window.__dumber_pane) { | |
| 116 | - | window.__dumber_pane.active = workspaceHasFocus; | |
| 117 | - | } | |
| 118 | - | ||
| 119 | 131 | console.log( | |
| 120 | 132 | "[workspace focus updated] focus=", | |
| 121 | 133 | workspaceHasFocus, | |
| 122 | - | "paneId=", | |
| 123 | - | currentPaneId, | |
| 134 | + | "webviewId=", | |
| 135 | + | currentWebViewId, | |
| 124 | 136 | ); | |
| 125 | 137 | ||
| 126 | 138 | // Handle GUI visibility based on focus | |
| @@ -265,19 +277,19 @@ if (!window.__dumber_gui_ready) { | |||
| 265 | 277 | document.addEventListener("dumber:ui:shortcut", (e: Event) => { | |
| 266 | 278 | const detail = (e as CustomEvent).detail || {}; | |
| 267 | 279 | const action = detail?.action; | |
| 268 | - | const eventPaneId = detail?.paneId; | |
| 280 | + | const eventWebViewId = detail?.webviewId; | |
| 269 | 281 | const source = detail?.source || "unknown"; | |
| 270 | 282 | ||
| 271 | 283 | if (typeof action !== "string") { | |
| 272 | 284 | return; | |
| 273 | 285 | } | |
| 274 | 286 | ||
| 275 | - | // Enhanced focus checking with pane ID validation | |
| 276 | - | const isForThisPane = !eventPaneId || eventPaneId === currentPaneId; | |
| 287 | + | // Enhanced focus checking with webview ID validation | |
| 288 | + | const isForThisWebView = !eventWebViewId || eventWebViewId === currentWebViewId; | |
| 277 | 289 | // Only apply strict focus checking to omnibox actions, be permissive for others | |
| 278 | 290 | const isOmniboxAction = action.startsWith("omnibox-"); | |
| 279 | 291 | const shouldHandle = | |
| 280 | - | isForThisPane && | |
| 292 | + | isForThisWebView && | |
| 281 | 293 | (isOmniboxAction | |
| 282 | 294 | ? hasReceivedFocusEvent | |
| 283 | 295 | ? workspaceHasFocus | |
| @@ -288,9 +300,9 @@ if (!window.__dumber_gui_ready) { | |||
| 288 | 300 | console.log("[dumber shortcuts] ignored action", { | |
| 289 | 301 | action, | |
| 290 | 302 | workspaceHasFocus, | |
| 291 | - | isForThisPane, | |
| 292 | - | eventPaneId, | |
| 293 | - | currentPaneId, | |
| 303 | + | isForThisWebView, | |
| 304 | + | eventWebViewId, | |
| 305 | + | currentWebViewId, | |
| 294 | 306 | source, | |
| 295 | 307 | detail, | |
| 296 | 308 | hasReceivedFocusEvent, | |
| @@ -301,7 +313,7 @@ if (!window.__dumber_gui_ready) { | |||
| 301 | 313 | console.log("[dumber shortcuts] handling action", { | |
| 302 | 314 | action, | |
| 303 | 315 | source, | |
| 304 | - | paneId: currentPaneId, | |
| 316 | + | webviewId: currentWebViewId, | |
| 305 | 317 | }); | |
| 306 | 318 | ||
| 307 | 319 | const omnibox = window.__dumber_omnibox; | |
| @@ -398,18 +410,18 @@ if (!window.__dumber_gui_ready) { | |||
| 398 | 410 | // Ensure omnibox is available for this pane | |
| 399 | 411 | if (!window.__dumber_omnibox) { | |
| 400 | 412 | console.log( | |
| 401 | - | `[gui-bootstrap] Omnibox not yet available for pane ${currentPaneId}, will be loaded when needed`, | |
| 413 | + | `[gui-bootstrap] Omnibox not yet available for webview ${currentWebViewId}, will be loaded when needed`, | |
| 402 | 414 | ); | |
| 403 | 415 | } else { | |
| 404 | 416 | console.log( | |
| 405 | - | `[gui-bootstrap] Omnibox already available for pane ${currentPaneId}`, | |
| 417 | + | `[gui-bootstrap] Omnibox already available for webview ${currentWebViewId}`, | |
| 406 | 418 | ); | |
| 407 | 419 | // Ensure omnibox is aware of the current pane | |
| 408 | 420 | window.__dumber_omnibox.setActive(workspaceHasFocus); | |
| 409 | 421 | } | |
| 410 | 422 | ||
| 411 | 423 | console.log( | |
| 412 | - | `[gui-bootstrap] GUI bootstrap complete for pane ${currentPaneId}`, | |
| 424 | + | `[gui-bootstrap] GUI bootstrap complete for webview ${currentWebViewId}`, | |
| 413 | 425 | ); | |
| 414 | 426 | }; | |
| 415 | 427 | } | |
| @@ -58,7 +58,6 @@ declare global { | |||||
| 58 | __dumber_keyboard?: KeyboardService; | 58 | __dumber_keyboard?: KeyboardService; | ||
| 59 | __dumber_workspace_config?: WorkspaceConfigPayload; | 59 | __dumber_workspace_config?: WorkspaceConfigPayload; | ||
| 60 | __dumber_workspace?: WorkspaceRuntime; | 60 | __dumber_workspace?: WorkspaceRuntime; | ||
| 61 | - | __dumber_pane?: { id: string; active: boolean }; | |||
| 62 | __dumber_omnibox?: { | 61 | __dumber_omnibox?: { | ||
| 63 | setSuggestions: (suggestions: Suggestion[]) => void; | 62 | setSuggestions: (suggestions: Suggestion[]) => void; | ||
| 64 | toggle: () => void; | 63 | toggle: () => void; | ||
| @@ -85,9 +84,32 @@ if (!window.__dumber_gui_ready) { | |||||
| 85 | window.__dumber_gui_ready = true; | 84 | window.__dumber_gui_ready = true; | ||
| 86 | 85 | ||||
| 87 | let workspaceHasFocus = true; // Assume focus initially, will be corrected by focus events | 86 | let workspaceHasFocus = true; // Assume focus initially, will be corrected by focus events | ||
| 88 | - | let currentPaneId = window.__dumber_pane?.id || "unknown"; | 87 | + | let currentWebViewId = window.__dumber_webview_id || "unknown"; |
| 89 | let hasReceivedFocusEvent = false; // Track if we've received any workspace focus events | 88 | let hasReceivedFocusEvent = false; // Track if we've received any workspace focus events | ||
| 90 | 89 | ||||
| 90 | + | // Request webview ID from Go if it's unknown (needed for stub builds) | |||
| 91 | + | if (currentWebViewId === "unknown" || currentWebViewId === "__WEBVIEW_ID__") { | |||
| 92 | + | console.log("[workspace] WebView ID unknown, requesting from Go backend"); | |||
| 93 | + | if (window.webkit?.messageHandlers?.dumber) { | |||
| 94 | + | window.webkit.messageHandlers.dumber.postMessage( | |||
| 95 | + | JSON.stringify({ | |||
| 96 | + | type: "request-webview-id", | |||
| 97 | + | payload: { timestamp: Date.now() } | |||
| 98 | + | }) | |||
| 99 | + | ); | |||
| 100 | + | } | |||
| 101 | + | } | |||
| 102 | + | ||||
| 103 | + | // Listen for webview ID from Go backend | |||
| 104 | + | document.addEventListener("dumber:webview-id", (event: Event) => { | |||
| 105 | + | const detail = (event as CustomEvent).detail || {}; | |||
| 106 | + | if (detail.webviewId) { | |||
| 107 | + | currentWebViewId = detail.webviewId; | |||
| 108 | + | window.__dumber_webview_id = detail.webviewId; | |||
| 109 | + | console.log("[workspace] Received webview ID from Go:", detail.webviewId); | |||
| 110 | + | } | |||
| 111 | + | }); | |||
| 112 | + | ||||
| 91 | document.addEventListener("dumber:workspace-focus", (event: Event) => { | 113 | document.addEventListener("dumber:workspace-focus", (event: Event) => { | ||
| 92 | const detail = (event as CustomEvent).detail || {}; | 114 | const detail = (event as CustomEvent).detail || {}; | ||
| 93 | console.log( | 115 | console.log( | ||
| @@ -106,21 +128,11 @@ if (!window.__dumber_gui_ready) { | |||||
| 106 | workspaceHasFocus = detail !== false; | 128 | workspaceHasFocus = detail !== false; | ||
| 107 | } | 129 | } | ||
| 108 | 130 | ||||
| 109 | - | // Update pane tracking | |||
| 110 | - | if (detail?.paneId) { | |||
| 111 | - | currentPaneId = detail.paneId; | |||
| 112 | - | } | |||
| 113 | - | ||||
| 114 | - | // Update global pane state | |||
| 115 | - | if (window.__dumber_pane) { | |||
| 116 | - | window.__dumber_pane.active = workspaceHasFocus; | |||
| 117 | - | } | |||
| 118 | - | ||||
| 119 | console.log( | 131 | console.log( | ||
| 120 | "[workspace focus updated] focus=", | 132 | "[workspace focus updated] focus=", | ||
| 121 | workspaceHasFocus, | 133 | workspaceHasFocus, | ||
| 122 | - | "paneId=", | 134 | + | "webviewId=", |
| 123 | - | currentPaneId, | 135 | + | currentWebViewId, |
| 124 | ); | 136 | ); | ||
| 125 | 137 | ||||
| 126 | // Handle GUI visibility based on focus | 138 | // Handle GUI visibility based on focus | ||
| @@ -265,19 +277,19 @@ if (!window.__dumber_gui_ready) { | |||||
| 265 | document.addEventListener("dumber:ui:shortcut", (e: Event) => { | 277 | document.addEventListener("dumber:ui:shortcut", (e: Event) => { | ||
| 266 | const detail = (e as CustomEvent).detail || {}; | 278 | const detail = (e as CustomEvent).detail || {}; | ||
| 267 | const action = detail?.action; | 279 | const action = detail?.action; | ||
| 268 | - | const eventPaneId = detail?.paneId; | 280 | + | const eventWebViewId = detail?.webviewId; |
| 269 | const source = detail?.source || "unknown"; | 281 | const source = detail?.source || "unknown"; | ||
| 270 | 282 | ||||
| 271 | if (typeof action !== "string") { | 283 | if (typeof action !== "string") { | ||
| 272 | return; | 284 | return; | ||
| 273 | } | 285 | } | ||
| 274 | 286 | ||||
| 275 | - | // Enhanced focus checking with pane ID validation | 287 | + | // Enhanced focus checking with webview ID validation |
| 276 | - | const isForThisPane = !eventPaneId || eventPaneId === currentPaneId; | 288 | + | const isForThisWebView = !eventWebViewId || eventWebViewId === currentWebViewId; |
| 277 | // Only apply strict focus checking to omnibox actions, be permissive for others | 289 | // Only apply strict focus checking to omnibox actions, be permissive for others | ||
| 278 | const isOmniboxAction = action.startsWith("omnibox-"); | 290 | const isOmniboxAction = action.startsWith("omnibox-"); | ||
| 279 | const shouldHandle = | 291 | const shouldHandle = | ||
| 280 | - | isForThisPane && | 292 | + | isForThisWebView && |
| 281 | (isOmniboxAction | 293 | (isOmniboxAction | ||
| 282 | ? hasReceivedFocusEvent | 294 | ? hasReceivedFocusEvent | ||
| 283 | ? workspaceHasFocus | 295 | ? workspaceHasFocus | ||
| @@ -288,9 +300,9 @@ if (!window.__dumber_gui_ready) { | |||||
| 288 | console.log("[dumber shortcuts] ignored action", { | 300 | console.log("[dumber shortcuts] ignored action", { | ||
| 289 | action, | 301 | action, | ||
| 290 | workspaceHasFocus, | 302 | workspaceHasFocus, | ||
| 291 | - | isForThisPane, | 303 | + | isForThisWebView, |
| 292 | - | eventPaneId, | 304 | + | eventWebViewId, |
| 293 | - | currentPaneId, | 305 | + | currentWebViewId, |
| 294 | source, | 306 | source, | ||
| 295 | detail, | 307 | detail, | ||
| 296 | hasReceivedFocusEvent, | 308 | hasReceivedFocusEvent, | ||
| @@ -301,7 +313,7 @@ if (!window.__dumber_gui_ready) { | |||||
| 301 | console.log("[dumber shortcuts] handling action", { | 313 | console.log("[dumber shortcuts] handling action", { | ||
| 302 | action, | 314 | action, | ||
| 303 | source, | 315 | source, | ||
| 304 | - | paneId: currentPaneId, | 316 | + | webviewId: currentWebViewId, |
| 305 | }); | 317 | }); | ||
| 306 | 318 | ||||
| 307 | const omnibox = window.__dumber_omnibox; | 319 | const omnibox = window.__dumber_omnibox; | ||
| @@ -398,18 +410,18 @@ if (!window.__dumber_gui_ready) { | |||||
| 398 | // Ensure omnibox is available for this pane | 410 | // Ensure omnibox is available for this pane | ||
| 399 | if (!window.__dumber_omnibox) { | 411 | if (!window.__dumber_omnibox) { | ||
| 400 | console.log( | 412 | console.log( | ||
| 401 | - | `[gui-bootstrap] Omnibox not yet available for pane ${currentPaneId}, will be loaded when needed`, | 413 | + | `[gui-bootstrap] Omnibox not yet available for webview ${currentWebViewId}, will be loaded when needed`, |
| 402 | ); | 414 | ); | ||
| 403 | } else { | 415 | } else { | ||
| 404 | console.log( | 416 | console.log( | ||
| 405 | - | `[gui-bootstrap] Omnibox already available for pane ${currentPaneId}`, | 417 | + | `[gui-bootstrap] Omnibox already available for webview ${currentWebViewId}`, |
| 406 | ); | 418 | ); | ||
| 407 | // Ensure omnibox is aware of the current pane | 419 | // Ensure omnibox is aware of the current pane | ||
| 408 | window.__dumber_omnibox.setActive(workspaceHasFocus); | 420 | window.__dumber_omnibox.setActive(workspaceHasFocus); | ||
| 409 | } | 421 | } | ||
| 410 | 422 | ||||
| 411 | console.log( | 423 | console.log( | ||
| 412 | - | `[gui-bootstrap] GUI bootstrap complete for pane ${currentPaneId}`, | 424 | + | `[gui-bootstrap] GUI bootstrap complete for webview ${currentWebViewId}`, |
| 413 | ); | 425 | ); | ||
| 414 | }; | 426 | }; | ||
| 415 | } | 427 | } | ||
M gui/src/injected/main-world.ts +1 -1
| @@ -404,7 +404,7 @@ function detectWindowType(features?: string | null): string { | |||
| 404 | 404 | setupPopupOpenerBridge(); | |
| 405 | 405 | ||
| 406 | 406 | // Parent window message polling - only for OAuth scenarios | |
| 407 | - | let parentPollingInterval = null; | |
| 407 | + | let parentPollingInterval: ReturnType<typeof setInterval> | null = null; | |
| 408 | 408 | ||
| 409 | 409 | const setupParentMessagePolling = () => { | |
| 410 | 410 | // Only start polling if we have popup mappings or OAuth callbacks | |
| @@ -404,7 +404,7 @@ function detectWindowType(features?: string | null): string { | |||||
| 404 | setupPopupOpenerBridge(); | 404 | setupPopupOpenerBridge(); | ||
| 405 | 405 | ||||
| 406 | // Parent window message polling - only for OAuth scenarios | 406 | // Parent window message polling - only for OAuth scenarios | ||
| 407 | - | let parentPollingInterval = null; | 407 | + | let parentPollingInterval: ReturnType<typeof setInterval> | null = null; |
| 408 | 408 | ||||
| 409 | const setupParentMessagePolling = () => { | 409 | const setupParentMessagePolling = () => { | ||
| 410 | // Only start polling if we have popup mappings or OAuth callbacks | 410 | // Only start polling if we have popup mappings or OAuth callbacks | ||
M gui/src/services/app.ts +3 -0
| @@ -54,6 +54,7 @@ export class AppService { | |||
| 54 | 54 | visit_count: 3, | |
| 55 | 55 | last_visited: new Date().toISOString(), | |
| 56 | 56 | created_at: new Date().toISOString(), | |
| 57 | + | favicon_url: null, | |
| 57 | 58 | }, | |
| 58 | 59 | { | |
| 59 | 60 | id: 2, | |
| @@ -62,6 +63,7 @@ export class AppService { | |||
| 62 | 63 | visit_count: 2, | |
| 63 | 64 | last_visited: new Date().toISOString(), | |
| 64 | 65 | created_at: new Date().toISOString(), | |
| 66 | + | favicon_url: null, | |
| 65 | 67 | }, | |
| 66 | 68 | { | |
| 67 | 69 | id: 3, | |
| @@ -70,6 +72,7 @@ export class AppService { | |||
| 70 | 72 | visit_count: 1, | |
| 71 | 73 | last_visited: new Date().toISOString(), | |
| 72 | 74 | created_at: new Date().toISOString(), | |
| 75 | + | favicon_url: null, | |
| 73 | 76 | }, | |
| 74 | 77 | ]; | |
| 75 | 78 | } | |
| @@ -54,6 +54,7 @@ export class AppService { | |||||
| 54 | visit_count: 3, | 54 | visit_count: 3, | ||
| 55 | last_visited: new Date().toISOString(), | 55 | last_visited: new Date().toISOString(), | ||
| 56 | created_at: new Date().toISOString(), | 56 | created_at: new Date().toISOString(), | ||
| 57 | + | favicon_url: null, | |||
| 57 | }, | 58 | }, | ||
| 58 | { | 59 | { | ||
| 59 | id: 2, | 60 | id: 2, | ||
| @@ -62,6 +63,7 @@ export class AppService { | |||||
| 62 | visit_count: 2, | 63 | visit_count: 2, | ||
| 63 | last_visited: new Date().toISOString(), | 64 | last_visited: new Date().toISOString(), | ||
| 64 | created_at: new Date().toISOString(), | 65 | created_at: new Date().toISOString(), | ||
| 66 | + | favicon_url: null, | |||
| 65 | }, | 67 | }, | ||
| 66 | { | 68 | { | ||
| 67 | id: 3, | 69 | id: 3, | ||
| @@ -70,6 +72,7 @@ export class AppService { | |||||
| 70 | visit_count: 1, | 72 | visit_count: 1, | ||
| 71 | last_visited: new Date().toISOString(), | 73 | last_visited: new Date().toISOString(), | ||
| 72 | created_at: new Date().toISOString(), | 74 | created_at: new Date().toISOString(), | ||
| 75 | + | favicon_url: null, | |||
| 73 | }, | 76 | }, | ||
| 74 | ]; | 77 | ]; | ||
| 75 | } | 78 | } | ||
M gui/src/types/generated.ts +1 -1
| @@ -86,4 +86,4 @@ export enum InputType { | |||
| 86 | 86 | // Type aliases for frontend usage | |
| 87 | 87 | export type HistoryEntry = History; | |
| 88 | 88 | export type SearchShortcut = Shortcut; | |
| 89 | - | export type ZoomEntry = ZoomSetting; | |
| 89 | + | export type ZoomEntry = ZoomLevel; | |
| @@ -86,4 +86,4 @@ export enum InputType { | |||||
| 86 | // Type aliases for frontend usage | 86 | // Type aliases for frontend usage | ||
| 87 | export type HistoryEntry = History; | 87 | export type HistoryEntry = History; | ||
| 88 | export type SearchShortcut = Shortcut; | 88 | export type SearchShortcut = Shortcut; | ||
| 89 | - | export type ZoomEntry = ZoomSetting; | 89 | + | export type ZoomEntry = ZoomLevel; |
M internal/app/browser/webview.go +7 -17
| @@ -119,6 +119,11 @@ func (app *BrowserApp) buildPane(view *webkit.WebView) (*BrowserPane, error) { | |||
| 119 | 119 | pane.zoomController.RegisterHandlers() | |
| 120 | 120 | pane.shortcutHandler.RegisterShortcuts() | |
| 121 | 121 | ||
| 122 | + | // Register navigation handler for workspace focus events if workspace exists | |
| 123 | + | if app.workspace != nil { | |
| 124 | + | app.workspace.RegisterNavigationHandler(view) | |
| 125 | + | } | |
| 126 | + | ||
| 122 | 127 | return pane, nil | |
| 123 | 128 | } | |
| 124 | 129 | ||
| @@ -132,23 +137,8 @@ func (app *BrowserApp) createPaneForView(view *webkit.WebView) (*BrowserPane, er | |||
| 132 | 137 | pane.SetID(fmt.Sprintf("pane-%d-%p", time.Now().Unix(), view)) | |
| 133 | 138 | pane.initializeGUITracking() | |
| 134 | 139 | ||
| 135 | - | // Inject minimal bootstrap script for pane initialization | |
| 136 | - | bootstrapScript := fmt.Sprintf(` | |
| 137 | - | window.__dumber_pane = { | |
| 138 | - | id: '%s', | |
| 139 | - | created: %d, | |
| 140 | - | active: false | |
| 141 | - | }; | |
| 142 | - | ||
| 143 | - | // GUI manager will be loaded on-demand | |
| 144 | - | window.__dumber_gui_ready = true; | |
| 145 | - | ||
| 146 | - | console.log('[pane] Initialized pane %s'); | |
| 147 | - | `, pane.ID(), time.Now().UnixMilli(), pane.ID()) | |
| 148 | - | ||
| 149 | - | if err := view.InjectScript(bootstrapScript); err != nil { | |
| 150 | - | log.Printf("[pane-%s] Failed to inject bootstrap: %v", pane.ID(), err) | |
| 151 | - | } | |
| 140 | + | // GUI manager will be loaded on-demand when needed | |
| 141 | + | log.Printf("[webview] Created pane for webview: %s", view.ID()) | |
| 152 | 142 | ||
| 153 | 143 | app.attachPaneHandlers(pane) | |
| 154 | 144 | return pane, nil | |
| @@ -119,6 +119,11 @@ func (app *BrowserApp) buildPane(view *webkit.WebView) (*BrowserPane, error) { | |||||
| 119 | pane.zoomController.RegisterHandlers() | 119 | pane.zoomController.RegisterHandlers() | ||
| 120 | pane.shortcutHandler.RegisterShortcuts() | 120 | pane.shortcutHandler.RegisterShortcuts() | ||
| 121 | 121 | ||||
| 122 | + | // Register navigation handler for workspace focus events if workspace exists | |||
| 123 | + | if app.workspace != nil { | |||
| 124 | + | app.workspace.RegisterNavigationHandler(view) | |||
| 125 | + | } | |||
| 126 | + | ||||
| 122 | return pane, nil | 127 | return pane, nil | ||
| 123 | } | 128 | } | ||
| 124 | 129 | ||||
| @@ -132,23 +137,8 @@ func (app *BrowserApp) createPaneForView(view *webkit.WebView) (*BrowserPane, er | |||||
| 132 | pane.SetID(fmt.Sprintf("pane-%d-%p", time.Now().Unix(), view)) | 137 | pane.SetID(fmt.Sprintf("pane-%d-%p", time.Now().Unix(), view)) | ||
| 133 | pane.initializeGUITracking() | 138 | pane.initializeGUITracking() | ||
| 134 | 139 | ||||
| 135 | - | // Inject minimal bootstrap script for pane initialization | 140 | + | // GUI manager will be loaded on-demand when needed |
| 136 | - | bootstrapScript := fmt.Sprintf(` | 141 | + | log.Printf("[webview] Created pane for webview: %s", view.ID()) |
| 137 | - | window.__dumber_pane = { | |||
| 138 | - | id: '%s', | |||
| 139 | - | created: %d, | |||
| 140 | - | active: false | |||
| 141 | - | }; | |||
| 142 | - | ||||
| 143 | - | // GUI manager will be loaded on-demand | |||
| 144 | - | window.__dumber_gui_ready = true; | |||
| 145 | - | ||||
| 146 | - | console.log('[pane] Initialized pane %s'); | |||
| 147 | - | `, pane.ID(), time.Now().UnixMilli(), pane.ID()) | |||
| 148 | - | ||||
| 149 | - | if err := view.InjectScript(bootstrapScript); err != nil { | |||
| 150 | - | log.Printf("[pane-%s] Failed to inject bootstrap: %v", pane.ID(), err) | |||
| 151 | - | } | |||
| 152 | 142 | ||||
| 153 | app.attachPaneHandlers(pane) | 143 | app.attachPaneHandlers(pane) | ||
| 154 | return pane, nil | 144 | return pane, nil | ||
M internal/app/browser/window_shortcuts.go +4 -4
| @@ -227,11 +227,11 @@ func (h *WindowShortcutHandler) ensureGUIInActivePane(component string) { | |||
| 227 | 227 | } | |
| 228 | 228 | } | |
| 229 | 229 | ||
| 230 | - | func (h *WindowShortcutHandler) getPaneId(pane *BrowserPane) string { | |
| 231 | - | if pane == nil { | |
| 230 | + | func (h *WindowShortcutHandler) getWebViewId(pane *BrowserPane) string { | |
| 231 | + | if pane == nil || pane.webView == nil { | |
| 232 | 232 | return "unknown" | |
| 233 | 233 | } | |
| 234 | - | return pane.ID() | |
| 234 | + | return pane.webView.ID() | |
| 235 | 235 | } | |
| 236 | 236 | ||
| 237 | 237 | func (h *WindowShortcutHandler) handleUIToggle(lastToggle *time.Time, featureName, action string) { | |
| @@ -255,7 +255,7 @@ func (h *WindowShortcutHandler) handleUIToggle(lastToggle *time.Time, featureNam | |||
| 255 | 255 | ||
| 256 | 256 | if err := h.app.activePane.webView.DispatchCustomEvent("dumber:ui:shortcut", map[string]any{ | |
| 257 | 257 | "action": action, | |
| 258 | - | "paneId": h.getPaneId(h.app.activePane), | |
| 258 | + | "webviewId": h.getWebViewId(h.app.activePane), | |
| 259 | 259 | "timestamp": time.Now().UnixMilli(), | |
| 260 | 260 | "source": "window-global", | |
| 261 | 261 | }); err != nil { | |
| @@ -227,11 +227,11 @@ func (h *WindowShortcutHandler) ensureGUIInActivePane(component string) { | |||||
| 227 | } | 227 | } | ||
| 228 | } | 228 | } | ||
| 229 | 229 | ||||
| 230 | - | func (h *WindowShortcutHandler) getPaneId(pane *BrowserPane) string { | 230 | + | func (h *WindowShortcutHandler) getWebViewId(pane *BrowserPane) string { |
| 231 | - | if pane == nil { | 231 | + | if pane == nil || pane.webView == nil { |
| 232 | return "unknown" | 232 | return "unknown" | ||
| 233 | } | 233 | } | ||
| 234 | - | return pane.ID() | 234 | + | return pane.webView.ID() |
| 235 | } | 235 | } | ||
| 236 | 236 | ||||
| 237 | func (h *WindowShortcutHandler) handleUIToggle(lastToggle *time.Time, featureName, action string) { | 237 | func (h *WindowShortcutHandler) handleUIToggle(lastToggle *time.Time, featureName, action string) { | ||
| @@ -255,7 +255,7 @@ func (h *WindowShortcutHandler) handleUIToggle(lastToggle *time.Time, featureNam | |||||
| 255 | 255 | ||||
| 256 | if err := h.app.activePane.webView.DispatchCustomEvent("dumber:ui:shortcut", map[string]any{ | 256 | if err := h.app.activePane.webView.DispatchCustomEvent("dumber:ui:shortcut", map[string]any{ | ||
| 257 | "action": action, | 257 | "action": action, | ||
| 258 | - | "paneId": h.getPaneId(h.app.activePane), | 258 | + | "webviewId": h.getWebViewId(h.app.activePane), |
| 259 | "timestamp": time.Now().UnixMilli(), | 259 | "timestamp": time.Now().UnixMilli(), | ||
| 260 | "source": "window-global", | 260 | "source": "window-global", | ||
| 261 | }); err != nil { | 261 | }); err != nil { | ||
M internal/app/browser/workspace_manager.go +46 -27
| @@ -354,6 +354,47 @@ func (wm *WorkspaceManager) OnWorkspaceMessage(source *webkit.WebView, msg messa | |||
| 354 | 354 | } | |
| 355 | 355 | } | |
| 356 | 356 | ||
| 357 | + | // GetActiveNode returns the currently active pane node | |
| 358 | + | func (wm *WorkspaceManager) GetActiveNode() *paneNode { | |
| 359 | + | return wm.active | |
| 360 | + | } | |
| 361 | + | ||
| 362 | + | // GetNodeForWebView returns the pane node associated with a WebView | |
| 363 | + | func (wm *WorkspaceManager) GetNodeForWebView(webView *webkit.WebView) *paneNode { | |
| 364 | + | return wm.viewToNode[webView] | |
| 365 | + | } | |
| 366 | + | ||
| 367 | + | // RegisterNavigationHandler sets up navigation handling for a webview (simplified) | |
| 368 | + | func (wm *WorkspaceManager) RegisterNavigationHandler(webView *webkit.WebView) { | |
| 369 | + | if webView == nil { | |
| 370 | + | return | |
| 371 | + | } | |
| 372 | + | ||
| 373 | + | log.Printf("[workspace] Registered navigation handler for webview: %s", webView.ID()) | |
| 374 | + | } | |
| 375 | + | ||
| 376 | + | // DispatchPaneFocusEvent sends a workspace focus event to a pane's webview | |
| 377 | + | func (wm *WorkspaceManager) DispatchPaneFocusEvent(node *paneNode, active bool) { | |
| 378 | + | if node == nil || node.pane == nil || node.pane.webView == nil { | |
| 379 | + | return | |
| 380 | + | } | |
| 381 | + | ||
| 382 | + | detail := map[string]any{ | |
| 383 | + | "active": active, | |
| 384 | + | "webview": fmt.Sprintf("%p", node.pane.webView), | |
| 385 | + | "webviewId": node.pane.webView.ID(), | |
| 386 | + | "hasGUI": node.pane.HasGUI(), | |
| 387 | + | "timestamp": time.Now().UnixMilli(), | |
| 388 | + | } | |
| 389 | + | ||
| 390 | + | if err := node.pane.webView.DispatchCustomEvent("dumber:workspace-focus", detail); err != nil { | |
| 391 | + | log.Printf("[workspace] failed to dispatch focus event: %v", err) | |
| 392 | + | } else if wm.app.config != nil && wm.app.config.Debug.EnableWorkspaceDebug { | |
| 393 | + | log.Printf("[workspace] dispatched focus event for webview %s (active=%v)", node.pane.webView.ID(), active) | |
| 394 | + | } | |
| 395 | + | } | |
| 396 | + | ||
| 397 | + | ||
| 357 | 398 | func (wm *WorkspaceManager) focusNode(node *paneNode) { | |
| 358 | 399 | if node == nil || !node.isLeaf || node.pane == nil || node.pane.webView == nil { | |
| 359 | 400 | return | |
| @@ -371,17 +412,7 @@ func (wm *WorkspaceManager) focusNode(node *paneNode) { | |||
| 371 | 412 | if previous != nil && previous != node && previous.pane != nil && previous.pane.webView != nil { | |
| 372 | 413 | previousContainer = previous.container | |
| 373 | 414 | previousWebView = previous.pane.webView | |
| 374 | - | detail := map[string]any{ | |
| 375 | - | "active": false, | |
| 376 | - | "webview": fmt.Sprintf("%p", previous.pane.webView), | |
| 377 | - | "paneId": previous.pane.ID(), | |
| 378 | - | "timestamp": time.Now().UnixMilli(), | |
| 379 | - | } | |
| 380 | - | if err := previous.pane.webView.DispatchCustomEvent("dumber:workspace-focus", detail); err != nil { | |
| 381 | - | log.Printf("[workspace] failed to notify focus loss: %v", err) | |
| 382 | - | } else if wm.app.config != nil && wm.app.config.Debug.EnableWorkspaceDebug { | |
| 383 | - | log.Printf("[workspace] notified focus loss for pane %s", previous.pane.ID()) | |
| 384 | - | } | |
| 415 | + | wm.DispatchPaneFocusEvent(previous, false) | |
| 385 | 416 | } | |
| 386 | 417 | ||
| 387 | 418 | // Set previously active WebView as inactive | |
| @@ -453,25 +484,13 @@ func (wm *WorkspaceManager) focusNode(node *paneNode) { | |||
| 453 | 484 | }) | |
| 454 | 485 | } | |
| 455 | 486 | ||
| 456 | - | if node.pane != nil && node.pane.webView != nil && node.pane.webView != previousWebView { | |
| 487 | + | if node.pane != nil && node.pane.webView != nil { | |
| 457 | 488 | // Update pane focus time | |
| 458 | 489 | node.pane.UpdateLastFocus() | |
| 459 | 490 | ||
| 460 | - | // Enhanced focus event with pane ID and GUI status | |
| 461 | - | detail := map[string]any{ | |
| 462 | - | "active": true, | |
| 463 | - | "webview": fmt.Sprintf("%p", node.pane.webView), | |
| 464 | - | "paneId": node.pane.ID(), | |
| 465 | - | "hasGUI": node.pane.HasGUI(), | |
| 466 | - | "timestamp": time.Now().UnixMilli(), | |
| 467 | - | } | |
| 468 | - | ||
| 469 | - | // Dispatch focus event to new active pane | |
| 470 | - | if err := node.pane.webView.DispatchCustomEvent("dumber:workspace-focus", detail); err != nil { | |
| 471 | - | log.Printf("[workspace] failed to notify focus gain: %v", err) | |
| 472 | - | } else if wm.app.config != nil && wm.app.config.Debug.EnableWorkspaceDebug { | |
| 473 | - | log.Printf("[workspace] notified focus gain for pane %s", node.pane.ID()) | |
| 474 | - | } | |
| 491 | + | // Always dispatch focus event to ensure JavaScript side knows the active pane | |
| 492 | + | // This fixes the issue where navigation within the same pane doesn't update JS state | |
| 493 | + | wm.DispatchPaneFocusEvent(node, true) | |
| 475 | 494 | ||
| 476 | 495 | // Lazy-load GUI components if first focus | |
| 477 | 496 | if !node.pane.HasGUI() { | |
| @@ -354,6 +354,47 @@ func (wm *WorkspaceManager) OnWorkspaceMessage(source *webkit.WebView, msg messa | |||||
| 354 | } | 354 | } | ||
| 355 | } | 355 | } | ||
| 356 | 356 | ||||
| 357 | + | // GetActiveNode returns the currently active pane node | |||
| 358 | + | func (wm *WorkspaceManager) GetActiveNode() *paneNode { | |||
| 359 | + | return wm.active | |||
| 360 | + | } | |||
| 361 | + | ||||
| 362 | + | // GetNodeForWebView returns the pane node associated with a WebView | |||
| 363 | + | func (wm *WorkspaceManager) GetNodeForWebView(webView *webkit.WebView) *paneNode { | |||
| 364 | + | return wm.viewToNode[webView] | |||
| 365 | + | } | |||
| 366 | + | ||||
| 367 | + | // RegisterNavigationHandler sets up navigation handling for a webview (simplified) | |||
| 368 | + | func (wm *WorkspaceManager) RegisterNavigationHandler(webView *webkit.WebView) { | |||
| 369 | + | if webView == nil { | |||
| 370 | + | return | |||
| 371 | + | } | |||
| 372 | + | ||||
| 373 | + | log.Printf("[workspace] Registered navigation handler for webview: %s", webView.ID()) | |||
| 374 | + | } | |||
| 375 | + | ||||
| 376 | + | // DispatchPaneFocusEvent sends a workspace focus event to a pane's webview | |||
| 377 | + | func (wm *WorkspaceManager) DispatchPaneFocusEvent(node *paneNode, active bool) { | |||
| 378 | + | if node == nil || node.pane == nil || node.pane.webView == nil { | |||
| 379 | + | return | |||
| 380 | + | } | |||
| 381 | + | ||||
| 382 | + | detail := map[string]any{ | |||
| 383 | + | "active": active, | |||
| 384 | + | "webview": fmt.Sprintf("%p", node.pane.webView), | |||
| 385 | + | "webviewId": node.pane.webView.ID(), | |||
| 386 | + | "hasGUI": node.pane.HasGUI(), | |||
| 387 | + | "timestamp": time.Now().UnixMilli(), | |||
| 388 | + | } | |||
| 389 | + | ||||
| 390 | + | if err := node.pane.webView.DispatchCustomEvent("dumber:workspace-focus", detail); err != nil { | |||
| 391 | + | log.Printf("[workspace] failed to dispatch focus event: %v", err) | |||
| 392 | + | } else if wm.app.config != nil && wm.app.config.Debug.EnableWorkspaceDebug { | |||
| 393 | + | log.Printf("[workspace] dispatched focus event for webview %s (active=%v)", node.pane.webView.ID(), active) | |||
| 394 | + | } | |||
| 395 | + | } | |||
| 396 | + | ||||
| 397 | + | ||||
| 357 | func (wm *WorkspaceManager) focusNode(node *paneNode) { | 398 | func (wm *WorkspaceManager) focusNode(node *paneNode) { | ||
| 358 | if node == nil || !node.isLeaf || node.pane == nil || node.pane.webView == nil { | 399 | if node == nil || !node.isLeaf || node.pane == nil || node.pane.webView == nil { | ||
| 359 | return | 400 | return | ||
| @@ -371,17 +412,7 @@ func (wm *WorkspaceManager) focusNode(node *paneNode) { | |||||
| 371 | if previous != nil && previous != node && previous.pane != nil && previous.pane.webView != nil { | 412 | if previous != nil && previous != node && previous.pane != nil && previous.pane.webView != nil { | ||
| 372 | previousContainer = previous.container | 413 | previousContainer = previous.container | ||
| 373 | previousWebView = previous.pane.webView | 414 | previousWebView = previous.pane.webView | ||
| 374 | - | detail := map[string]any{ | 415 | + | wm.DispatchPaneFocusEvent(previous, false) |
| 375 | - | "active": false, | |||
| 376 | - | "webview": fmt.Sprintf("%p", previous.pane.webView), | |||
| 377 | - | "paneId": previous.pane.ID(), | |||
| 378 | - | "timestamp": time.Now().UnixMilli(), | |||
| 379 | - | } | |||
| 380 | - | if err := previous.pane.webView.DispatchCustomEvent("dumber:workspace-focus", detail); err != nil { | |||
| 381 | - | log.Printf("[workspace] failed to notify focus loss: %v", err) | |||
| 382 | - | } else if wm.app.config != nil && wm.app.config.Debug.EnableWorkspaceDebug { | |||
| 383 | - | log.Printf("[workspace] notified focus loss for pane %s", previous.pane.ID()) | |||
| 384 | - | } | |||
| 385 | } | 416 | } | ||
| 386 | 417 | ||||
| 387 | // Set previously active WebView as inactive | 418 | // Set previously active WebView as inactive | ||
| @@ -453,25 +484,13 @@ func (wm *WorkspaceManager) focusNode(node *paneNode) { | |||||
| 453 | }) | 484 | }) | ||
| 454 | } | 485 | } | ||
| 455 | 486 | ||||
| 456 | - | if node.pane != nil && node.pane.webView != nil && node.pane.webView != previousWebView { | 487 | + | if node.pane != nil && node.pane.webView != nil { |
| 457 | // Update pane focus time | 488 | // Update pane focus time | ||
| 458 | node.pane.UpdateLastFocus() | 489 | node.pane.UpdateLastFocus() | ||
| 459 | 490 | ||||
| 460 | - | // Enhanced focus event with pane ID and GUI status | 491 | + | // Always dispatch focus event to ensure JavaScript side knows the active pane |
| 461 | - | detail := map[string]any{ | 492 | + | // This fixes the issue where navigation within the same pane doesn't update JS state |
| 462 | - | "active": true, | 493 | + | wm.DispatchPaneFocusEvent(node, true) |
| 463 | - | "webview": fmt.Sprintf("%p", node.pane.webView), | |||
| 464 | - | "paneId": node.pane.ID(), | |||
| 465 | - | "hasGUI": node.pane.HasGUI(), | |||
| 466 | - | "timestamp": time.Now().UnixMilli(), | |||
| 467 | - | } | |||
| 468 | - | ||||
| 469 | - | // Dispatch focus event to new active pane | |||
| 470 | - | if err := node.pane.webView.DispatchCustomEvent("dumber:workspace-focus", detail); err != nil { | |||
| 471 | - | log.Printf("[workspace] failed to notify focus gain: %v", err) | |||
| 472 | - | } else if wm.app.config != nil && wm.app.config.Debug.EnableWorkspaceDebug { | |||
| 473 | - | log.Printf("[workspace] notified focus gain for pane %s", node.pane.ID()) | |||
| 474 | - | } | |||
| 475 | 494 | ||||
| 476 | // Lazy-load GUI components if first focus | 495 | // Lazy-load GUI components if first focus | ||
| 477 | if !node.pane.HasGUI() { | 496 | if !node.pane.HasGUI() { | ||
M internal/app/messaging/handler.go +22 -1
| @@ -8,6 +8,7 @@ import ( | |||
| 8 | 8 | "sort" | |
| 9 | 9 | "strconv" | |
| 10 | 10 | "strings" | |
| 11 | + | "time" | |
| 11 | 12 | ||
| 12 | 13 | "github.com/bnema/dumber/internal/app/constants" | |
| 13 | 14 | "github.com/bnema/dumber/internal/app/control" | |
| @@ -42,7 +43,6 @@ type Message struct { | |||
| 42 | 43 | Event string `json:"event"` | |
| 43 | 44 | Action string `json:"action"` | |
| 44 | 45 | Direction string `json:"direction"` | |
| 45 | - | PaneID string `json:"paneId"` | |
| 46 | 46 | // History operations | |
| 47 | 47 | HistoryID string `json:"historyId"` | |
| 48 | 48 | // Request tracking | |
| @@ -129,6 +129,8 @@ func (h *Handler) Handle(payload string) { | |||
| 129 | 129 | h.handleClosePopup(msg) | |
| 130 | 130 | case "console-message": | |
| 131 | 131 | h.handleConsoleMessage(msg) | |
| 132 | + | case "request-webview-id": | |
| 133 | + | h.handleWebViewIDRequest(msg) | |
| 132 | 134 | } | |
| 133 | 135 | } | |
| 134 | 136 | ||
| @@ -602,3 +604,22 @@ func (h *Handler) handleConsoleMessage(msg Message) { | |||
| 602 | 604 | // Send to logging system with [CONSOLE] tag | |
| 603 | 605 | logging.CaptureWebKitLog(consolePayload.Message) | |
| 604 | 606 | } | |
| 607 | + | ||
| 608 | + | // handleWebViewIDRequest responds to JavaScript requests for the webview ID | |
| 609 | + | func (h *Handler) handleWebViewIDRequest(msg Message) { | |
| 610 | + | if h.webView == nil { | |
| 611 | + | log.Printf("[messaging] Cannot provide webview ID - no webview available") | |
| 612 | + | return | |
| 613 | + | } | |
| 614 | + | ||
| 615 | + | webViewID := h.webView.ID() | |
| 616 | + | log.Printf("[messaging] Sending webview ID %s to JavaScript", webViewID) | |
| 617 | + | ||
| 618 | + | // Send the webview ID back to JavaScript via custom event | |
| 619 | + | if err := h.webView.DispatchCustomEvent("dumber:webview-id", map[string]any{ | |
| 620 | + | "webviewId": webViewID, | |
| 621 | + | "timestamp": time.Now().UnixMilli(), | |
| 622 | + | }); err != nil { | |
| 623 | + | log.Printf("[messaging] Failed to send webview ID: %v", err) | |
| 624 | + | } | |
| 625 | + | } | |
| @@ -8,6 +8,7 @@ import ( | |||||
| 8 | "sort" | 8 | "sort" | ||
| 9 | "strconv" | 9 | "strconv" | ||
| 10 | "strings" | 10 | "strings" | ||
| 11 | + | "time" | |||
| 11 | 12 | ||||
| 12 | "github.com/bnema/dumber/internal/app/constants" | 13 | "github.com/bnema/dumber/internal/app/constants" | ||
| 13 | "github.com/bnema/dumber/internal/app/control" | 14 | "github.com/bnema/dumber/internal/app/control" | ||
| @@ -42,7 +43,6 @@ type Message struct { | |||||
| 42 | Event string `json:"event"` | 43 | Event string `json:"event"` | ||
| 43 | Action string `json:"action"` | 44 | Action string `json:"action"` | ||
| 44 | Direction string `json:"direction"` | 45 | Direction string `json:"direction"` | ||
| 45 | - | PaneID string `json:"paneId"` | |||
| 46 | // History operations | 46 | // History operations | ||
| 47 | HistoryID string `json:"historyId"` | 47 | HistoryID string `json:"historyId"` | ||
| 48 | // Request tracking | 48 | // Request tracking | ||
| @@ -129,6 +129,8 @@ func (h *Handler) Handle(payload string) { | |||||
| 129 | h.handleClosePopup(msg) | 129 | h.handleClosePopup(msg) | ||
| 130 | case "console-message": | 130 | case "console-message": | ||
| 131 | h.handleConsoleMessage(msg) | 131 | h.handleConsoleMessage(msg) | ||
| 132 | + | case "request-webview-id": | |||
| 133 | + | h.handleWebViewIDRequest(msg) | |||
| 132 | } | 134 | } | ||
| 133 | } | 135 | } | ||
| 134 | 136 | ||||
| @@ -602,3 +604,22 @@ func (h *Handler) handleConsoleMessage(msg Message) { | |||||
| 602 | // Send to logging system with [CONSOLE] tag | 604 | // Send to logging system with [CONSOLE] tag | ||
| 603 | logging.CaptureWebKitLog(consolePayload.Message) | 605 | logging.CaptureWebKitLog(consolePayload.Message) | ||
| 604 | } | 606 | } | ||
| 607 | + | ||||
| 608 | + | // handleWebViewIDRequest responds to JavaScript requests for the webview ID | |||
| 609 | + | func (h *Handler) handleWebViewIDRequest(msg Message) { | |||
| 610 | + | if h.webView == nil { | |||
| 611 | + | log.Printf("[messaging] Cannot provide webview ID - no webview available") | |||
| 612 | + | return | |||
| 613 | + | } | |||
| 614 | + | ||||
| 615 | + | webViewID := h.webView.ID() | |||
| 616 | + | log.Printf("[messaging] Sending webview ID %s to JavaScript", webViewID) | |||
| 617 | + | ||||
| 618 | + | // Send the webview ID back to JavaScript via custom event | |||
| 619 | + | if err := h.webView.DispatchCustomEvent("dumber:webview-id", map[string]any{ | |||
| 620 | + | "webviewId": webViewID, | |||
| 621 | + | "timestamp": time.Now().UnixMilli(), | |||
| 622 | + | }); err != nil { | |||
| 623 | + | log.Printf("[messaging] Failed to send webview ID: %v", err) | |||
| 624 | + | } | |||
| 625 | + | } | |||
M pkg/webkit/webview.go +5 -0
| @@ -278,3 +278,8 @@ func (w *WebView) ID() string { | |||
| 278 | 278 | } | |
| 279 | 279 | return strconv.FormatUint(uint64(w.id), 10) | |
| 280 | 280 | } | |
| 281 | + | ||
| 282 | + | // PrefersDarkTheme returns true if GTK is configured to prefer dark theme (stub) | |
| 283 | + | func PrefersDarkTheme() bool { | |
| 284 | + | return false // Fallback to light theme when not using CGO | |
| 285 | + | } | |
| @@ -278,3 +278,8 @@ func (w *WebView) ID() string { | |||||
| 278 | } | 278 | } | ||
| 279 | return strconv.FormatUint(uint64(w.id), 10) | 279 | return strconv.FormatUint(uint64(w.id), 10) | ||
| 280 | } | 280 | } | ||
| 281 | + | ||||
| 282 | + | // PrefersDarkTheme returns true if GTK is configured to prefer dark theme (stub) | |||
| 283 | + | func PrefersDarkTheme() bool { | |||
| 284 | + | return false // Fallback to light theme when not using CGO | |||
| 285 | + | } | |||