fix(workspace): load URL in new split panes to enable script execution
New split panes were showing white screens because injected GUI scripts never executed. Root cause: handlePaneSplit() created new panes via splitNode() but never called clonePaneState() to load a URL. Without a document load, WebKit doesn't execute document-start scripts, so the GUI bootstrap never ran. Changes: - workspace_pane_mode.go: Call clonePaneState() after splitNode() - workspace_pane_ops.go: Load about:blank with diagnostic logging - gui/src/injected/gui.ts: Always call bootstrapGUI() (remove conditional) - gui/src/injected/bootstrap.ts: Auto-open omnibox on about:blank pages New split panes now load about:blank, trigger script execution, and auto-open the omnibox for immediate navigation.
- commit
- 5675ccb77993949c08e2963c87457fc124adcfd1
- author
- bamen · · 2025-10-13 17:52 UTC
- committer
- bamen · · 2025-10-13 17:53 UTC
- parent
- 8b68c9e
- signature
- unsigned
4 files · +25 -20
M gui/src/injected/bootstrap.ts +8 -0
| @@ -262,6 +262,14 @@ export function bootstrapGUI(): void { | |||
| 262 | 262 | try { | |
| 263 | 263 | await initializeOmnibox(); | |
| 264 | 264 | console.log("✅ Omnibox system initialized successfully"); | |
| 265 | + | ||
| 266 | + | // Auto-open omnibox on about:blank pages | |
| 267 | + | if (window.location.href === "about:blank" && window.__dumber_omnibox) { | |
| 268 | + | console.log("[about:blank] Auto-opening omnibox"); | |
| 269 | + | setTimeout(() => { | |
| 270 | + | window.__dumber_omnibox?.open?.("omnibox", ""); | |
| 271 | + | }, 100); | |
| 272 | + | } | |
| 265 | 273 | } catch (omniboxError) { | |
| 266 | 274 | console.error("❌ Failed to initialize omnibox system:", omniboxError); | |
| 267 | 275 | } | |
| @@ -262,6 +262,14 @@ export function bootstrapGUI(): void { | |||||
| 262 | try { | 262 | try { | ||
| 263 | await initializeOmnibox(); | 263 | await initializeOmnibox(); | ||
| 264 | console.log("✅ Omnibox system initialized successfully"); | 264 | console.log("✅ Omnibox system initialized successfully"); | ||
| 265 | + | ||||
| 266 | + | // Auto-open omnibox on about:blank pages | |||
| 267 | + | if (window.location.href === "about:blank" && window.__dumber_omnibox) { | |||
| 268 | + | console.log("[about:blank] Auto-opening omnibox"); | |||
| 269 | + | setTimeout(() => { | |||
| 270 | + | window.__dumber_omnibox?.open?.("omnibox", ""); | |||
| 271 | + | }, 100); | |||
| 272 | + | } | |||
| 265 | } catch (omniboxError) { | 273 | } catch (omniboxError) { | ||
| 266 | console.error("❌ Failed to initialize omnibox system:", omniboxError); | 274 | console.error("❌ Failed to initialize omnibox system:", omniboxError); | ||
| 267 | } | 275 | } | ||
M gui/src/injected/gui.ts +5 -16
| @@ -3,23 +3,12 @@ | |||
| 3 | 3 | * | |
| 4 | 4 | * Delegates to the shared bootstrap so special pages can reuse the same setup | |
| 5 | 5 | * without depending on this injected build. | |
| 6 | + | * | |
| 7 | + | * Always calls bootstrapGUI() - deduplication is handled inside bootstrap.ts | |
| 8 | + | * via window.__dumber_gui_ready_for check. This ensures GUI loads in all | |
| 9 | + | * contexts including new split panes and about:blank pages. | |
| 6 | 10 | */ | |
| 7 | 11 | ||
| 8 | 12 | import { bootstrapGUI } from "./bootstrap"; | |
| 9 | 13 | ||
| 10 | - | const href = window.location.href; | |
| 11 | - | const protocol = window.location.protocol; | |
| 12 | - | const host = window.location.host || ""; | |
| 13 | - | ||
| 14 | - | const isDumbProtocol = protocol === "dumb:" || href.startsWith("dumb://"); | |
| 15 | - | const dumbHost = host.toLowerCase(); | |
| 16 | - | const shouldSkip = | |
| 17 | - | isDumbProtocol && | |
| 18 | - | (href.toLowerCase().startsWith("dumb://home") || | |
| 19 | - | href.toLowerCase().startsWith("dumb://homepage") || | |
| 20 | - | dumbHost === "home" || | |
| 21 | - | dumbHost === "homepage"); | |
| 22 | - | ||
| 23 | - | if (!shouldSkip) { | |
| 24 | - | bootstrapGUI(); | |
| 25 | - | } | |
| 14 | + | bootstrapGUI(); | |
| @@ -3,23 +3,12 @@ | |||||
| 3 | * | 3 | * | ||
| 4 | * Delegates to the shared bootstrap so special pages can reuse the same setup | 4 | * Delegates to the shared bootstrap so special pages can reuse the same setup | ||
| 5 | * without depending on this injected build. | 5 | * without depending on this injected build. | ||
| 6 | + | * | |||
| 7 | + | * Always calls bootstrapGUI() - deduplication is handled inside bootstrap.ts | |||
| 8 | + | * via window.__dumber_gui_ready_for check. This ensures GUI loads in all | |||
| 9 | + | * contexts including new split panes and about:blank pages. | |||
| 6 | */ | 10 | */ | ||
| 7 | 11 | ||||
| 8 | import { bootstrapGUI } from "./bootstrap"; | 12 | import { bootstrapGUI } from "./bootstrap"; | ||
| 9 | 13 | ||||
| 10 | - | const href = window.location.href; | 14 | + | bootstrapGUI(); |
| 11 | - | const protocol = window.location.protocol; | |||
| 12 | - | const host = window.location.host || ""; | |||
| 13 | - | ||||
| 14 | - | const isDumbProtocol = protocol === "dumb:" || href.startsWith("dumb://"); | |||
| 15 | - | const dumbHost = host.toLowerCase(); | |||
| 16 | - | const shouldSkip = | |||
| 17 | - | isDumbProtocol && | |||
| 18 | - | (href.toLowerCase().startsWith("dumb://home") || | |||
| 19 | - | href.toLowerCase().startsWith("dumb://homepage") || | |||
| 20 | - | dumbHost === "home" || | |||
| 21 | - | dumbHost === "homepage"); | |||
| 22 | - | ||||
| 23 | - | if (!shouldSkip) { | |||
| 24 | - | bootstrapGUI(); | |||
| 25 | - | } | |||
M internal/app/browser/workspace_pane_mode.go +5 -1
| @@ -144,11 +144,15 @@ func (wm *WorkspaceManager) handlePaneSplit(node *paneNode, action string) { | |||
| 144 | 144 | wm.dispatchPaneModeEvent("action", action) | |
| 145 | 145 | ||
| 146 | 146 | // Perform the split (pass nil so it creates a fresh pane, not popup mode) | |
| 147 | - | if _, err := wm.splitNode(node, direction, nil); err != nil { | |
| 147 | + | newNode, err := wm.splitNode(node, direction, nil) | |
| 148 | + | if err != nil { | |
| 148 | 149 | log.Printf("[pane-mode] Failed to split pane: %v", err) | |
| 149 | 150 | return | |
| 150 | 151 | } | |
| 151 | 152 | ||
| 153 | + | // Load initial URL in the new pane so scripts execute | |
| 154 | + | wm.clonePaneState(node, newNode) | |
| 155 | + | ||
| 152 | 156 | log.Printf("[pane-mode] Split successful: direction=%s", direction) | |
| 153 | 157 | } | |
| 154 | 158 | ||
| @@ -144,11 +144,15 @@ func (wm *WorkspaceManager) handlePaneSplit(node *paneNode, action string) { | |||||
| 144 | wm.dispatchPaneModeEvent("action", action) | 144 | wm.dispatchPaneModeEvent("action", action) | ||
| 145 | 145 | ||||
| 146 | // Perform the split (pass nil so it creates a fresh pane, not popup mode) | 146 | // Perform the split (pass nil so it creates a fresh pane, not popup mode) | ||
| 147 | - | if _, err := wm.splitNode(node, direction, nil); err != nil { | 147 | + | newNode, err := wm.splitNode(node, direction, nil) |
| 148 | + | if err != nil { | |||
| 148 | log.Printf("[pane-mode] Failed to split pane: %v", err) | 149 | log.Printf("[pane-mode] Failed to split pane: %v", err) | ||
| 149 | return | 150 | return | ||
| 150 | } | 151 | } | ||
| 151 | 152 | ||||
| 153 | + | // Load initial URL in the new pane so scripts execute | |||
| 154 | + | wm.clonePaneState(node, newNode) | |||
| 155 | + | ||||
| 152 | log.Printf("[pane-mode] Split successful: direction=%s", direction) | 156 | log.Printf("[pane-mode] Split successful: direction=%s", direction) | ||
| 153 | } | 157 | } | ||
| 154 | 158 | ||||
M internal/app/browser/workspace_pane_ops.go +7 -3
| @@ -114,19 +114,23 @@ func (wm *WorkspaceManager) createPane(view *webkit.WebView) (*BrowserPane, erro | |||
| 114 | 114 | // clonePaneState sets up a new pane with cloned state from another pane | |
| 115 | 115 | func (wm *WorkspaceManager) clonePaneState(_ *paneNode, target *paneNode) { | |
| 116 | 116 | if wm == nil || target == nil { | |
| 117 | + | log.Printf("[workspace] clonePaneState: wm or target is nil") | |
| 117 | 118 | return | |
| 118 | 119 | } | |
| 119 | 120 | if target.pane == nil || target.pane.webView == nil { | |
| 121 | + | log.Printf("[workspace] clonePaneState: target pane or webView is nil") | |
| 120 | 122 | return | |
| 121 | 123 | } | |
| 122 | 124 | ||
| 125 | + | // Load about:blank and auto-open omnibox | |
| 123 | 126 | const blankURL = "about:blank" | |
| 124 | 127 | ||
| 128 | + | log.Printf("[workspace] clonePaneState: loading %s in pane %p", blankURL, target.pane) | |
| 125 | 129 | if err := target.pane.webView.LoadURL(blankURL); err != nil { | |
| 126 | - | log.Printf("[workspace] failed to prepare blank pane: %v", err) | |
| 130 | + | log.Printf("[workspace] failed to load blank page in new pane: %v", err) | |
| 131 | + | } else { | |
| 132 | + | log.Printf("[workspace] successfully initiated load of %s", blankURL) | |
| 127 | 133 | } | |
| 128 | - | ||
| 129 | - | // Omnibox will auto-open on about:blank via client-side detection | |
| 130 | 134 | } | |
| 131 | 135 | ||
| 132 | 136 | // safelyDetachControllersBeforeReparent marks nodes for controller reattachment after reparenting. | |
| @@ -114,19 +114,23 @@ func (wm *WorkspaceManager) createPane(view *webkit.WebView) (*BrowserPane, erro | |||||
| 114 | // clonePaneState sets up a new pane with cloned state from another pane | 114 | // clonePaneState sets up a new pane with cloned state from another pane | ||
| 115 | func (wm *WorkspaceManager) clonePaneState(_ *paneNode, target *paneNode) { | 115 | func (wm *WorkspaceManager) clonePaneState(_ *paneNode, target *paneNode) { | ||
| 116 | if wm == nil || target == nil { | 116 | if wm == nil || target == nil { | ||
| 117 | + | log.Printf("[workspace] clonePaneState: wm or target is nil") | |||
| 117 | return | 118 | return | ||
| 118 | } | 119 | } | ||
| 119 | if target.pane == nil || target.pane.webView == nil { | 120 | if target.pane == nil || target.pane.webView == nil { | ||
| 121 | + | log.Printf("[workspace] clonePaneState: target pane or webView is nil") | |||
| 120 | return | 122 | return | ||
| 121 | } | 123 | } | ||
| 122 | 124 | ||||
| 125 | + | // Load about:blank and auto-open omnibox | |||
| 123 | const blankURL = "about:blank" | 126 | const blankURL = "about:blank" | ||
| 124 | 127 | ||||
| 128 | + | log.Printf("[workspace] clonePaneState: loading %s in pane %p", blankURL, target.pane) | |||
| 125 | if err := target.pane.webView.LoadURL(blankURL); err != nil { | 129 | if err := target.pane.webView.LoadURL(blankURL); err != nil { | ||
| 126 | - | log.Printf("[workspace] failed to prepare blank pane: %v", err) | 130 | + | log.Printf("[workspace] failed to load blank page in new pane: %v", err) |
| 131 | + | } else { | |||
| 132 | + | log.Printf("[workspace] successfully initiated load of %s", blankURL) | |||
| 127 | } | 133 | } | ||
| 128 | - | ||||
| 129 | - | // Omnibox will auto-open on about:blank via client-side detection | |||
| 130 | } | 134 | } | ||
| 131 | 135 | ||||
| 132 | // safelyDetachControllersBeforeReparent marks nodes for controller reattachment after reparenting. | 136 | // safelyDetachControllersBeforeReparent marks nodes for controller reattachment after reparenting. | ||