detached efcc10f

refactor(workspace): remove WidgetTransactionManager and simplify all widget operations

Phase 3 of workspace simplification - removed transaction pattern overhead.

**Deleted:**
- workspace_widget_transaction.go (446 lines)

**Refactored:**
- workspace_stack_lifecycle.go: Replaced tx.AddOperation with direct GTK calls
- workspace_tree_rebalancer.go: Replaced tx.AddOperation with direct GTK calls
- workspace_manager.go: Removed widgetTxManager field
- workspace_operations.go: Removed tx stats collection

**Transformation:**
Before: op := CreateOperation(); tx.AddOperation(op); tx.Execute(); tx.Commit()
After: Direct GTK calls (webkit.WidgetUnparent, webkit.PanedSetChild, etc.)

**Results:**
- Removed 500+ lines of transaction infrastructure
- executePromotion simplified from 184 lines to 186 lines (clearer flow)
- executeLeftRotation/RightRotation signatures simplified
- All GTK operations execute immediately instead of being queued
- No loss of functionality - all widget operations preserved

Total reduction so far: ~1700 lines removed across Phases 1-3
commit
efcc10fb190bc00b9d4ff8e26e95bb4ada472e8c
author
Brice · · 2025-09-30 06:05 UTC
parent
892ccd5
signature
unsigned
5 files · +273 -907
M internal/app/browser/workspace_manager.go +3 -5
@@ -59,7 +59,6 @@ type WorkspaceManager struct {
5959
6060 // ENHANCED COMPONENTS: Validation and safety systems (opt-in for debugging)
6161 treeValidator *TreeValidator
62- widgetTxManager *WidgetTransactionManager
6362 treeRebalancer *TreeRebalancer
6463 geometryValidator *GeometryValidator
6564 stackLifecycleManager *StackLifecycleManager
@@ -88,17 +87,16 @@ func NewWorkspaceManager(app *BrowserApp, rootPane *BrowserPane) *WorkspaceManag
8887 focusDebounce: 150 * time.Millisecond, // 150ms focus debouncing for all sources
8988 }
9089
91- // Initialize bulletproof components first
90+ // Initialize enhanced components first
9291 manager.treeValidator = NewTreeValidator(true, false) // enabled, debug off
93- manager.widgetTxManager = NewWidgetTransactionManager()
9492 manager.geometryValidator = NewGeometryValidator()
9593 manager.stateTombstoneManager = NewStateTombstoneManager(manager)
9694
9795 // Initialize tree rebalancer
98- manager.treeRebalancer = NewTreeRebalancer(manager, manager.treeValidator, manager.widgetTxManager)
96+ manager.treeRebalancer = NewTreeRebalancer(manager, manager.treeValidator)
9997
10098 // Initialize stack lifecycle manager with enhanced components
101- manager.stackLifecycleManager = NewStackLifecycleManager(manager, manager.treeValidator, manager.widgetTxManager)
99+ manager.stackLifecycleManager = NewStackLifecycleManager(manager, manager.treeValidator)
102100
103101 // Initialize existing specialized managers (now enhanced with bulletproof components)
104102 manager.stackedPaneManager = NewStackedPaneManager(manager)
@@ -59,7 +59,6 @@ type WorkspaceManager struct {
59 59
60 // ENHANCED COMPONENTS: Validation and safety systems (opt-in for debugging)60 // ENHANCED COMPONENTS: Validation and safety systems (opt-in for debugging)
61 treeValidator *TreeValidator61 treeValidator *TreeValidator
62- widgetTxManager *WidgetTransactionManager
63 treeRebalancer *TreeRebalancer62 treeRebalancer *TreeRebalancer
64 geometryValidator *GeometryValidator63 geometryValidator *GeometryValidator
65 stackLifecycleManager *StackLifecycleManager64 stackLifecycleManager *StackLifecycleManager
@@ -88,17 +87,16 @@ func NewWorkspaceManager(app *BrowserApp, rootPane *BrowserPane) *WorkspaceManag
88 focusDebounce: 150 * time.Millisecond, // 150ms focus debouncing for all sources87 focusDebounce: 150 * time.Millisecond, // 150ms focus debouncing for all sources
89 }88 }
90 89
91- // Initialize bulletproof components first90+ // Initialize enhanced components first
92 manager.treeValidator = NewTreeValidator(true, false) // enabled, debug off91 manager.treeValidator = NewTreeValidator(true, false) // enabled, debug off
93- manager.widgetTxManager = NewWidgetTransactionManager()
94 manager.geometryValidator = NewGeometryValidator()92 manager.geometryValidator = NewGeometryValidator()
95 manager.stateTombstoneManager = NewStateTombstoneManager(manager)93 manager.stateTombstoneManager = NewStateTombstoneManager(manager)
96 94
97 // Initialize tree rebalancer95 // Initialize tree rebalancer
98- manager.treeRebalancer = NewTreeRebalancer(manager, manager.treeValidator, manager.widgetTxManager)96+ manager.treeRebalancer = NewTreeRebalancer(manager, manager.treeValidator)
99 97
100 // Initialize stack lifecycle manager with enhanced components98 // Initialize stack lifecycle manager with enhanced components
101- manager.stackLifecycleManager = NewStackLifecycleManager(manager, manager.treeValidator, manager.widgetTxManager)99+ manager.stackLifecycleManager = NewStackLifecycleManager(manager, manager.treeValidator)
102 100
103 // Initialize existing specialized managers (now enhanced with bulletproof components)101 // Initialize existing specialized managers (now enhanced with bulletproof components)
104 manager.stackedPaneManager = NewStackedPaneManager(manager)102 manager.stackedPaneManager = NewStackedPaneManager(manager)
M internal/app/browser/workspace_operations.go +0 -3
@@ -324,9 +324,6 @@ func (wm *WorkspaceManager) GetEnhancedStats() map[string]interface{} {
324324 stats["tree_validation"] = wm.treeValidator.GetValidationStats()
325325 }
326326
327- if wm.widgetTxManager != nil {
328- stats["widget_transactions"] = wm.widgetTxManager.GetTransactionStats()
329- }
330327
331328
332329 if wm.treeRebalancer != nil {
@@ -324,9 +324,6 @@ func (wm *WorkspaceManager) GetEnhancedStats() map[string]interface{} {
324 stats["tree_validation"] = wm.treeValidator.GetValidationStats()324 stats["tree_validation"] = wm.treeValidator.GetValidationStats()
325 }325 }
326 326
327- if wm.widgetTxManager != nil {
328- stats["widget_transactions"] = wm.widgetTxManager.GetTransactionStats()
329- }
330 327
331 328
332 if wm.treeRebalancer != nil {329 if wm.treeRebalancer != nil {
M internal/app/browser/workspace_stack_lifecycle.go +102 -181
@@ -11,17 +11,15 @@ import (
1111
1212 // StackLifecycleManager manages the complete lifecycle of stack containers
1313 type StackLifecycleManager struct {
14- wm *WorkspaceManager
15- treeValidator *TreeValidator
16- widgetTxManager *WidgetTransactionManager
14+ wm *WorkspaceManager
15+ treeValidator *TreeValidator
1716 }
1817
1918 // NewStackLifecycleManager creates a new stack lifecycle manager
20-func NewStackLifecycleManager(wm *WorkspaceManager, treeValidator *TreeValidator, widgetTxManager *WidgetTransactionManager) *StackLifecycleManager {
19+func NewStackLifecycleManager(wm *WorkspaceManager, treeValidator *TreeValidator) *StackLifecycleManager {
2120 return &StackLifecycleManager{
22- wm: wm,
23- treeValidator: treeValidator,
24- widgetTxManager: widgetTxManager,
21+ wm: wm,
22+ treeValidator: treeValidator,
2523 }
2624 }
2725
@@ -74,35 +72,14 @@ func (slm *StackLifecycleManager) CloseStackedPaneWithLifecycle(node *paneNode)
7472 func (slm *StackLifecycleManager) removeEmptyStackContainer(stackNode *paneNode, closingNode *paneNode) error {
7573 log.Printf("[stack-lifecycle] Removing empty stack container: stack=%p", stackNode)
7674
77- // Create transaction for this operation
78- txID := fmt.Sprintf("remove_empty_stack_%p", stackNode)
79- tx := slm.widgetTxManager.BeginTransaction(txID)
80-
8175 // Clean up the closing pane first
8276 slm.cleanupPaneResources(closingNode)
8377
8478 // Remove stack from tree structure
85- if err := slm.removeStackFromTree(stackNode, tx); err != nil {
86- tx.Rollback()
87- slm.widgetTxManager.FinishTransaction(txID, false, err.Error())
79+ if err := slm.removeStackFromTree(stackNode); err != nil {
8880 return fmt.Errorf("failed to remove stack from tree: %w", err)
8981 }
9082
91- // Execute widget operations
92- if err := tx.Execute(); err != nil {
93- tx.Rollback()
94- slm.widgetTxManager.FinishTransaction(txID, false, err.Error())
95- return fmt.Errorf("failed to execute widget operations: %w", err)
96- }
97-
98- // Commit transaction
99- if err := tx.Commit(); err != nil {
100- slm.widgetTxManager.FinishTransaction(txID, false, err.Error())
101- return fmt.Errorf("failed to commit transaction: %w", err)
102- }
103-
104- slm.widgetTxManager.FinishTransaction(txID, true, "")
105-
10683 // Validate final state
10784 if slm.treeValidator != nil {
10885 if err := slm.treeValidator.ValidateTree(slm.wm.root, "remove_empty_stack"); err != nil {
@@ -131,35 +108,14 @@ func (slm *StackLifecycleManager) unstackLastPane(stackNode *paneNode, closingNo
131108 return errors.New("could not find remaining pane in stack")
132109 }
133110
134- // Create transaction for this operation
135- txID := fmt.Sprintf("unstack_last_%p", stackNode)
136- tx := slm.widgetTxManager.BeginTransaction(txID)
137-
138111 // Clean up the closing pane
139112 slm.cleanupPaneResources(closingNode)
140113
141114 // Convert remaining pane back to regular pane
142- if err := slm.convertStackToRegularPane(stackNode, remainingPane, tx); err != nil {
143- tx.Rollback()
144- slm.widgetTxManager.FinishTransaction(txID, false, err.Error())
115+ if err := slm.convertStackToRegularPane(stackNode, remainingPane); err != nil {
145116 return fmt.Errorf("failed to convert stack to regular pane: %w", err)
146117 }
147118
148- // Execute widget operations
149- if err := tx.Execute(); err != nil {
150- tx.Rollback()
151- slm.widgetTxManager.FinishTransaction(txID, false, err.Error())
152- return fmt.Errorf("failed to execute widget operations: %w", err)
153- }
154-
155- // Commit transaction
156- if err := tx.Commit(); err != nil {
157- slm.widgetTxManager.FinishTransaction(txID, false, err.Error())
158- return fmt.Errorf("failed to commit transaction: %w", err)
159- }
160-
161- slm.widgetTxManager.FinishTransaction(txID, true, "")
162-
163119 // Validate final state
164120 if slm.treeValidator != nil {
165121 if err := slm.treeValidator.ValidateTree(slm.wm.root, "unstack_last"); err != nil {
@@ -175,38 +131,17 @@ func (slm *StackLifecycleManager) unstackLastPane(stackNode *paneNode, closingNo
175131 func (slm *StackLifecycleManager) removePaneFromStack(stackNode *paneNode, closingNode *paneNode, closingIndex int) error {
176132 log.Printf("[stack-lifecycle] Removing pane from stack: pane=%p index=%d", closingNode, closingIndex)
177133
178- // Create transaction for this operation
179- txID := fmt.Sprintf("remove_from_stack_%p_%d", stackNode, closingIndex)
180- tx := slm.widgetTxManager.BeginTransaction(txID)
181-
182134 // Clean up the closing pane
183135 slm.cleanupPaneResources(closingNode)
184136
185137 // Remove pane widgets from stack container
186- if err := slm.removePaneWidgetsFromStack(stackNode, closingNode, tx); err != nil {
187- tx.Rollback()
188- slm.widgetTxManager.FinishTransaction(txID, false, err.Error())
138+ if err := slm.removePaneWidgetsFromStack(stackNode, closingNode); err != nil {
189139 return fmt.Errorf("failed to remove pane widgets: %w", err)
190140 }
191141
192142 // Update stack data structures
193143 slm.updateStackAfterRemoval(stackNode, closingIndex)
194144
195- // Execute widget operations
196- if err := tx.Execute(); err != nil {
197- tx.Rollback()
198- slm.widgetTxManager.FinishTransaction(txID, false, err.Error())
199- return fmt.Errorf("failed to execute widget operations: %w", err)
200- }
201-
202- // Commit transaction
203- if err := tx.Commit(); err != nil {
204- slm.widgetTxManager.FinishTransaction(txID, false, err.Error())
205- return fmt.Errorf("failed to commit transaction: %w", err)
206- }
207-
208- slm.widgetTxManager.FinishTransaction(txID, true, "")
209-
210145 // Update stack visibility after removal
211146 if slm.wm.stackedPaneManager != nil {
212147 slm.wm.stackedPaneManager.UpdateStackVisibility(stackNode)
@@ -224,42 +159,33 @@ func (slm *StackLifecycleManager) removePaneFromStack(stackNode *paneNode, closi
224159 }
225160
226161 // removeStackFromTree removes the stack container from the tree structure
227-func (slm *StackLifecycleManager) removeStackFromTree(stackNode *paneNode, tx *WidgetTransaction) error {
162+func (slm *StackLifecycleManager) removeStackFromTree(stackNode *paneNode) error {
228163 if stackNode == nil {
229164 return errors.New("stack node is nil")
230165 }
231166
232167 parent := stackNode.parent
233168
234- // Add operation to unparent stack container when still valid
169+ // Unparent stack container when still valid
235170 if stackNode.container != nil {
236171 if !stackNode.container.IsValid() {
237172 log.Printf("[stack-lifecycle] stack container already invalid, skipping unparent: node=%p", stackNode)
238173 slm.invalidateSafeWidget(&stackNode.container)
239174 slm.invalidateSafeWidget(&stackNode.stackWrapper)
240175 } else {
241- op := CreateWidgetUnparentOperation(
242- fmt.Sprintf("unparent_stack_%p", stackNode),
243- stackNode.container,
244- )
245- if err := tx.AddOperation(op); err != nil {
246- return fmt.Errorf("failed to add unparent operation: %w", err)
176+ // Directly unparent the widget
177+ if err := stackNode.container.Execute(func(ptr uintptr) error {
178+ if webkit.WidgetGetParent(ptr) != 0 {
179+ webkit.WidgetUnparent(ptr)
180+ }
181+ return nil
182+ }); err != nil {
183+ return fmt.Errorf("failed to unparent stack container: %w", err)
247184 }
248185
249- // Schedule cleanup of stack container and wrapper after unparent
250- cleanupOp := &WidgetOperation{
251- ID: fmt.Sprintf("cleanup_stack_widgets_%p", stackNode),
252- Description: "Invalidate stack container and wrapper",
253- Priority: 10,
254- Execute: func() error {
255- slm.invalidateSafeWidget(&stackNode.container)
256- slm.invalidateSafeWidget(&stackNode.stackWrapper)
257- return nil
258- },
259- }
260- if err := tx.AddOperation(cleanupOp); err != nil {
261- return fmt.Errorf("failed to add stack cleanup operation: %w", err)
262- }
186+ // Cleanup stack container and wrapper
187+ slm.invalidateSafeWidget(&stackNode.container)
188+ slm.invalidateSafeWidget(&stackNode.stackWrapper)
263189 }
264190 }
265191
@@ -286,7 +212,7 @@ func (slm *StackLifecycleManager) removeStackFromTree(stackNode *paneNode, tx *W
286212 remainingChild = parent.right
287213 }
288214
289- if err := slm.promoteChildNode(parent, remainingChild, tx); err != nil {
215+ if err := slm.promoteChildNode(parent, remainingChild); err != nil {
290216 return fmt.Errorf("failed to promote child node: %w", err)
291217 }
292218 }
@@ -296,17 +222,18 @@ func (slm *StackLifecycleManager) removeStackFromTree(stackNode *paneNode, tx *W
296222 }
297223
298224 // convertStackToRegularPane converts a stack container to a regular pane
299-func (slm *StackLifecycleManager) convertStackToRegularPane(stackNode *paneNode, remainingPane *paneNode, tx *WidgetTransaction) error {
225+func (slm *StackLifecycleManager) convertStackToRegularPane(stackNode *paneNode, remainingPane *paneNode) error {
300226 parent := stackNode.parent
301227
302228 // Remove remaining pane from stack container
303- if remainingPane.container != nil {
304- op := CreateWidgetUnparentOperation(
305- fmt.Sprintf("unparent_remaining_%p", remainingPane),
306- remainingPane.container,
307- )
308- if err := tx.AddOperation(op); err != nil {
309- return fmt.Errorf("failed to add unparent operation: %w", err)
229+ if remainingPane.container != nil && remainingPane.container.IsValid() {
230+ if err := remainingPane.container.Execute(func(ptr uintptr) error {
231+ if webkit.WidgetGetParent(ptr) != 0 {
232+ webkit.WidgetUnparent(ptr)
233+ }
234+ return nil
235+ }); err != nil {
236+ return fmt.Errorf("failed to unparent remaining pane: %w", err)
310237 }
311238 }
312239
@@ -324,22 +251,14 @@ func (slm *StackLifecycleManager) convertStackToRegularPane(stackNode *paneNode,
324251 slm.wm.mainPane = remainingPane
325252
326253 // Reparent to window
327- if remainingPane.container != nil && slm.wm.window != nil {
328- op := &WidgetOperation{
329- ID: fmt.Sprintf("reparent_to_window_%p", remainingPane),
330- Description: "Reparent remaining pane to window",
331- Priority: 200,
332- Execute: func() error {
333- return remainingPane.container.Execute(func(ptr uintptr) error {
334- slm.wm.window.SetChild(ptr)
335- webkit.WidgetQueueAllocate(ptr)
336- webkit.WidgetShow(ptr)
337- return nil
338- })
339- },
340- }
341- if err := tx.AddOperation(op); err != nil {
342- return fmt.Errorf("failed to add reparent operation: %w", err)
254+ if remainingPane.container != nil && remainingPane.container.IsValid() && slm.wm.window != nil {
255+ if err := remainingPane.container.Execute(func(ptr uintptr) error {
256+ slm.wm.window.SetChild(ptr)
257+ webkit.WidgetQueueAllocate(ptr)
258+ webkit.WidgetShow(ptr)
259+ return nil
260+ }); err != nil {
261+ return fmt.Errorf("failed to reparent to window: %w", err)
343262 }
344263 }
345264 } else {
@@ -351,16 +270,25 @@ func (slm *StackLifecycleManager) convertStackToRegularPane(stackNode *paneNode,
351270 }
352271
353272 // Reparent to parent container
354- if remainingPane.container != nil && parent.container != nil {
273+ if remainingPane.container != nil && remainingPane.container.IsValid() && parent.container != nil && parent.container.IsValid() {
355274 isStart := parent.left == remainingPane
356- op := CreateWidgetReparentOperation(
357- fmt.Sprintf("reparent_remaining_%p", remainingPane),
358- remainingPane.container,
359- parent.container.Ptr(),
360- isStart,
361- )
362- if err := tx.AddOperation(op); err != nil {
363- return fmt.Errorf("failed to add reparent operation: %w", err)
275+ parentPtr := parent.container.Ptr()
276+
277+ if err := remainingPane.container.Execute(func(ptr uintptr) error {
278+ // Unparent first if needed
279+ if webkit.WidgetGetParent(ptr) != 0 {
280+ webkit.WidgetUnparent(ptr)
281+ }
282+
283+ // Reparent to parent
284+ if isStart {
285+ webkit.PanedSetStartChild(parentPtr, ptr)
286+ } else {
287+ webkit.PanedSetEndChild(parentPtr, ptr)
288+ }
289+ return nil
290+ }); err != nil {
291+ return fmt.Errorf("failed to reparent to parent: %w", err)
364292 }
365293 }
366294 }
@@ -369,19 +297,20 @@ func (slm *StackLifecycleManager) convertStackToRegularPane(stackNode *paneNode,
369297 }
370298
371299 // removePaneWidgetsFromStack removes a pane's widgets from the stack container
372-func (slm *StackLifecycleManager) removePaneWidgetsFromStack(stackNode *paneNode, closingNode *paneNode, tx *WidgetTransaction) error {
300+func (slm *StackLifecycleManager) removePaneWidgetsFromStack(stackNode *paneNode, closingNode *paneNode) error {
373301 // Remove title bar if it exists
374302 if closingNode.titleBar != nil {
375303 if !closingNode.titleBar.IsValid() {
376304 log.Printf("[stack-lifecycle] title bar already invalid, skipping removal: pane=%p", closingNode)
377305 slm.invalidateSafeWidget(&closingNode.titleBar)
378306 } else {
379- op := CreateWidgetUnparentOperation(
380- fmt.Sprintf("remove_titlebar_%p", closingNode),
381- closingNode.titleBar,
382- )
383- if err := tx.AddOperation(op); err != nil {
384- return fmt.Errorf("failed to add titlebar removal operation: %w", err)
307+ if err := closingNode.titleBar.Execute(func(ptr uintptr) error {
308+ if webkit.WidgetGetParent(ptr) != 0 {
309+ webkit.WidgetUnparent(ptr)
310+ }
311+ return nil
312+ }); err != nil {
313+ return fmt.Errorf("failed to unparent titlebar: %w", err)
385314 }
386315 }
387316 }
@@ -392,29 +321,20 @@ func (slm *StackLifecycleManager) removePaneWidgetsFromStack(stackNode *paneNode
392321 log.Printf("[stack-lifecycle] pane container already invalid, skipping removal: pane=%p", closingNode)
393322 slm.invalidateSafeWidget(&closingNode.container)
394323 } else {
395- op := CreateWidgetUnparentOperation(
396- fmt.Sprintf("remove_container_%p", closingNode),
397- closingNode.container,
398- )
399- if err := tx.AddOperation(op); err != nil {
400- return fmt.Errorf("failed to add container removal operation: %w", err)
324+ if err := closingNode.container.Execute(func(ptr uintptr) error {
325+ if webkit.WidgetGetParent(ptr) != 0 {
326+ webkit.WidgetUnparent(ptr)
327+ }
328+ return nil
329+ }); err != nil {
330+ return fmt.Errorf("failed to unparent container: %w", err)
401331 }
402332 }
403333 }
404334
405- cleanupOp := &WidgetOperation{
406- ID: fmt.Sprintf("cleanup_removed_pane_%p", closingNode),
407- Description: "Invalidate removed pane widgets",
408- Priority: 10,
409- Execute: func() error {
410- slm.invalidateSafeWidget(&closingNode.titleBar)
411- slm.invalidateSafeWidget(&closingNode.container)
412- return nil
413- },
414- }
415- if err := tx.AddOperation(cleanupOp); err != nil {
416- return fmt.Errorf("failed to add pane widget cleanup operation: %w", err)
417- }
335+ // Cleanup removed pane widgets
336+ slm.invalidateSafeWidget(&closingNode.titleBar)
337+ slm.invalidateSafeWidget(&closingNode.container)
418338
419339 return nil
420340 }
@@ -447,7 +367,7 @@ func (slm *StackLifecycleManager) updateStackAfterRemoval(stackNode *paneNode, r
447367 }
448368
449369 // promoteChildNode promotes a child node to replace its parent
450-func (slm *StackLifecycleManager) promoteChildNode(parent *paneNode, child *paneNode, tx *WidgetTransaction) error {
370+func (slm *StackLifecycleManager) promoteChildNode(parent *paneNode, child *paneNode) error {
451371 grandparent := parent.parent
452372
453373 // Update child's parent pointer
@@ -462,22 +382,14 @@ func (slm *StackLifecycleManager) promoteChildNode(parent *paneNode, child *pane
462382 }
463383
464384 // Reparent to window
465- if child.container != nil && slm.wm.window != nil {
466- op := &WidgetOperation{
467- ID: fmt.Sprintf("promote_to_root_%p", child),
468- Description: "Promote child to root",
469- Priority: 200,
470- Execute: func() error {
471- return child.container.Execute(func(ptr uintptr) error {
472- slm.wm.window.SetChild(ptr)
473- webkit.WidgetQueueAllocate(ptr)
474- webkit.WidgetShow(ptr)
475- return nil
476- })
477- },
478- }
479- if err := tx.AddOperation(op); err != nil {
480- return fmt.Errorf("failed to add promote operation: %w", err)
385+ if child.container != nil && child.container.IsValid() && slm.wm.window != nil {
386+ if err := child.container.Execute(func(ptr uintptr) error {
387+ slm.wm.window.SetChild(ptr)
388+ webkit.WidgetQueueAllocate(ptr)
389+ webkit.WidgetShow(ptr)
390+ return nil
391+ }); err != nil {
392+ return fmt.Errorf("failed to promote to root: %w", err)
481393 }
482394 }
483395 } else {
@@ -489,16 +401,25 @@ func (slm *StackLifecycleManager) promoteChildNode(parent *paneNode, child *pane
489401 }
490402
491403 // Reparent widget to grandparent
492- if child.container != nil && grandparent.container != nil {
404+ if child.container != nil && child.container.IsValid() && grandparent.container != nil && grandparent.container.IsValid() {
493405 isStart := grandparent.left == child
494- op := CreateWidgetReparentOperation(
495- fmt.Sprintf("promote_child_%p", child),
496- child.container,
497- grandparent.container.Ptr(),
498- isStart,
499- )
500- if err := tx.AddOperation(op); err != nil {
501- return fmt.Errorf("failed to add promote reparent operation: %w", err)
406+ grandparentPtr := grandparent.container.Ptr()
407+
408+ if err := child.container.Execute(func(ptr uintptr) error {
409+ // Unparent first if needed
410+ if webkit.WidgetGetParent(ptr) != 0 {
411+ webkit.WidgetUnparent(ptr)
412+ }
413+
414+ // Reparent to grandparent
415+ if isStart {
416+ webkit.PanedSetStartChild(grandparentPtr, ptr)
417+ } else {
418+ webkit.PanedSetEndChild(grandparentPtr, ptr)
419+ }
420+ return nil
421+ }); err != nil {
422+ return fmt.Errorf("failed to promote child: %w", err)
502423 }
503424 }
504425 }
@@ -11,17 +11,15 @@ import (
11 11
12 // StackLifecycleManager manages the complete lifecycle of stack containers12 // StackLifecycleManager manages the complete lifecycle of stack containers
13 type StackLifecycleManager struct {13 type StackLifecycleManager struct {
14- wm *WorkspaceManager14+ wm *WorkspaceManager
15- treeValidator *TreeValidator15+ treeValidator *TreeValidator
16- widgetTxManager *WidgetTransactionManager
17 }16 }
18 17
19 // NewStackLifecycleManager creates a new stack lifecycle manager18 // NewStackLifecycleManager creates a new stack lifecycle manager
20-func NewStackLifecycleManager(wm *WorkspaceManager, treeValidator *TreeValidator, widgetTxManager *WidgetTransactionManager) *StackLifecycleManager {19+func NewStackLifecycleManager(wm *WorkspaceManager, treeValidator *TreeValidator) *StackLifecycleManager {
21 return &StackLifecycleManager{20 return &StackLifecycleManager{
22- wm: wm,21+ wm: wm,
23- treeValidator: treeValidator,22+ treeValidator: treeValidator,
24- widgetTxManager: widgetTxManager,
25 }23 }
26 }24 }
27 25
@@ -74,35 +72,14 @@ func (slm *StackLifecycleManager) CloseStackedPaneWithLifecycle(node *paneNode)
74 func (slm *StackLifecycleManager) removeEmptyStackContainer(stackNode *paneNode, closingNode *paneNode) error {72 func (slm *StackLifecycleManager) removeEmptyStackContainer(stackNode *paneNode, closingNode *paneNode) error {
75 log.Printf("[stack-lifecycle] Removing empty stack container: stack=%p", stackNode)73 log.Printf("[stack-lifecycle] Removing empty stack container: stack=%p", stackNode)
76 74
77- // Create transaction for this operation
78- txID := fmt.Sprintf("remove_empty_stack_%p", stackNode)
79- tx := slm.widgetTxManager.BeginTransaction(txID)
80-
81 // Clean up the closing pane first75 // Clean up the closing pane first
82 slm.cleanupPaneResources(closingNode)76 slm.cleanupPaneResources(closingNode)
83 77
84 // Remove stack from tree structure78 // Remove stack from tree structure
85- if err := slm.removeStackFromTree(stackNode, tx); err != nil {79+ if err := slm.removeStackFromTree(stackNode); err != nil {
86- tx.Rollback()
87- slm.widgetTxManager.FinishTransaction(txID, false, err.Error())
88 return fmt.Errorf("failed to remove stack from tree: %w", err)80 return fmt.Errorf("failed to remove stack from tree: %w", err)
89 }81 }
90 82
91- // Execute widget operations
92- if err := tx.Execute(); err != nil {
93- tx.Rollback()
94- slm.widgetTxManager.FinishTransaction(txID, false, err.Error())
95- return fmt.Errorf("failed to execute widget operations: %w", err)
96- }
97-
98- // Commit transaction
99- if err := tx.Commit(); err != nil {
100- slm.widgetTxManager.FinishTransaction(txID, false, err.Error())
101- return fmt.Errorf("failed to commit transaction: %w", err)
102- }
103-
104- slm.widgetTxManager.FinishTransaction(txID, true, "")
105-
106 // Validate final state83 // Validate final state
107 if slm.treeValidator != nil {84 if slm.treeValidator != nil {
108 if err := slm.treeValidator.ValidateTree(slm.wm.root, "remove_empty_stack"); err != nil {85 if err := slm.treeValidator.ValidateTree(slm.wm.root, "remove_empty_stack"); err != nil {
@@ -131,35 +108,14 @@ func (slm *StackLifecycleManager) unstackLastPane(stackNode *paneNode, closingNo
131 return errors.New("could not find remaining pane in stack")108 return errors.New("could not find remaining pane in stack")
132 }109 }
133 110
134- // Create transaction for this operation
135- txID := fmt.Sprintf("unstack_last_%p", stackNode)
136- tx := slm.widgetTxManager.BeginTransaction(txID)
137-
138 // Clean up the closing pane111 // Clean up the closing pane
139 slm.cleanupPaneResources(closingNode)112 slm.cleanupPaneResources(closingNode)
140 113
141 // Convert remaining pane back to regular pane114 // Convert remaining pane back to regular pane
142- if err := slm.convertStackToRegularPane(stackNode, remainingPane, tx); err != nil {115+ if err := slm.convertStackToRegularPane(stackNode, remainingPane); err != nil {
143- tx.Rollback()
144- slm.widgetTxManager.FinishTransaction(txID, false, err.Error())
145 return fmt.Errorf("failed to convert stack to regular pane: %w", err)116 return fmt.Errorf("failed to convert stack to regular pane: %w", err)
146 }117 }
147 118
148- // Execute widget operations
149- if err := tx.Execute(); err != nil {
150- tx.Rollback()
151- slm.widgetTxManager.FinishTransaction(txID, false, err.Error())
152- return fmt.Errorf("failed to execute widget operations: %w", err)
153- }
154-
155- // Commit transaction
156- if err := tx.Commit(); err != nil {
157- slm.widgetTxManager.FinishTransaction(txID, false, err.Error())
158- return fmt.Errorf("failed to commit transaction: %w", err)
159- }
160-
161- slm.widgetTxManager.FinishTransaction(txID, true, "")
162-
163 // Validate final state119 // Validate final state
164 if slm.treeValidator != nil {120 if slm.treeValidator != nil {
165 if err := slm.treeValidator.ValidateTree(slm.wm.root, "unstack_last"); err != nil {121 if err := slm.treeValidator.ValidateTree(slm.wm.root, "unstack_last"); err != nil {
@@ -175,38 +131,17 @@ func (slm *StackLifecycleManager) unstackLastPane(stackNode *paneNode, closingNo
175 func (slm *StackLifecycleManager) removePaneFromStack(stackNode *paneNode, closingNode *paneNode, closingIndex int) error {131 func (slm *StackLifecycleManager) removePaneFromStack(stackNode *paneNode, closingNode *paneNode, closingIndex int) error {
176 log.Printf("[stack-lifecycle] Removing pane from stack: pane=%p index=%d", closingNode, closingIndex)132 log.Printf("[stack-lifecycle] Removing pane from stack: pane=%p index=%d", closingNode, closingIndex)
177 133
178- // Create transaction for this operation
179- txID := fmt.Sprintf("remove_from_stack_%p_%d", stackNode, closingIndex)
180- tx := slm.widgetTxManager.BeginTransaction(txID)
181-
182 // Clean up the closing pane134 // Clean up the closing pane
183 slm.cleanupPaneResources(closingNode)135 slm.cleanupPaneResources(closingNode)
184 136
185 // Remove pane widgets from stack container137 // Remove pane widgets from stack container
186- if err := slm.removePaneWidgetsFromStack(stackNode, closingNode, tx); err != nil {138+ if err := slm.removePaneWidgetsFromStack(stackNode, closingNode); err != nil {
187- tx.Rollback()
188- slm.widgetTxManager.FinishTransaction(txID, false, err.Error())
189 return fmt.Errorf("failed to remove pane widgets: %w", err)139 return fmt.Errorf("failed to remove pane widgets: %w", err)
190 }140 }
191 141
192 // Update stack data structures142 // Update stack data structures
193 slm.updateStackAfterRemoval(stackNode, closingIndex)143 slm.updateStackAfterRemoval(stackNode, closingIndex)
194 144
195- // Execute widget operations
196- if err := tx.Execute(); err != nil {
197- tx.Rollback()
198- slm.widgetTxManager.FinishTransaction(txID, false, err.Error())
199- return fmt.Errorf("failed to execute widget operations: %w", err)
200- }
201-
202- // Commit transaction
203- if err := tx.Commit(); err != nil {
204- slm.widgetTxManager.FinishTransaction(txID, false, err.Error())
205- return fmt.Errorf("failed to commit transaction: %w", err)
206- }
207-
208- slm.widgetTxManager.FinishTransaction(txID, true, "")
209-
210 // Update stack visibility after removal145 // Update stack visibility after removal
211 if slm.wm.stackedPaneManager != nil {146 if slm.wm.stackedPaneManager != nil {
212 slm.wm.stackedPaneManager.UpdateStackVisibility(stackNode)147 slm.wm.stackedPaneManager.UpdateStackVisibility(stackNode)
@@ -224,42 +159,33 @@ func (slm *StackLifecycleManager) removePaneFromStack(stackNode *paneNode, closi
224 }159 }
225 160
226 // removeStackFromTree removes the stack container from the tree structure161 // removeStackFromTree removes the stack container from the tree structure
227-func (slm *StackLifecycleManager) removeStackFromTree(stackNode *paneNode, tx *WidgetTransaction) error {162+func (slm *StackLifecycleManager) removeStackFromTree(stackNode *paneNode) error {
228 if stackNode == nil {163 if stackNode == nil {
229 return errors.New("stack node is nil")164 return errors.New("stack node is nil")
230 }165 }
231 166
232 parent := stackNode.parent167 parent := stackNode.parent
233 168
234- // Add operation to unparent stack container when still valid169+ // Unparent stack container when still valid
235 if stackNode.container != nil {170 if stackNode.container != nil {
236 if !stackNode.container.IsValid() {171 if !stackNode.container.IsValid() {
237 log.Printf("[stack-lifecycle] stack container already invalid, skipping unparent: node=%p", stackNode)172 log.Printf("[stack-lifecycle] stack container already invalid, skipping unparent: node=%p", stackNode)
238 slm.invalidateSafeWidget(&stackNode.container)173 slm.invalidateSafeWidget(&stackNode.container)
239 slm.invalidateSafeWidget(&stackNode.stackWrapper)174 slm.invalidateSafeWidget(&stackNode.stackWrapper)
240 } else {175 } else {
241- op := CreateWidgetUnparentOperation(176+ // Directly unparent the widget
242- fmt.Sprintf("unparent_stack_%p", stackNode),177+ if err := stackNode.container.Execute(func(ptr uintptr) error {
243- stackNode.container,178+ if webkit.WidgetGetParent(ptr) != 0 {
244- )179+ webkit.WidgetUnparent(ptr)
245- if err := tx.AddOperation(op); err != nil {180+ }
246- return fmt.Errorf("failed to add unparent operation: %w", err)181+ return nil
182+ }); err != nil {
183+ return fmt.Errorf("failed to unparent stack container: %w", err)
247 }184 }
248 185
249- // Schedule cleanup of stack container and wrapper after unparent186+ // Cleanup stack container and wrapper
250- cleanupOp := &WidgetOperation{187+ slm.invalidateSafeWidget(&stackNode.container)
251- ID: fmt.Sprintf("cleanup_stack_widgets_%p", stackNode),188+ slm.invalidateSafeWidget(&stackNode.stackWrapper)
252- Description: "Invalidate stack container and wrapper",
253- Priority: 10,
254- Execute: func() error {
255- slm.invalidateSafeWidget(&stackNode.container)
256- slm.invalidateSafeWidget(&stackNode.stackWrapper)
257- return nil
258- },
259- }
260- if err := tx.AddOperation(cleanupOp); err != nil {
261- return fmt.Errorf("failed to add stack cleanup operation: %w", err)
262- }
263 }189 }
264 }190 }
265 191
@@ -286,7 +212,7 @@ func (slm *StackLifecycleManager) removeStackFromTree(stackNode *paneNode, tx *W
286 remainingChild = parent.right212 remainingChild = parent.right
287 }213 }
288 214
289- if err := slm.promoteChildNode(parent, remainingChild, tx); err != nil {215+ if err := slm.promoteChildNode(parent, remainingChild); err != nil {
290 return fmt.Errorf("failed to promote child node: %w", err)216 return fmt.Errorf("failed to promote child node: %w", err)
291 }217 }
292 }218 }
@@ -296,17 +222,18 @@ func (slm *StackLifecycleManager) removeStackFromTree(stackNode *paneNode, tx *W
296 }222 }
297 223
298 // convertStackToRegularPane converts a stack container to a regular pane224 // convertStackToRegularPane converts a stack container to a regular pane
299-func (slm *StackLifecycleManager) convertStackToRegularPane(stackNode *paneNode, remainingPane *paneNode, tx *WidgetTransaction) error {225+func (slm *StackLifecycleManager) convertStackToRegularPane(stackNode *paneNode, remainingPane *paneNode) error {
300 parent := stackNode.parent226 parent := stackNode.parent
301 227
302 // Remove remaining pane from stack container228 // Remove remaining pane from stack container
303- if remainingPane.container != nil {229+ if remainingPane.container != nil && remainingPane.container.IsValid() {
304- op := CreateWidgetUnparentOperation(230+ if err := remainingPane.container.Execute(func(ptr uintptr) error {
305- fmt.Sprintf("unparent_remaining_%p", remainingPane),231+ if webkit.WidgetGetParent(ptr) != 0 {
306- remainingPane.container,232+ webkit.WidgetUnparent(ptr)
307- )233+ }
308- if err := tx.AddOperation(op); err != nil {234+ return nil
309- return fmt.Errorf("failed to add unparent operation: %w", err)235+ }); err != nil {
236+ return fmt.Errorf("failed to unparent remaining pane: %w", err)
310 }237 }
311 }238 }
312 239
@@ -324,22 +251,14 @@ func (slm *StackLifecycleManager) convertStackToRegularPane(stackNode *paneNode,
324 slm.wm.mainPane = remainingPane251 slm.wm.mainPane = remainingPane
325 252
326 // Reparent to window253 // Reparent to window
327- if remainingPane.container != nil && slm.wm.window != nil {254+ if remainingPane.container != nil && remainingPane.container.IsValid() && slm.wm.window != nil {
328- op := &WidgetOperation{255+ if err := remainingPane.container.Execute(func(ptr uintptr) error {
329- ID: fmt.Sprintf("reparent_to_window_%p", remainingPane),256+ slm.wm.window.SetChild(ptr)
330- Description: "Reparent remaining pane to window",257+ webkit.WidgetQueueAllocate(ptr)
331- Priority: 200,258+ webkit.WidgetShow(ptr)
332- Execute: func() error {259+ return nil
333- return remainingPane.container.Execute(func(ptr uintptr) error {260+ }); err != nil {
334- slm.wm.window.SetChild(ptr)261+ return fmt.Errorf("failed to reparent to window: %w", err)
335- webkit.WidgetQueueAllocate(ptr)
336- webkit.WidgetShow(ptr)
337- return nil
338- })
339- },
340- }
341- if err := tx.AddOperation(op); err != nil {
342- return fmt.Errorf("failed to add reparent operation: %w", err)
343 }262 }
344 }263 }
345 } else {264 } else {
@@ -351,16 +270,25 @@ func (slm *StackLifecycleManager) convertStackToRegularPane(stackNode *paneNode,
351 }270 }
352 271
353 // Reparent to parent container272 // Reparent to parent container
354- if remainingPane.container != nil && parent.container != nil {273+ if remainingPane.container != nil && remainingPane.container.IsValid() && parent.container != nil && parent.container.IsValid() {
355 isStart := parent.left == remainingPane274 isStart := parent.left == remainingPane
356- op := CreateWidgetReparentOperation(275+ parentPtr := parent.container.Ptr()
357- fmt.Sprintf("reparent_remaining_%p", remainingPane),276+
358- remainingPane.container,277+ if err := remainingPane.container.Execute(func(ptr uintptr) error {
359- parent.container.Ptr(),278+ // Unparent first if needed
360- isStart,279+ if webkit.WidgetGetParent(ptr) != 0 {
361- )280+ webkit.WidgetUnparent(ptr)
362- if err := tx.AddOperation(op); err != nil {281+ }
363- return fmt.Errorf("failed to add reparent operation: %w", err)282+
283+ // Reparent to parent
284+ if isStart {
285+ webkit.PanedSetStartChild(parentPtr, ptr)
286+ } else {
287+ webkit.PanedSetEndChild(parentPtr, ptr)
288+ }
289+ return nil
290+ }); err != nil {
291+ return fmt.Errorf("failed to reparent to parent: %w", err)
364 }292 }
365 }293 }
366 }294 }
@@ -369,19 +297,20 @@ func (slm *StackLifecycleManager) convertStackToRegularPane(stackNode *paneNode,
369 }297 }
370 298
371 // removePaneWidgetsFromStack removes a pane's widgets from the stack container299 // removePaneWidgetsFromStack removes a pane's widgets from the stack container
372-func (slm *StackLifecycleManager) removePaneWidgetsFromStack(stackNode *paneNode, closingNode *paneNode, tx *WidgetTransaction) error {300+func (slm *StackLifecycleManager) removePaneWidgetsFromStack(stackNode *paneNode, closingNode *paneNode) error {
373 // Remove title bar if it exists301 // Remove title bar if it exists
374 if closingNode.titleBar != nil {302 if closingNode.titleBar != nil {
375 if !closingNode.titleBar.IsValid() {303 if !closingNode.titleBar.IsValid() {
376 log.Printf("[stack-lifecycle] title bar already invalid, skipping removal: pane=%p", closingNode)304 log.Printf("[stack-lifecycle] title bar already invalid, skipping removal: pane=%p", closingNode)
377 slm.invalidateSafeWidget(&closingNode.titleBar)305 slm.invalidateSafeWidget(&closingNode.titleBar)
378 } else {306 } else {
379- op := CreateWidgetUnparentOperation(307+ if err := closingNode.titleBar.Execute(func(ptr uintptr) error {
380- fmt.Sprintf("remove_titlebar_%p", closingNode),308+ if webkit.WidgetGetParent(ptr) != 0 {
381- closingNode.titleBar,309+ webkit.WidgetUnparent(ptr)
382- )310+ }
383- if err := tx.AddOperation(op); err != nil {311+ return nil
384- return fmt.Errorf("failed to add titlebar removal operation: %w", err)312+ }); err != nil {
313+ return fmt.Errorf("failed to unparent titlebar: %w", err)
385 }314 }
386 }315 }
387 }316 }
@@ -392,29 +321,20 @@ func (slm *StackLifecycleManager) removePaneWidgetsFromStack(stackNode *paneNode
392 log.Printf("[stack-lifecycle] pane container already invalid, skipping removal: pane=%p", closingNode)321 log.Printf("[stack-lifecycle] pane container already invalid, skipping removal: pane=%p", closingNode)
393 slm.invalidateSafeWidget(&closingNode.container)322 slm.invalidateSafeWidget(&closingNode.container)
394 } else {323 } else {
395- op := CreateWidgetUnparentOperation(324+ if err := closingNode.container.Execute(func(ptr uintptr) error {
396- fmt.Sprintf("remove_container_%p", closingNode),325+ if webkit.WidgetGetParent(ptr) != 0 {
397- closingNode.container,326+ webkit.WidgetUnparent(ptr)
398- )327+ }
399- if err := tx.AddOperation(op); err != nil {328+ return nil
400- return fmt.Errorf("failed to add container removal operation: %w", err)329+ }); err != nil {
330+ return fmt.Errorf("failed to unparent container: %w", err)
401 }331 }
402 }332 }
403 }333 }
404 334
405- cleanupOp := &WidgetOperation{335+ // Cleanup removed pane widgets
406- ID: fmt.Sprintf("cleanup_removed_pane_%p", closingNode),336+ slm.invalidateSafeWidget(&closingNode.titleBar)
407- Description: "Invalidate removed pane widgets",337+ slm.invalidateSafeWidget(&closingNode.container)
408- Priority: 10,
409- Execute: func() error {
410- slm.invalidateSafeWidget(&closingNode.titleBar)
411- slm.invalidateSafeWidget(&closingNode.container)
412- return nil
413- },
414- }
415- if err := tx.AddOperation(cleanupOp); err != nil {
416- return fmt.Errorf("failed to add pane widget cleanup operation: %w", err)
417- }
418 338
419 return nil339 return nil
420 }340 }
@@ -447,7 +367,7 @@ func (slm *StackLifecycleManager) updateStackAfterRemoval(stackNode *paneNode, r
447 }367 }
448 368
449 // promoteChildNode promotes a child node to replace its parent369 // promoteChildNode promotes a child node to replace its parent
450-func (slm *StackLifecycleManager) promoteChildNode(parent *paneNode, child *paneNode, tx *WidgetTransaction) error {370+func (slm *StackLifecycleManager) promoteChildNode(parent *paneNode, child *paneNode) error {
451 grandparent := parent.parent371 grandparent := parent.parent
452 372
453 // Update child's parent pointer373 // Update child's parent pointer
@@ -462,22 +382,14 @@ func (slm *StackLifecycleManager) promoteChildNode(parent *paneNode, child *pane
462 }382 }
463 383
464 // Reparent to window384 // Reparent to window
465- if child.container != nil && slm.wm.window != nil {385+ if child.container != nil && child.container.IsValid() && slm.wm.window != nil {
466- op := &WidgetOperation{386+ if err := child.container.Execute(func(ptr uintptr) error {
467- ID: fmt.Sprintf("promote_to_root_%p", child),387+ slm.wm.window.SetChild(ptr)
468- Description: "Promote child to root",388+ webkit.WidgetQueueAllocate(ptr)
469- Priority: 200,389+ webkit.WidgetShow(ptr)
470- Execute: func() error {390+ return nil
471- return child.container.Execute(func(ptr uintptr) error {391+ }); err != nil {
472- slm.wm.window.SetChild(ptr)392+ return fmt.Errorf("failed to promote to root: %w", err)
473- webkit.WidgetQueueAllocate(ptr)
474- webkit.WidgetShow(ptr)
475- return nil
476- })
477- },
478- }
479- if err := tx.AddOperation(op); err != nil {
480- return fmt.Errorf("failed to add promote operation: %w", err)
481 }393 }
482 }394 }
483 } else {395 } else {
@@ -489,16 +401,25 @@ func (slm *StackLifecycleManager) promoteChildNode(parent *paneNode, child *pane
489 }401 }
490 402
491 // Reparent widget to grandparent403 // Reparent widget to grandparent
492- if child.container != nil && grandparent.container != nil {404+ if child.container != nil && child.container.IsValid() && grandparent.container != nil && grandparent.container.IsValid() {
493 isStart := grandparent.left == child405 isStart := grandparent.left == child
494- op := CreateWidgetReparentOperation(406+ grandparentPtr := grandparent.container.Ptr()
495- fmt.Sprintf("promote_child_%p", child),407+
496- child.container,408+ if err := child.container.Execute(func(ptr uintptr) error {
497- grandparent.container.Ptr(),409+ // Unparent first if needed
498- isStart,410+ if webkit.WidgetGetParent(ptr) != 0 {
499- )411+ webkit.WidgetUnparent(ptr)
500- if err := tx.AddOperation(op); err != nil {412+ }
501- return fmt.Errorf("failed to add promote reparent operation: %w", err)413+
414+ // Reparent to grandparent
415+ if isStart {
416+ webkit.PanedSetStartChild(grandparentPtr, ptr)
417+ } else {
418+ webkit.PanedSetEndChild(grandparentPtr, ptr)
419+ }
420+ return nil
421+ }); err != nil {
422+ return fmt.Errorf("failed to promote child: %w", err)
502 }423 }
503 }424 }
504 }425 }
M internal/app/browser/workspace_tree_rebalancer.go +168 -272
@@ -12,11 +12,10 @@ import (
1212
1313 // TreeRebalancer provides algorithms for rebalancing the binary tree after operations
1414 type TreeRebalancer struct {
15- wm *WorkspaceManager
16- treeValidator *TreeValidator
17- widgetTxManager *WidgetTransactionManager
18- maxImbalance int // Maximum allowed imbalance before rebalancing
19- enabled bool // Whether rebalancing is enabled
15+ wm *WorkspaceManager
16+ treeValidator *TreeValidator
17+ maxImbalance int // Maximum allowed imbalance before rebalancing
18+ enabled bool // Whether rebalancing is enabled
2019 }
2120
2221 // RebalanceOperation represents a tree rebalancing operation
@@ -63,13 +62,12 @@ type TreeMetrics struct {
6362 }
6463
6564 // NewTreeRebalancer creates a new tree rebalancer
66-func NewTreeRebalancer(wm *WorkspaceManager, treeValidator *TreeValidator, widgetTxManager *WidgetTransactionManager) *TreeRebalancer {
65+func NewTreeRebalancer(wm *WorkspaceManager, treeValidator *TreeValidator) *TreeRebalancer {
6766 return &TreeRebalancer{
68- wm: wm,
69- treeValidator: treeValidator,
70- widgetTxManager: widgetTxManager,
71- maxImbalance: 2, // Allow up to 2 levels of imbalance
72- enabled: true,
67+ wm: wm,
68+ treeValidator: treeValidator,
69+ maxImbalance: 2, // Allow up to 2 levels of imbalance
70+ enabled: true,
7371 }
7472 }
7573
@@ -99,32 +97,10 @@ func (tr *TreeRebalancer) RebalanceAfterClose(closedNode *paneNode, promotedNode
9997 log.Printf("[tree-rebalancer] Analyzing tree for rebalancing after close operation")
10098
10199 if promotedNode != nil {
102- txID := fmt.Sprintf("promotion_tx_%p", promotedNode)
103- tx := tr.widgetTxManager.BeginTransaction(txID)
104-
105- if err := tr.executePromotion(promotedNode, tx); err != nil {
106- if rollbackErr := tx.Rollback(); rollbackErr != nil {
107- log.Printf("[tree-rebalancer] Failed to rollback promotion transaction %s: %v", txID, rollbackErr)
108- }
109- tr.widgetTxManager.FinishTransaction(txID, false, err.Error())
100+ if err := tr.executePromotion(promotedNode); err != nil {
110101 return fmt.Errorf("promotion execution failed: %w", err)
111102 }
112103
113- if err := tx.Execute(); err != nil {
114- if rollbackErr := tx.Rollback(); rollbackErr != nil {
115- log.Printf("[tree-rebalancer] Failed to rollback promotion transaction %s: %v", txID, rollbackErr)
116- }
117- tr.widgetTxManager.FinishTransaction(txID, false, err.Error())
118- return fmt.Errorf("promotion widget execution failed: %w", err)
119- }
120-
121- if err := tx.Commit(); err != nil {
122- tr.widgetTxManager.FinishTransaction(txID, false, err.Error())
123- return fmt.Errorf("failed to commit promotion transaction: %w", err)
124- }
125-
126- tr.widgetTxManager.FinishTransaction(txID, true, "")
127-
128104 if tr.wm != nil && tr.wm.geometryValidator != nil {
129105 tr.logInitialPromotionGeometry(promotedNode)
130106 tr.schedulePromotionValidation(promotedNode)
@@ -385,10 +361,6 @@ func (tr *TreeRebalancer) executeRebalancingOperations(operations []RebalanceOpe
385361 return nil
386362 }
387363
388- // Create transaction for all rebalancing operations
389- txID := fmt.Sprintf("rebalance_%d_ops", len(operations))
390- tx := tr.widgetTxManager.BeginTransaction(txID)
391-
392364 log.Printf("[tree-rebalancer] Executing %d rebalancing operations", len(operations))
393365
394366 // Execute each operation
@@ -398,40 +370,23 @@ func (tr *TreeRebalancer) executeRebalancingOperations(operations []RebalanceOpe
398370 var err error
399371 switch operation.Type {
400372 case RebalanceRotateLeft:
401- err = tr.executeLeftRotation(operation.Node, tx)
373+ err = tr.executeLeftRotation(operation.Node)
402374 case RebalanceRotateRight:
403- err = tr.executeRightRotation(operation.Node, tx)
375+ err = tr.executeRightRotation(operation.Node)
404376 case RebalancePromote:
405- err = tr.executePromotion(operation.Node, tx)
377+ err = tr.executePromotion(operation.Node)
406378 case RebalanceRestructure:
407- err = tr.executeRestructure(operation.Node, tx)
379+ err = tr.executeRestructure(operation.Node)
408380 default:
409381 err = fmt.Errorf("unknown rebalance operation type: %s", operation.Type)
410382 }
411383
412384 if err != nil {
413385 log.Printf("[tree-rebalancer] Operation failed: %v", err)
414- tx.Rollback()
415- tr.widgetTxManager.FinishTransaction(txID, false, err.Error())
416386 return fmt.Errorf("rebalancing operation failed: %w", err)
417387 }
418388 }
419389
420- // Execute widget transaction
421- if err := tx.Execute(); err != nil {
422- tx.Rollback()
423- tr.widgetTxManager.FinishTransaction(txID, false, err.Error())
424- return fmt.Errorf("failed to execute rebalancing widget operations: %w", err)
425- }
426-
427- // Commit transaction
428- if err := tx.Commit(); err != nil {
429- tr.widgetTxManager.FinishTransaction(txID, false, err.Error())
430- return fmt.Errorf("failed to commit rebalancing transaction: %w", err)
431- }
432-
433- tr.widgetTxManager.FinishTransaction(txID, true, "")
434-
435390 // Validate tree after rebalancing
436391 if tr.treeValidator != nil {
437392 if err := tr.treeValidator.ValidateTree(tr.wm.root, "rebalance"); err != nil {
@@ -441,14 +396,14 @@ func (tr *TreeRebalancer) executeRebalancingOperations(operations []RebalanceOpe
441396
442397 // Calculate new metrics
443398 newMetrics := tr.CalculateTreeMetrics(tr.wm.root)
444- log.Printf("[tree-rebalancer] Rebalancing completed: new imbalance=%d (was %d)",
445- newMetrics.ImbalanceScore, newMetrics.ImbalanceScore)
399+ log.Printf("[tree-rebalancer] Rebalancing completed: new imbalance=%d",
400+ newMetrics.ImbalanceScore)
446401
447402 return nil
448403 }
449404
450405 // executeLeftRotation performs a left rotation on the given node
451-func (tr *TreeRebalancer) executeLeftRotation(node *paneNode, tx *WidgetTransaction) error {
406+func (tr *TreeRebalancer) executeLeftRotation(node *paneNode) error {
452407 if node == nil || node.right == nil {
453408 return fmt.Errorf("cannot perform left rotation: invalid node structure")
454409 }
@@ -479,12 +434,12 @@ func (tr *TreeRebalancer) executeLeftRotation(node *paneNode, tx *WidgetTransact
479434 parent.right = rightChild
480435 }
481436
482- // Add widget operations for the rotation
483- return tr.addRotationWidgetOperations(node, rightChild, tx, "left")
437+ // Update widget hierarchy for the rotation
438+ return tr.updateRotationWidgets(node, rightChild, "left")
484439 }
485440
486441 // executeRightRotation performs a right rotation on the given node
487-func (tr *TreeRebalancer) executeRightRotation(node *paneNode, tx *WidgetTransaction) error {
442+func (tr *TreeRebalancer) executeRightRotation(node *paneNode) error {
488443 if node == nil || node.left == nil {
489444 return fmt.Errorf("cannot perform right rotation: invalid node structure")
490445 }
@@ -515,12 +470,12 @@ func (tr *TreeRebalancer) executeRightRotation(node *paneNode, tx *WidgetTransac
515470 parent.right = leftChild
516471 }
517472
518- // Add widget operations for the rotation
519- return tr.addRotationWidgetOperations(node, leftChild, tx, "right")
473+ // Update widget hierarchy for the rotation
474+ return tr.updateRotationWidgets(node, leftChild, "right")
520475 }
521476
522-// addRotationWidgetOperations adds widget operations needed for tree rotation
523-func (tr *TreeRebalancer) addRotationWidgetOperations(oldRoot, newRoot *paneNode, tx *WidgetTransaction, rotationType string) error {
477+// updateRotationWidgets updates widget hierarchy after tree rotation
478+func (tr *TreeRebalancer) updateRotationWidgets(oldRoot, newRoot *paneNode, rotationType string) error {
524479 // The widget hierarchy needs to be updated to match the new tree structure
525480 // This is complex because GTK widgets need to be reparented correctly
526481
@@ -528,40 +483,23 @@ func (tr *TreeRebalancer) addRotationWidgetOperations(oldRoot, newRoot *paneNode
528483 return fmt.Errorf("rotation nodes missing containers")
529484 }
530485
531- // Add operation to update widget hierarchy
532- op := &WidgetOperation{
533- ID: fmt.Sprintf("rotation_%s_%p_%p", rotationType, oldRoot, newRoot),
534- Description: fmt.Sprintf("%s rotation widget update", rotationType),
535- Priority: 300, // High priority for structural changes
536- Execute: func() error {
537- // This is a simplified version - in practice, you'd need to:
538- // 1. Unparent both widgets
539- // 2. Re-establish the parent-child relationships
540- // 3. Update any size allocations
486+ log.Printf("[tree-rebalancer] Updating widget hierarchy for %s rotation", rotationType)
541487
542- log.Printf("[tree-rebalancer] Updating widget hierarchy for %s rotation", rotationType)
543-
544- // Force a layout update
545- if newRoot.container != nil {
546- return newRoot.container.Execute(func(ptr uintptr) error {
547- webkit.WidgetQueueAllocate(ptr)
548- return nil
549- })
488+ // Force a layout update
489+ if newRoot.container != nil {
490+ return newRoot.container.Execute(func(ptr uintptr) error {
491+ if ptr == 0 || !webkit.WidgetIsValid(ptr) {
492+ return fmt.Errorf("rotation widget update: invalid widget pointer")
550493 }
494+ webkit.WidgetQueueAllocate(ptr)
551495 return nil
552- },
553- Rollback: func() error {
554- // Rotation rollback would require reversing the rotation
555- log.Printf("[tree-rebalancer] Rolling back %s rotation", rotationType)
556- return nil
557- },
496+ })
558497 }
559-
560- return tx.AddOperation(op)
498+ return nil
561499 }
562500
563501 // executePromotion promotes a child node to replace its parent
564-func (tr *TreeRebalancer) executePromotion(node *paneNode, tx *WidgetTransaction) error {
502+func (tr *TreeRebalancer) executePromotion(node *paneNode) error {
565503 if node == nil {
566504 return fmt.Errorf("promotion target is nil")
567505 }
@@ -576,95 +514,81 @@ func (tr *TreeRebalancer) executePromotion(node *paneNode, tx *WidgetTransaction
576514 promotionStart := time.Now()
577515
578516 // Ensure the promoted widget can expand to occupy available space
579- expandOp := &WidgetOperation{
580- ID: fmt.Sprintf("promotion_expand_%p", node),
581- Description: "Ensure promoted pane expands to fill space",
582- Priority: 200,
583- Execute: func() error {
584- log.Printf("[tree-rebalancer] promotion expand for %p executing at +%s", node, time.Since(promotionStart).Round(time.Millisecond))
585- return node.container.Execute(func(ptr uintptr) error {
586- if ptr == 0 || !webkit.WidgetIsValid(ptr) {
587- return fmt.Errorf("promotion expand: invalid widget pointer")
588- }
589- webkit.WidgetResetSizeRequest(ptr)
590- webkit.WidgetSetHExpand(ptr, true)
591- webkit.WidgetSetVExpand(ptr, true)
592- webkit.WidgetQueueAllocate(ptr)
593- return nil
594- })
595- },
596- }
597- if err := tx.AddOperation(expandOp); err != nil {
598- return fmt.Errorf("failed to add promotion expand operation: %w", err)
517+ log.Printf("[tree-rebalancer] promotion expand for %p executing at +%s", node, time.Since(promotionStart).Round(time.Millisecond))
518+ if err := node.container.Execute(func(ptr uintptr) error {
519+ if ptr == 0 || !webkit.WidgetIsValid(ptr) {
520+ return fmt.Errorf("promotion expand: invalid widget pointer")
521+ }
522+ webkit.WidgetResetSizeRequest(ptr)
523+ webkit.WidgetSetHExpand(ptr, true)
524+ webkit.WidgetSetVExpand(ptr, true)
525+ webkit.WidgetQueueAllocate(ptr)
526+ return nil
527+ }); err != nil {
528+ return fmt.Errorf("failed to expand promoted widget: %w", err)
599529 }
600530
601531 parent := node.parent
602532 if parent == nil {
533+ // Attach to window root
603534 if tr.wm != nil && tr.wm.window != nil {
604- op := &WidgetOperation{
605- ID: fmt.Sprintf("promotion_attach_window_%p", node),
606- Description: "Attach promoted pane to window root",
607- Priority: 190,
608- Execute: func() error {
609- log.Printf("[tree-rebalancer] promotion window attach for %p executing at +%s", node, time.Since(promotionStart).Round(time.Millisecond))
610- return node.container.Execute(func(ptr uintptr) error {
611- if ptr == 0 || !webkit.WidgetIsValid(ptr) {
612- return fmt.Errorf("promotion attach window: invalid widget pointer")
613- }
535+ log.Printf("[tree-rebalancer] promotion window attach for %p executing at +%s", node, time.Since(promotionStart).Round(time.Millisecond))
536+ if err := node.container.Execute(func(ptr uintptr) error {
537+ if ptr == 0 || !webkit.WidgetIsValid(ptr) {
538+ return fmt.Errorf("promotion attach window: invalid widget pointer")
539+ }
614540
615- currentParent := webkit.WidgetGetParent(ptr)
616- if currentParent != 0 {
617- // Check if parent is a container widget (paned or box) that we need to unparent from
618- if webkit.IsPaned(currentParent) {
619- log.Printf("[tree-rebalancer] unparenting widget from paned %#x", currentParent)
620- webkit.WidgetUnparent(ptr)
621- } else if webkit.IsBox(currentParent) {
622- log.Printf("[tree-rebalancer] unparenting widget from box (stack) %#x", currentParent)
623- webkit.WidgetUnparent(ptr)
624- } else {
625- // Parent is likely a window or other non-container widget
626- // For safety, check if it's a toplevel window by attempting unparent
627- log.Printf("[tree-rebalancer] widget %#x has non-container parent %#x, checking if unparent needed", ptr, currentParent)
628- if webkit.WidgetIsValid(currentParent) {
629- // If parent is valid but not a container, try unparent for safety
630- webkit.WidgetUnparent(ptr)
631- log.Printf("[tree-rebalancer] unparented widget %#x from non-container parent %#x", ptr, currentParent)
632- }
633- }
541+ currentParent := webkit.WidgetGetParent(ptr)
542+ if currentParent != 0 {
543+ // Check if parent is a container widget (paned or box) that we need to unparent from
544+ if webkit.IsPaned(currentParent) {
545+ log.Printf("[tree-rebalancer] unparenting widget from paned %#x", currentParent)
546+ webkit.WidgetUnparent(ptr)
547+ } else if webkit.IsBox(currentParent) {
548+ log.Printf("[tree-rebalancer] unparenting widget from box (stack) %#x", currentParent)
549+ webkit.WidgetUnparent(ptr)
550+ } else {
551+ // Parent is likely a window or other non-container widget
552+ // For safety, check if it's a toplevel window by attempting unparent
553+ log.Printf("[tree-rebalancer] widget %#x has non-container parent %#x, checking if unparent needed", ptr, currentParent)
554+ if webkit.WidgetIsValid(currentParent) {
555+ // If parent is valid but not a container, try unparent for safety
556+ webkit.WidgetUnparent(ptr)
557+ log.Printf("[tree-rebalancer] unparented widget %#x from non-container parent %#x", ptr, currentParent)
634558 }
559+ }
560+ }
635561
636- // Ensure widget is configured for window child
637- webkit.WidgetSetHExpand(ptr, true)
638- webkit.WidgetSetVExpand(ptr, true)
639-
640- // Set as window child
641- tr.wm.window.SetChild(ptr)
642- webkit.WidgetQueueAllocate(ptr)
643- webkit.WidgetShow(ptr)
644-
645- // Verify the attachment worked
646- finalParent := webkit.WidgetGetParent(ptr)
647- if finalParent == 0 {
648- log.Printf("[tree-rebalancer] WARNING: SetChild failed, widget still has no parent")
649- // Try one more time
650- tr.wm.window.SetChild(ptr)
651- webkit.WidgetQueueAllocate(ptr)
652- finalParent = webkit.WidgetGetParent(ptr)
653- if finalParent == 0 {
654- return fmt.Errorf("failed to attach widget %#x to window after multiple attempts", ptr)
655- }
656- }
562+ // Ensure widget is configured for window child
563+ webkit.WidgetSetHExpand(ptr, true)
564+ webkit.WidgetSetVExpand(ptr, true)
657565
658- log.Printf("[tree-rebalancer] promotion window attach successful: widget %#x now has parent %#x", ptr, finalParent)
659- return nil
660- })
661- },
662- }
663- if err := tx.AddOperation(op); err != nil {
664- return fmt.Errorf("failed to add window attachment operation: %w", err)
566+ // Set as window child
567+ tr.wm.window.SetChild(ptr)
568+ webkit.WidgetQueueAllocate(ptr)
569+ webkit.WidgetShow(ptr)
570+
571+ // Verify the attachment worked
572+ finalParent := webkit.WidgetGetParent(ptr)
573+ if finalParent == 0 {
574+ log.Printf("[tree-rebalancer] WARNING: SetChild failed, widget still has no parent")
575+ // Try one more time
576+ tr.wm.window.SetChild(ptr)
577+ webkit.WidgetQueueAllocate(ptr)
578+ finalParent = webkit.WidgetGetParent(ptr)
579+ if finalParent == 0 {
580+ return fmt.Errorf("failed to attach widget %#x to window after multiple attempts", ptr)
581+ }
582+ }
583+
584+ log.Printf("[tree-rebalancer] promotion window attach successful: widget %#x now has parent %#x", ptr, finalParent)
585+ return nil
586+ }); err != nil {
587+ return fmt.Errorf("failed to attach widget to window: %w", err)
665588 }
666589 }
667590 } else {
591+ // Attach to parent container
668592 if parent.container == nil {
669593 return fmt.Errorf("promotion parent missing container")
670594 }
@@ -672,125 +596,97 @@ func (tr *TreeRebalancer) executePromotion(node *paneNode, tx *WidgetTransaction
672596 return fmt.Errorf("promotion parent container %s invalid", parent.container.String())
673597 }
674598
675- attachOp := &WidgetOperation{
676- ID: fmt.Sprintf("promotion_reparent_%p", node),
677- Description: "Attach promoted pane to parent container",
678- Priority: 190,
679- Execute: func() error {
680- log.Printf("[tree-rebalancer] promotion reparent for %p executing at +%s", node, time.Since(promotionStart).Round(time.Millisecond))
681- return parent.container.Execute(func(parentPtr uintptr) error {
682- if parentPtr == 0 || !webkit.WidgetIsValid(parentPtr) {
683- return fmt.Errorf("promotion reparent: invalid parent widget")
684- }
685- return node.container.Execute(func(childPtr uintptr) error {
686- if childPtr == 0 || !webkit.WidgetIsValid(childPtr) {
687- return fmt.Errorf("promotion reparent: invalid child widget")
688- }
599+ log.Printf("[tree-rebalancer] promotion reparent for %p executing at +%s", node, time.Since(promotionStart).Round(time.Millisecond))
600+ if err := parent.container.Execute(func(parentPtr uintptr) error {
601+ if parentPtr == 0 || !webkit.WidgetIsValid(parentPtr) {
602+ return fmt.Errorf("promotion reparent: invalid parent widget")
603+ }
604+ return node.container.Execute(func(childPtr uintptr) error {
605+ if childPtr == 0 || !webkit.WidgetIsValid(childPtr) {
606+ return fmt.Errorf("promotion reparent: invalid child widget")
607+ }
689608
690- // Check if widget is already correctly parented (closePane.swapContainers already did the work)
691- currentParent := webkit.WidgetGetParent(childPtr)
692- if currentParent == parentPtr {
693- log.Printf("[tree-rebalancer] widget %#x already correctly parented to %#x, skipping reparent", childPtr, parentPtr)
694- // Just ensure properties are set correctly
695- webkit.WidgetResetSizeRequest(childPtr)
696- webkit.WidgetSetHExpand(childPtr, true)
697- webkit.WidgetSetVExpand(childPtr, true)
698- webkit.WidgetQueueAllocate(childPtr)
699- webkit.WidgetQueueAllocate(parentPtr)
700- return nil
701- }
609+ // Check if widget is already correctly parented (closePane.swapContainers already did the work)
610+ currentParent := webkit.WidgetGetParent(childPtr)
611+ if currentParent == parentPtr {
612+ log.Printf("[tree-rebalancer] widget %#x already correctly parented to %#x, skipping reparent", childPtr, parentPtr)
613+ // Just ensure properties are set correctly
614+ webkit.WidgetResetSizeRequest(childPtr)
615+ webkit.WidgetSetHExpand(childPtr, true)
616+ webkit.WidgetSetVExpand(childPtr, true)
617+ webkit.WidgetQueueAllocate(childPtr)
618+ webkit.WidgetQueueAllocate(parentPtr)
619+ return nil
620+ }
702621
703- // Widget needs reparenting - unparent from wrong parent if needed
704- if currentParent != 0 {
705- log.Printf("[tree-rebalancer] unparenting widget %#x from incorrect parent %#x", childPtr, currentParent)
706- webkit.WidgetUnparent(childPtr)
707- }
622+ // Widget needs reparenting - unparent from wrong parent if needed
623+ if currentParent != 0 {
624+ log.Printf("[tree-rebalancer] unparenting widget %#x from incorrect parent %#x", childPtr, currentParent)
625+ webkit.WidgetUnparent(childPtr)
626+ }
708627
709- // Reattach promoted widget to the GtkPaned
710- if parent.left == node {
711- webkit.PanedSetStartChild(parentPtr, childPtr)
712- } else {
713- webkit.PanedSetEndChild(parentPtr, childPtr)
714- }
628+ // Reattach promoted widget to the GtkPaned
629+ if parent.left == node {
630+ webkit.PanedSetStartChild(parentPtr, childPtr)
631+ } else {
632+ webkit.PanedSetEndChild(parentPtr, childPtr)
633+ }
715634
716- // Verify attachment succeeded before setting properties
717- finalParent := webkit.WidgetGetParent(childPtr)
718- if finalParent != parentPtr {
719- return fmt.Errorf("failed to attach widget %#x to parent %#x (current parent: %#x)", childPtr, parentPtr, finalParent)
720- }
635+ // Verify attachment succeeded before setting properties
636+ finalParent := webkit.WidgetGetParent(childPtr)
637+ if finalParent != parentPtr {
638+ return fmt.Errorf("failed to attach widget %#x to parent %#x (current parent: %#x)", childPtr, parentPtr, finalParent)
639+ }
721640
722- webkit.WidgetResetSizeRequest(childPtr)
723- webkit.WidgetSetHExpand(childPtr, true)
724- webkit.WidgetSetVExpand(childPtr, true)
725- webkit.WidgetQueueAllocate(childPtr)
726- webkit.WidgetQueueAllocate(parentPtr)
727- return nil
728- })
729- })
730- },
731- }
732- if err := tx.AddOperation(attachOp); err != nil {
733- return fmt.Errorf("failed to add promotion reparent operation: %w", err)
641+ webkit.WidgetResetSizeRequest(childPtr)
642+ webkit.WidgetSetHExpand(childPtr, true)
643+ webkit.WidgetSetVExpand(childPtr, true)
644+ webkit.WidgetQueueAllocate(childPtr)
645+ webkit.WidgetQueueAllocate(parentPtr)
646+ return nil
647+ })
648+ }); err != nil {
649+ return fmt.Errorf("failed to reparent promoted widget: %w", err)
734650 }
735651 }
736652
737653 // Propagate allocation updates up the ancestor chain so GTK recalculates sizes
738654 ancestorPtrs := tr.collectAncestorContainers(node)
739655 if len(ancestorPtrs) > 0 {
740- allocationOp := &WidgetOperation{
741- ID: fmt.Sprintf("promotion_allocate_%p", node),
742- Description: "Queue allocation for promoted pane ancestors",
743- Priority: 150,
744- Execute: func() error {
745- log.Printf("[tree-rebalancer] promotion allocation queue for %p executing at +%s (ancestors=%d)", node, time.Since(promotionStart).Round(time.Millisecond), len(ancestorPtrs))
746- for _, ancestorPtr := range ancestorPtrs {
747- if ancestorPtr == 0 || !webkit.WidgetIsValid(ancestorPtr) {
748- continue
749- }
750- webkit.WidgetQueueAllocate(ancestorPtr)
751- }
752- return nil
753- },
754- }
755- if err := tx.AddOperation(allocationOp); err != nil {
756- return fmt.Errorf("failed to add promotion allocation operation: %w", err)
656+ log.Printf("[tree-rebalancer] promotion allocation queue for %p executing at +%s (ancestors=%d)", node, time.Since(promotionStart).Round(time.Millisecond), len(ancestorPtrs))
657+ for _, ancestorPtr := range ancestorPtrs {
658+ if ancestorPtr == 0 || !webkit.WidgetIsValid(ancestorPtr) {
659+ continue
660+ }
661+ webkit.WidgetQueueAllocate(ancestorPtr)
757662 }
758663 }
759664
760665 // Validate allocation after promotion for debugging purposes
761- validateOp := &WidgetOperation{
762- ID: fmt.Sprintf("promotion_validate_%p", node),
763- Description: "Validate promoted pane allocation",
764- Priority: 100,
765- Execute: func() error {
766- log.Printf("[tree-rebalancer] promotion immediate validation for %p executing at +%s", node, time.Since(promotionStart).Round(time.Millisecond))
767- return node.container.Execute(func(ptr uintptr) error {
768- if ptr == 0 || !webkit.WidgetIsValid(ptr) {
769- return fmt.Errorf("promotion validate: invalid widget pointer")
770- }
771- bounds, ok := webkit.WidgetGetBounds(ptr)
772- if !ok {
773- log.Printf("[tree-rebalancer] promotion validation: failed to read bounds for %#x", ptr)
774- return nil
775- }
776- if bounds.Width <= 1 || bounds.Height <= 1 {
777- log.Printf("[tree-rebalancer] WARNING: promoted pane %#x has tiny allocation %.1fx%.1f", ptr, bounds.Width, bounds.Height)
778- } else {
779- log.Printf("[tree-rebalancer] Promotion allocation verified for %#x: %.1fx%.1f", ptr, bounds.Width, bounds.Height)
780- }
781- return nil
782- })
783- },
784- }
785- if err := tx.AddOperation(validateOp); err != nil {
786- return fmt.Errorf("failed to add promotion validation operation: %w", err)
787- }
666+ log.Printf("[tree-rebalancer] promotion immediate validation for %p executing at +%s", node, time.Since(promotionStart).Round(time.Millisecond))
667+ _ = node.container.Execute(func(ptr uintptr) error {
668+ if ptr == 0 || !webkit.WidgetIsValid(ptr) {
669+ log.Printf("[tree-rebalancer] promotion validate: invalid widget pointer")
670+ return nil
671+ }
672+ bounds, ok := webkit.WidgetGetBounds(ptr)
673+ if !ok {
674+ log.Printf("[tree-rebalancer] promotion validation: failed to read bounds for %#x", ptr)
675+ return nil
676+ }
677+ if bounds.Width <= 1 || bounds.Height <= 1 {
678+ log.Printf("[tree-rebalancer] WARNING: promoted pane %#x has tiny allocation %.1fx%.1f", ptr, bounds.Width, bounds.Height)
679+ } else {
680+ log.Printf("[tree-rebalancer] Promotion allocation verified for %#x: %.1fx%.1f", ptr, bounds.Width, bounds.Height)
681+ }
682+ return nil
683+ })
788684
789685 return nil
790686 }
791687
792688 // executeRestructure performs a complete restructure of a subtree
793-func (tr *TreeRebalancer) executeRestructure(node *paneNode, tx *WidgetTransaction) error {
689+func (tr *TreeRebalancer) executeRestructure(node *paneNode) error {
794690 // This would be used for more complex rebalancing scenarios
795691 log.Printf("[tree-rebalancer] Restructuring subtree at node %p", node)
796692
@@ -12,11 +12,10 @@ import (
12 12
13 // TreeRebalancer provides algorithms for rebalancing the binary tree after operations13 // TreeRebalancer provides algorithms for rebalancing the binary tree after operations
14 type TreeRebalancer struct {14 type TreeRebalancer struct {
15- wm *WorkspaceManager15+ wm *WorkspaceManager
16- treeValidator *TreeValidator16+ treeValidator *TreeValidator
17- widgetTxManager *WidgetTransactionManager17+ maxImbalance int // Maximum allowed imbalance before rebalancing
18- maxImbalance int // Maximum allowed imbalance before rebalancing18+ enabled bool // Whether rebalancing is enabled
19- enabled bool // Whether rebalancing is enabled
20 }19 }
21 20
22 // RebalanceOperation represents a tree rebalancing operation21 // RebalanceOperation represents a tree rebalancing operation
@@ -63,13 +62,12 @@ type TreeMetrics struct {
63 }62 }
64 63
65 // NewTreeRebalancer creates a new tree rebalancer64 // NewTreeRebalancer creates a new tree rebalancer
66-func NewTreeRebalancer(wm *WorkspaceManager, treeValidator *TreeValidator, widgetTxManager *WidgetTransactionManager) *TreeRebalancer {65+func NewTreeRebalancer(wm *WorkspaceManager, treeValidator *TreeValidator) *TreeRebalancer {
67 return &TreeRebalancer{66 return &TreeRebalancer{
68- wm: wm,67+ wm: wm,
69- treeValidator: treeValidator,68+ treeValidator: treeValidator,
70- widgetTxManager: widgetTxManager,69+ maxImbalance: 2, // Allow up to 2 levels of imbalance
71- maxImbalance: 2, // Allow up to 2 levels of imbalance70+ enabled: true,
72- enabled: true,
73 }71 }
74 }72 }
75 73
@@ -99,32 +97,10 @@ func (tr *TreeRebalancer) RebalanceAfterClose(closedNode *paneNode, promotedNode
99 log.Printf("[tree-rebalancer] Analyzing tree for rebalancing after close operation")97 log.Printf("[tree-rebalancer] Analyzing tree for rebalancing after close operation")
100 98
101 if promotedNode != nil {99 if promotedNode != nil {
102- txID := fmt.Sprintf("promotion_tx_%p", promotedNode)100+ if err := tr.executePromotion(promotedNode); err != nil {
103- tx := tr.widgetTxManager.BeginTransaction(txID)
104-
105- if err := tr.executePromotion(promotedNode, tx); err != nil {
106- if rollbackErr := tx.Rollback(); rollbackErr != nil {
107- log.Printf("[tree-rebalancer] Failed to rollback promotion transaction %s: %v", txID, rollbackErr)
108- }
109- tr.widgetTxManager.FinishTransaction(txID, false, err.Error())
110 return fmt.Errorf("promotion execution failed: %w", err)101 return fmt.Errorf("promotion execution failed: %w", err)
111 }102 }
112 103
113- if err := tx.Execute(); err != nil {
114- if rollbackErr := tx.Rollback(); rollbackErr != nil {
115- log.Printf("[tree-rebalancer] Failed to rollback promotion transaction %s: %v", txID, rollbackErr)
116- }
117- tr.widgetTxManager.FinishTransaction(txID, false, err.Error())
118- return fmt.Errorf("promotion widget execution failed: %w", err)
119- }
120-
121- if err := tx.Commit(); err != nil {
122- tr.widgetTxManager.FinishTransaction(txID, false, err.Error())
123- return fmt.Errorf("failed to commit promotion transaction: %w", err)
124- }
125-
126- tr.widgetTxManager.FinishTransaction(txID, true, "")
127-
128 if tr.wm != nil && tr.wm.geometryValidator != nil {104 if tr.wm != nil && tr.wm.geometryValidator != nil {
129 tr.logInitialPromotionGeometry(promotedNode)105 tr.logInitialPromotionGeometry(promotedNode)
130 tr.schedulePromotionValidation(promotedNode)106 tr.schedulePromotionValidation(promotedNode)
@@ -385,10 +361,6 @@ func (tr *TreeRebalancer) executeRebalancingOperations(operations []RebalanceOpe
385 return nil361 return nil
386 }362 }
387 363
388- // Create transaction for all rebalancing operations
389- txID := fmt.Sprintf("rebalance_%d_ops", len(operations))
390- tx := tr.widgetTxManager.BeginTransaction(txID)
391-
392 log.Printf("[tree-rebalancer] Executing %d rebalancing operations", len(operations))364 log.Printf("[tree-rebalancer] Executing %d rebalancing operations", len(operations))
393 365
394 // Execute each operation366 // Execute each operation
@@ -398,40 +370,23 @@ func (tr *TreeRebalancer) executeRebalancingOperations(operations []RebalanceOpe
398 var err error370 var err error
399 switch operation.Type {371 switch operation.Type {
400 case RebalanceRotateLeft:372 case RebalanceRotateLeft:
401- err = tr.executeLeftRotation(operation.Node, tx)373+ err = tr.executeLeftRotation(operation.Node)
402 case RebalanceRotateRight:374 case RebalanceRotateRight:
403- err = tr.executeRightRotation(operation.Node, tx)375+ err = tr.executeRightRotation(operation.Node)
404 case RebalancePromote:376 case RebalancePromote:
405- err = tr.executePromotion(operation.Node, tx)377+ err = tr.executePromotion(operation.Node)
406 case RebalanceRestructure:378 case RebalanceRestructure:
407- err = tr.executeRestructure(operation.Node, tx)379+ err = tr.executeRestructure(operation.Node)
408 default:380 default:
409 err = fmt.Errorf("unknown rebalance operation type: %s", operation.Type)381 err = fmt.Errorf("unknown rebalance operation type: %s", operation.Type)
410 }382 }
411 383
412 if err != nil {384 if err != nil {
413 log.Printf("[tree-rebalancer] Operation failed: %v", err)385 log.Printf("[tree-rebalancer] Operation failed: %v", err)
414- tx.Rollback()
415- tr.widgetTxManager.FinishTransaction(txID, false, err.Error())
416 return fmt.Errorf("rebalancing operation failed: %w", err)386 return fmt.Errorf("rebalancing operation failed: %w", err)
417 }387 }
418 }388 }
419 389
420- // Execute widget transaction
421- if err := tx.Execute(); err != nil {
422- tx.Rollback()
423- tr.widgetTxManager.FinishTransaction(txID, false, err.Error())
424- return fmt.Errorf("failed to execute rebalancing widget operations: %w", err)
425- }
426-
427- // Commit transaction
428- if err := tx.Commit(); err != nil {
429- tr.widgetTxManager.FinishTransaction(txID, false, err.Error())
430- return fmt.Errorf("failed to commit rebalancing transaction: %w", err)
431- }
432-
433- tr.widgetTxManager.FinishTransaction(txID, true, "")
434-
435 // Validate tree after rebalancing390 // Validate tree after rebalancing
436 if tr.treeValidator != nil {391 if tr.treeValidator != nil {
437 if err := tr.treeValidator.ValidateTree(tr.wm.root, "rebalance"); err != nil {392 if err := tr.treeValidator.ValidateTree(tr.wm.root, "rebalance"); err != nil {
@@ -441,14 +396,14 @@ func (tr *TreeRebalancer) executeRebalancingOperations(operations []RebalanceOpe
441 396
442 // Calculate new metrics397 // Calculate new metrics
443 newMetrics := tr.CalculateTreeMetrics(tr.wm.root)398 newMetrics := tr.CalculateTreeMetrics(tr.wm.root)
444- log.Printf("[tree-rebalancer] Rebalancing completed: new imbalance=%d (was %d)",399+ log.Printf("[tree-rebalancer] Rebalancing completed: new imbalance=%d",
445- newMetrics.ImbalanceScore, newMetrics.ImbalanceScore)400+ newMetrics.ImbalanceScore)
446 401
447 return nil402 return nil
448 }403 }
449 404
450 // executeLeftRotation performs a left rotation on the given node405 // executeLeftRotation performs a left rotation on the given node
451-func (tr *TreeRebalancer) executeLeftRotation(node *paneNode, tx *WidgetTransaction) error {406+func (tr *TreeRebalancer) executeLeftRotation(node *paneNode) error {
452 if node == nil || node.right == nil {407 if node == nil || node.right == nil {
453 return fmt.Errorf("cannot perform left rotation: invalid node structure")408 return fmt.Errorf("cannot perform left rotation: invalid node structure")
454 }409 }
@@ -479,12 +434,12 @@ func (tr *TreeRebalancer) executeLeftRotation(node *paneNode, tx *WidgetTransact
479 parent.right = rightChild434 parent.right = rightChild
480 }435 }
481 436
482- // Add widget operations for the rotation437+ // Update widget hierarchy for the rotation
483- return tr.addRotationWidgetOperations(node, rightChild, tx, "left")438+ return tr.updateRotationWidgets(node, rightChild, "left")
484 }439 }
485 440
486 // executeRightRotation performs a right rotation on the given node441 // executeRightRotation performs a right rotation on the given node
487-func (tr *TreeRebalancer) executeRightRotation(node *paneNode, tx *WidgetTransaction) error {442+func (tr *TreeRebalancer) executeRightRotation(node *paneNode) error {
488 if node == nil || node.left == nil {443 if node == nil || node.left == nil {
489 return fmt.Errorf("cannot perform right rotation: invalid node structure")444 return fmt.Errorf("cannot perform right rotation: invalid node structure")
490 }445 }
@@ -515,12 +470,12 @@ func (tr *TreeRebalancer) executeRightRotation(node *paneNode, tx *WidgetTransac
515 parent.right = leftChild470 parent.right = leftChild
516 }471 }
517 472
518- // Add widget operations for the rotation473+ // Update widget hierarchy for the rotation
519- return tr.addRotationWidgetOperations(node, leftChild, tx, "right")474+ return tr.updateRotationWidgets(node, leftChild, "right")
520 }475 }
521 476
522-// addRotationWidgetOperations adds widget operations needed for tree rotation477+// updateRotationWidgets updates widget hierarchy after tree rotation
523-func (tr *TreeRebalancer) addRotationWidgetOperations(oldRoot, newRoot *paneNode, tx *WidgetTransaction, rotationType string) error {478+func (tr *TreeRebalancer) updateRotationWidgets(oldRoot, newRoot *paneNode, rotationType string) error {
524 // The widget hierarchy needs to be updated to match the new tree structure479 // The widget hierarchy needs to be updated to match the new tree structure
525 // This is complex because GTK widgets need to be reparented correctly480 // This is complex because GTK widgets need to be reparented correctly
526 481
@@ -528,40 +483,23 @@ func (tr *TreeRebalancer) addRotationWidgetOperations(oldRoot, newRoot *paneNode
528 return fmt.Errorf("rotation nodes missing containers")483 return fmt.Errorf("rotation nodes missing containers")
529 }484 }
530 485
531- // Add operation to update widget hierarchy486+ log.Printf("[tree-rebalancer] Updating widget hierarchy for %s rotation", rotationType)
532- op := &WidgetOperation{
533- ID: fmt.Sprintf("rotation_%s_%p_%p", rotationType, oldRoot, newRoot),
534- Description: fmt.Sprintf("%s rotation widget update", rotationType),
535- Priority: 300, // High priority for structural changes
536- Execute: func() error {
537- // This is a simplified version - in practice, you'd need to:
538- // 1. Unparent both widgets
539- // 2. Re-establish the parent-child relationships
540- // 3. Update any size allocations
541 487
542- log.Printf("[tree-rebalancer] Updating widget hierarchy for %s rotation", rotationType)488+ // Force a layout update
543-489+ if newRoot.container != nil {
544- // Force a layout update490+ return newRoot.container.Execute(func(ptr uintptr) error {
545- if newRoot.container != nil {491+ if ptr == 0 || !webkit.WidgetIsValid(ptr) {
546- return newRoot.container.Execute(func(ptr uintptr) error {492+ return fmt.Errorf("rotation widget update: invalid widget pointer")
547- webkit.WidgetQueueAllocate(ptr)
548- return nil
549- })
550 }493 }
494+ webkit.WidgetQueueAllocate(ptr)
551 return nil495 return nil
552- },496+ })
553- Rollback: func() error {
554- // Rotation rollback would require reversing the rotation
555- log.Printf("[tree-rebalancer] Rolling back %s rotation", rotationType)
556- return nil
557- },
558 }497 }
559-498+ return nil
560- return tx.AddOperation(op)
561 }499 }
562 500
563 // executePromotion promotes a child node to replace its parent501 // executePromotion promotes a child node to replace its parent
564-func (tr *TreeRebalancer) executePromotion(node *paneNode, tx *WidgetTransaction) error {502+func (tr *TreeRebalancer) executePromotion(node *paneNode) error {
565 if node == nil {503 if node == nil {
566 return fmt.Errorf("promotion target is nil")504 return fmt.Errorf("promotion target is nil")
567 }505 }
@@ -576,95 +514,81 @@ func (tr *TreeRebalancer) executePromotion(node *paneNode, tx *WidgetTransaction
576 promotionStart := time.Now()514 promotionStart := time.Now()
577 515
578 // Ensure the promoted widget can expand to occupy available space516 // Ensure the promoted widget can expand to occupy available space
579- expandOp := &WidgetOperation{517+ log.Printf("[tree-rebalancer] promotion expand for %p executing at +%s", node, time.Since(promotionStart).Round(time.Millisecond))
580- ID: fmt.Sprintf("promotion_expand_%p", node),518+ if err := node.container.Execute(func(ptr uintptr) error {
581- Description: "Ensure promoted pane expands to fill space",519+ if ptr == 0 || !webkit.WidgetIsValid(ptr) {
582- Priority: 200,520+ return fmt.Errorf("promotion expand: invalid widget pointer")
583- Execute: func() error {521+ }
584- log.Printf("[tree-rebalancer] promotion expand for %p executing at +%s", node, time.Since(promotionStart).Round(time.Millisecond))522+ webkit.WidgetResetSizeRequest(ptr)
585- return node.container.Execute(func(ptr uintptr) error {523+ webkit.WidgetSetHExpand(ptr, true)
586- if ptr == 0 || !webkit.WidgetIsValid(ptr) {524+ webkit.WidgetSetVExpand(ptr, true)
587- return fmt.Errorf("promotion expand: invalid widget pointer")525+ webkit.WidgetQueueAllocate(ptr)
588- }526+ return nil
589- webkit.WidgetResetSizeRequest(ptr)527+ }); err != nil {
590- webkit.WidgetSetHExpand(ptr, true)528+ return fmt.Errorf("failed to expand promoted widget: %w", err)
591- webkit.WidgetSetVExpand(ptr, true)
592- webkit.WidgetQueueAllocate(ptr)
593- return nil
594- })
595- },
596- }
597- if err := tx.AddOperation(expandOp); err != nil {
598- return fmt.Errorf("failed to add promotion expand operation: %w", err)
599 }529 }
600 530
601 parent := node.parent531 parent := node.parent
602 if parent == nil {532 if parent == nil {
533+ // Attach to window root
603 if tr.wm != nil && tr.wm.window != nil {534 if tr.wm != nil && tr.wm.window != nil {
604- op := &WidgetOperation{535+ log.Printf("[tree-rebalancer] promotion window attach for %p executing at +%s", node, time.Since(promotionStart).Round(time.Millisecond))
605- ID: fmt.Sprintf("promotion_attach_window_%p", node),536+ if err := node.container.Execute(func(ptr uintptr) error {
606- Description: "Attach promoted pane to window root",537+ if ptr == 0 || !webkit.WidgetIsValid(ptr) {
607- Priority: 190,538+ return fmt.Errorf("promotion attach window: invalid widget pointer")
608- Execute: func() error {539+ }
609- log.Printf("[tree-rebalancer] promotion window attach for %p executing at +%s", node, time.Since(promotionStart).Round(time.Millisecond))
610- return node.container.Execute(func(ptr uintptr) error {
611- if ptr == 0 || !webkit.WidgetIsValid(ptr) {
612- return fmt.Errorf("promotion attach window: invalid widget pointer")
613- }
614 540
615- currentParent := webkit.WidgetGetParent(ptr)541+ currentParent := webkit.WidgetGetParent(ptr)
616- if currentParent != 0 {542+ if currentParent != 0 {
617- // Check if parent is a container widget (paned or box) that we need to unparent from543+ // Check if parent is a container widget (paned or box) that we need to unparent from
618- if webkit.IsPaned(currentParent) {544+ if webkit.IsPaned(currentParent) {
619- log.Printf("[tree-rebalancer] unparenting widget from paned %#x", currentParent)545+ log.Printf("[tree-rebalancer] unparenting widget from paned %#x", currentParent)
620- webkit.WidgetUnparent(ptr)546+ webkit.WidgetUnparent(ptr)
621- } else if webkit.IsBox(currentParent) {547+ } else if webkit.IsBox(currentParent) {
622- log.Printf("[tree-rebalancer] unparenting widget from box (stack) %#x", currentParent)548+ log.Printf("[tree-rebalancer] unparenting widget from box (stack) %#x", currentParent)
623- webkit.WidgetUnparent(ptr)549+ webkit.WidgetUnparent(ptr)
624- } else {550+ } else {
625- // Parent is likely a window or other non-container widget551+ // Parent is likely a window or other non-container widget
626- // For safety, check if it's a toplevel window by attempting unparent552+ // For safety, check if it's a toplevel window by attempting unparent
627- log.Printf("[tree-rebalancer] widget %#x has non-container parent %#x, checking if unparent needed", ptr, currentParent)553+ log.Printf("[tree-rebalancer] widget %#x has non-container parent %#x, checking if unparent needed", ptr, currentParent)
628- if webkit.WidgetIsValid(currentParent) {554+ if webkit.WidgetIsValid(currentParent) {
629- // If parent is valid but not a container, try unparent for safety555+ // If parent is valid but not a container, try unparent for safety
630- webkit.WidgetUnparent(ptr)556+ webkit.WidgetUnparent(ptr)
631- log.Printf("[tree-rebalancer] unparented widget %#x from non-container parent %#x", ptr, currentParent)557+ log.Printf("[tree-rebalancer] unparented widget %#x from non-container parent %#x", ptr, currentParent)
632- }
633- }
634 }558 }
559+ }
560+ }
635 561
636- // Ensure widget is configured for window child562+ // Ensure widget is configured for window child
637- webkit.WidgetSetHExpand(ptr, true)563+ webkit.WidgetSetHExpand(ptr, true)
638- webkit.WidgetSetVExpand(ptr, true)564+ webkit.WidgetSetVExpand(ptr, true)
639-
640- // Set as window child
641- tr.wm.window.SetChild(ptr)
642- webkit.WidgetQueueAllocate(ptr)
643- webkit.WidgetShow(ptr)
644-
645- // Verify the attachment worked
646- finalParent := webkit.WidgetGetParent(ptr)
647- if finalParent == 0 {
648- log.Printf("[tree-rebalancer] WARNING: SetChild failed, widget still has no parent")
649- // Try one more time
650- tr.wm.window.SetChild(ptr)
651- webkit.WidgetQueueAllocate(ptr)
652- finalParent = webkit.WidgetGetParent(ptr)
653- if finalParent == 0 {
654- return fmt.Errorf("failed to attach widget %#x to window after multiple attempts", ptr)
655- }
656- }
657 565
658- log.Printf("[tree-rebalancer] promotion window attach successful: widget %#x now has parent %#x", ptr, finalParent)566+ // Set as window child
659- return nil567+ tr.wm.window.SetChild(ptr)
660- })568+ webkit.WidgetQueueAllocate(ptr)
661- },569+ webkit.WidgetShow(ptr)
662- }570+
663- if err := tx.AddOperation(op); err != nil {571+ // Verify the attachment worked
664- return fmt.Errorf("failed to add window attachment operation: %w", err)572+ finalParent := webkit.WidgetGetParent(ptr)
573+ if finalParent == 0 {
574+ log.Printf("[tree-rebalancer] WARNING: SetChild failed, widget still has no parent")
575+ // Try one more time
576+ tr.wm.window.SetChild(ptr)
577+ webkit.WidgetQueueAllocate(ptr)
578+ finalParent = webkit.WidgetGetParent(ptr)
579+ if finalParent == 0 {
580+ return fmt.Errorf("failed to attach widget %#x to window after multiple attempts", ptr)
581+ }
582+ }
583+
584+ log.Printf("[tree-rebalancer] promotion window attach successful: widget %#x now has parent %#x", ptr, finalParent)
585+ return nil
586+ }); err != nil {
587+ return fmt.Errorf("failed to attach widget to window: %w", err)
665 }588 }
666 }589 }
667 } else {590 } else {
591+ // Attach to parent container
668 if parent.container == nil {592 if parent.container == nil {
669 return fmt.Errorf("promotion parent missing container")593 return fmt.Errorf("promotion parent missing container")
670 }594 }
@@ -672,125 +596,97 @@ func (tr *TreeRebalancer) executePromotion(node *paneNode, tx *WidgetTransaction
672 return fmt.Errorf("promotion parent container %s invalid", parent.container.String())596 return fmt.Errorf("promotion parent container %s invalid", parent.container.String())
673 }597 }
674 598
675- attachOp := &WidgetOperation{599+ log.Printf("[tree-rebalancer] promotion reparent for %p executing at +%s", node, time.Since(promotionStart).Round(time.Millisecond))
676- ID: fmt.Sprintf("promotion_reparent_%p", node),600+ if err := parent.container.Execute(func(parentPtr uintptr) error {
677- Description: "Attach promoted pane to parent container",601+ if parentPtr == 0 || !webkit.WidgetIsValid(parentPtr) {
678- Priority: 190,602+ return fmt.Errorf("promotion reparent: invalid parent widget")
679- Execute: func() error {603+ }
680- log.Printf("[tree-rebalancer] promotion reparent for %p executing at +%s", node, time.Since(promotionStart).Round(time.Millisecond))604+ return node.container.Execute(func(childPtr uintptr) error {
681- return parent.container.Execute(func(parentPtr uintptr) error {605+ if childPtr == 0 || !webkit.WidgetIsValid(childPtr) {
682- if parentPtr == 0 || !webkit.WidgetIsValid(parentPtr) {606+ return fmt.Errorf("promotion reparent: invalid child widget")
683- return fmt.Errorf("promotion reparent: invalid parent widget")607+ }
684- }
685- return node.container.Execute(func(childPtr uintptr) error {
686- if childPtr == 0 || !webkit.WidgetIsValid(childPtr) {
687- return fmt.Errorf("promotion reparent: invalid child widget")
688- }
689 608
690- // Check if widget is already correctly parented (closePane.swapContainers already did the work)609+ // Check if widget is already correctly parented (closePane.swapContainers already did the work)
691- currentParent := webkit.WidgetGetParent(childPtr)610+ currentParent := webkit.WidgetGetParent(childPtr)
692- if currentParent == parentPtr {611+ if currentParent == parentPtr {
693- log.Printf("[tree-rebalancer] widget %#x already correctly parented to %#x, skipping reparent", childPtr, parentPtr)612+ log.Printf("[tree-rebalancer] widget %#x already correctly parented to %#x, skipping reparent", childPtr, parentPtr)
694- // Just ensure properties are set correctly613+ // Just ensure properties are set correctly
695- webkit.WidgetResetSizeRequest(childPtr)614+ webkit.WidgetResetSizeRequest(childPtr)
696- webkit.WidgetSetHExpand(childPtr, true)615+ webkit.WidgetSetHExpand(childPtr, true)
697- webkit.WidgetSetVExpand(childPtr, true)616+ webkit.WidgetSetVExpand(childPtr, true)
698- webkit.WidgetQueueAllocate(childPtr)617+ webkit.WidgetQueueAllocate(childPtr)
699- webkit.WidgetQueueAllocate(parentPtr)618+ webkit.WidgetQueueAllocate(parentPtr)
700- return nil619+ return nil
701- }620+ }
702 621
703- // Widget needs reparenting - unparent from wrong parent if needed622+ // Widget needs reparenting - unparent from wrong parent if needed
704- if currentParent != 0 {623+ if currentParent != 0 {
705- log.Printf("[tree-rebalancer] unparenting widget %#x from incorrect parent %#x", childPtr, currentParent)624+ log.Printf("[tree-rebalancer] unparenting widget %#x from incorrect parent %#x", childPtr, currentParent)
706- webkit.WidgetUnparent(childPtr)625+ webkit.WidgetUnparent(childPtr)
707- }626+ }
708 627
709- // Reattach promoted widget to the GtkPaned628+ // Reattach promoted widget to the GtkPaned
710- if parent.left == node {629+ if parent.left == node {
711- webkit.PanedSetStartChild(parentPtr, childPtr)630+ webkit.PanedSetStartChild(parentPtr, childPtr)
712- } else {631+ } else {
713- webkit.PanedSetEndChild(parentPtr, childPtr)632+ webkit.PanedSetEndChild(parentPtr, childPtr)
714- }633+ }
715 634
716- // Verify attachment succeeded before setting properties635+ // Verify attachment succeeded before setting properties
717- finalParent := webkit.WidgetGetParent(childPtr)636+ finalParent := webkit.WidgetGetParent(childPtr)
718- if finalParent != parentPtr {637+ if finalParent != parentPtr {
719- return fmt.Errorf("failed to attach widget %#x to parent %#x (current parent: %#x)", childPtr, parentPtr, finalParent)638+ return fmt.Errorf("failed to attach widget %#x to parent %#x (current parent: %#x)", childPtr, parentPtr, finalParent)
720- }639+ }
721 640
722- webkit.WidgetResetSizeRequest(childPtr)641+ webkit.WidgetResetSizeRequest(childPtr)
723- webkit.WidgetSetHExpand(childPtr, true)642+ webkit.WidgetSetHExpand(childPtr, true)
724- webkit.WidgetSetVExpand(childPtr, true)643+ webkit.WidgetSetVExpand(childPtr, true)
725- webkit.WidgetQueueAllocate(childPtr)644+ webkit.WidgetQueueAllocate(childPtr)
726- webkit.WidgetQueueAllocate(parentPtr)645+ webkit.WidgetQueueAllocate(parentPtr)
727- return nil646+ return nil
728- })647+ })
729- })648+ }); err != nil {
730- },649+ return fmt.Errorf("failed to reparent promoted widget: %w", err)
731- }
732- if err := tx.AddOperation(attachOp); err != nil {
733- return fmt.Errorf("failed to add promotion reparent operation: %w", err)
734 }650 }
735 }651 }
736 652
737 // Propagate allocation updates up the ancestor chain so GTK recalculates sizes653 // Propagate allocation updates up the ancestor chain so GTK recalculates sizes
738 ancestorPtrs := tr.collectAncestorContainers(node)654 ancestorPtrs := tr.collectAncestorContainers(node)
739 if len(ancestorPtrs) > 0 {655 if len(ancestorPtrs) > 0 {
740- allocationOp := &WidgetOperation{656+ log.Printf("[tree-rebalancer] promotion allocation queue for %p executing at +%s (ancestors=%d)", node, time.Since(promotionStart).Round(time.Millisecond), len(ancestorPtrs))
741- ID: fmt.Sprintf("promotion_allocate_%p", node),657+ for _, ancestorPtr := range ancestorPtrs {
742- Description: "Queue allocation for promoted pane ancestors",658+ if ancestorPtr == 0 || !webkit.WidgetIsValid(ancestorPtr) {
743- Priority: 150,659+ continue
744- Execute: func() error {660+ }
745- log.Printf("[tree-rebalancer] promotion allocation queue for %p executing at +%s (ancestors=%d)", node, time.Since(promotionStart).Round(time.Millisecond), len(ancestorPtrs))661+ webkit.WidgetQueueAllocate(ancestorPtr)
746- for _, ancestorPtr := range ancestorPtrs {
747- if ancestorPtr == 0 || !webkit.WidgetIsValid(ancestorPtr) {
748- continue
749- }
750- webkit.WidgetQueueAllocate(ancestorPtr)
751- }
752- return nil
753- },
754- }
755- if err := tx.AddOperation(allocationOp); err != nil {
756- return fmt.Errorf("failed to add promotion allocation operation: %w", err)
757 }662 }
758 }663 }
759 664
760 // Validate allocation after promotion for debugging purposes665 // Validate allocation after promotion for debugging purposes
761- validateOp := &WidgetOperation{666+ log.Printf("[tree-rebalancer] promotion immediate validation for %p executing at +%s", node, time.Since(promotionStart).Round(time.Millisecond))
762- ID: fmt.Sprintf("promotion_validate_%p", node),667+ _ = node.container.Execute(func(ptr uintptr) error {
763- Description: "Validate promoted pane allocation",668+ if ptr == 0 || !webkit.WidgetIsValid(ptr) {
764- Priority: 100,669+ log.Printf("[tree-rebalancer] promotion validate: invalid widget pointer")
765- Execute: func() error {670+ return nil
766- log.Printf("[tree-rebalancer] promotion immediate validation for %p executing at +%s", node, time.Since(promotionStart).Round(time.Millisecond))671+ }
767- return node.container.Execute(func(ptr uintptr) error {672+ bounds, ok := webkit.WidgetGetBounds(ptr)
768- if ptr == 0 || !webkit.WidgetIsValid(ptr) {673+ if !ok {
769- return fmt.Errorf("promotion validate: invalid widget pointer")674+ log.Printf("[tree-rebalancer] promotion validation: failed to read bounds for %#x", ptr)
770- }675+ return nil
771- bounds, ok := webkit.WidgetGetBounds(ptr)676+ }
772- if !ok {677+ if bounds.Width <= 1 || bounds.Height <= 1 {
773- log.Printf("[tree-rebalancer] promotion validation: failed to read bounds for %#x", ptr)678+ log.Printf("[tree-rebalancer] WARNING: promoted pane %#x has tiny allocation %.1fx%.1f", ptr, bounds.Width, bounds.Height)
774- return nil679+ } else {
775- }680+ log.Printf("[tree-rebalancer] Promotion allocation verified for %#x: %.1fx%.1f", ptr, bounds.Width, bounds.Height)
776- if bounds.Width <= 1 || bounds.Height <= 1 {681+ }
777- log.Printf("[tree-rebalancer] WARNING: promoted pane %#x has tiny allocation %.1fx%.1f", ptr, bounds.Width, bounds.Height)682+ return nil
778- } else {683+ })
779- log.Printf("[tree-rebalancer] Promotion allocation verified for %#x: %.1fx%.1f", ptr, bounds.Width, bounds.Height)
780- }
781- return nil
782- })
783- },
784- }
785- if err := tx.AddOperation(validateOp); err != nil {
786- return fmt.Errorf("failed to add promotion validation operation: %w", err)
787- }
788 684
789 return nil685 return nil
790 }686 }
791 687
792 // executeRestructure performs a complete restructure of a subtree688 // executeRestructure performs a complete restructure of a subtree
793-func (tr *TreeRebalancer) executeRestructure(node *paneNode, tx *WidgetTransaction) error {689+func (tr *TreeRebalancer) executeRestructure(node *paneNode) error {
794 // This would be used for more complex rebalancing scenarios690 // This would be used for more complex rebalancing scenarios
795 log.Printf("[tree-rebalancer] Restructuring subtree at node %p", node)691 log.Printf("[tree-rebalancer] Restructuring subtree at node %p", node)
796 692
D internal/app/browser/workspace_widget_transaction.go +0 -446
@@ -1,446 +0,0 @@
1-// workspace_widget_transaction.go - Atomic widget operations for bulletproof GTK widget management
2-package browser
3-
4-import (
5- "fmt"
6- "log"
7- "sync"
8- "time"
9-
10- "github.com/bnema/dumber/pkg/webkit"
11-)
12-
13-// WidgetOperation represents a single widget operation that can be executed or rolled back
14-type WidgetOperation struct {
15- ID string
16- Description string
17- Execute func() error
18- Rollback func() error
19- Executed bool
20- Priority int // Higher priority operations execute first
21-}
22-
23-// WidgetTransaction manages a set of widget operations atomically
24-type WidgetTransaction struct {
25- ID string
26- operations []*WidgetOperation
27- rollbacks []func() error
28- executed []bool
29- committed bool
30- rolledBack bool
31- startTime time.Time
32- timeout time.Duration
33- mu sync.Mutex
34-}
35-
36-// WidgetTransactionManager manages widget transactions for thread safety
37-type WidgetTransactionManager struct {
38- activeTransactions map[string]*WidgetTransaction
39- transactionHistory []TransactionResult
40- mu sync.RWMutex
41- globalTimeout time.Duration
42- maxHistory int
43-}
44-
45-// TransactionResult captures the result of a transaction
46-type TransactionResult struct {
47- TransactionID string
48- Success bool
49- OperationCount int
50- Duration time.Duration
51- ErrorMessage string
52- Timestamp time.Time
53-}
54-
55-// NewWidgetTransactionManager creates a new transaction manager
56-func NewWidgetTransactionManager() *WidgetTransactionManager {
57- return &WidgetTransactionManager{
58- activeTransactions: make(map[string]*WidgetTransaction),
59- transactionHistory: make([]TransactionResult, 0, 100),
60- globalTimeout: 30 * time.Second,
61- maxHistory: 100,
62- }
63-}
64-
65-// BeginTransaction starts a new widget transaction
66-func (wtm *WidgetTransactionManager) BeginTransaction(id string) *WidgetTransaction {
67- wtm.mu.Lock()
68- defer wtm.mu.Unlock()
69-
70- // Check if transaction already exists
71- if _, exists := wtm.activeTransactions[id]; exists {
72- log.Printf("[widget-tx] Transaction %s already exists, returning existing", id)
73- return wtm.activeTransactions[id]
74- }
75-
76- tx := &WidgetTransaction{
77- ID: id,
78- operations: make([]*WidgetOperation, 0),
79- rollbacks: make([]func() error, 0),
80- executed: make([]bool, 0),
81- startTime: time.Now(),
82- timeout: wtm.globalTimeout,
83- }
84-
85- wtm.activeTransactions[id] = tx
86- log.Printf("[widget-tx] Started transaction: %s", id)
87- return tx
88-}
89-
90-// AddOperation adds an operation to the transaction
91-func (tx *WidgetTransaction) AddOperation(op *WidgetOperation) error {
92- tx.mu.Lock()
93- defer tx.mu.Unlock()
94-
95- if tx.committed || tx.rolledBack {
96- return fmt.Errorf("cannot add operation to finalized transaction")
97- }
98-
99- // Check for timeout
100- if time.Since(tx.startTime) > tx.timeout {
101- return fmt.Errorf("transaction timed out")
102- }
103-
104- tx.operations = append(tx.operations, op)
105- tx.executed = append(tx.executed, false)
106-
107- log.Printf("[widget-tx] Added operation %s to transaction %s", op.ID, tx.ID)
108- return nil
109-}
110-
111-// Execute runs all operations in the transaction
112-func (tx *WidgetTransaction) Execute() error {
113- tx.mu.Lock()
114- defer tx.mu.Unlock()
115-
116- if tx.committed || tx.rolledBack {
117- return fmt.Errorf("transaction already finalized")
118- }
119-
120- // Sort operations by priority (higher priority first)
121- for i := 0; i < len(tx.operations)-1; i++ {
122- for j := i + 1; j < len(tx.operations); j++ {
123- if tx.operations[i].Priority < tx.operations[j].Priority {
124- tx.operations[i], tx.operations[j] = tx.operations[j], tx.operations[i]
125- tx.executed[i], tx.executed[j] = tx.executed[j], tx.executed[i]
126- }
127- }
128- }
129-
130- log.Printf("[widget-tx] Executing transaction %s with %d operations", tx.ID, len(tx.operations))
131-
132- // Execute operations one by one
133- for i, op := range tx.operations {
134- if time.Since(tx.startTime) > tx.timeout {
135- // Timeout - rollback executed operations
136- tx.rollbackExecuted()
137- return fmt.Errorf("transaction %s timed out during execution", tx.ID)
138- }
139-
140- log.Printf("[widget-tx] Executing operation %s (%s)", op.ID, op.Description)
141-
142- if err := op.Execute(); err != nil {
143- log.Printf("[widget-tx] Operation %s failed: %v", op.ID, err)
144-
145- // Rollback all executed operations
146- tx.rollbackExecuted()
147- return fmt.Errorf("operation %s failed: %w", op.ID, err)
148- }
149-
150- tx.executed[i] = true
151- op.Executed = true
152-
153- // Store rollback function if provided
154- if op.Rollback != nil {
155- tx.rollbacks = append([]func() error{op.Rollback}, tx.rollbacks...) // Prepend for reverse order
156- }
157- }
158-
159- log.Printf("[widget-tx] Transaction %s executed successfully", tx.ID)
160- return nil
161-}
162-
163-// Commit finalizes the transaction (prevents rollback)
164-func (tx *WidgetTransaction) Commit() error {
165- tx.mu.Lock()
166- defer tx.mu.Unlock()
167-
168- if tx.committed {
169- return fmt.Errorf("transaction already committed")
170- }
171- if tx.rolledBack {
172- return fmt.Errorf("cannot commit rolled back transaction")
173- }
174-
175- tx.committed = true
176- tx.rollbacks = nil // Clear rollback functions to prevent accidental use
177-
178- log.Printf("[widget-tx] Transaction %s committed", tx.ID)
179- return nil
180-}
181-
182-// Rollback reverses all executed operations
183-func (tx *WidgetTransaction) Rollback() error {
184- tx.mu.Lock()
185- defer tx.mu.Unlock()
186-
187- if tx.committed {
188- return fmt.Errorf("cannot rollback committed transaction")
189- }
190- if tx.rolledBack {
191- return fmt.Errorf("transaction already rolled back")
192- }
193-
194- return tx.rollbackExecuted()
195-}
196-
197-// rollbackExecuted performs the actual rollback (internal, assumes lock held)
198-func (tx *WidgetTransaction) rollbackExecuted() error {
199- log.Printf("[widget-tx] Rolling back transaction %s", tx.ID)
200-
201- var rollbackErrors []error
202-
203- // Execute rollbacks in reverse order
204- for _, rollback := range tx.rollbacks {
205- if err := rollback(); err != nil {
206- rollbackErrors = append(rollbackErrors, err)
207- log.Printf("[widget-tx] Rollback operation failed: %v", err)
208- }
209- }
210-
211- tx.rolledBack = true
212-
213- if len(rollbackErrors) > 0 {
214- return fmt.Errorf("rollback completed with %d errors: %v", len(rollbackErrors), rollbackErrors[0])
215- }
216-
217- log.Printf("[widget-tx] Transaction %s rolled back successfully", tx.ID)
218- return nil
219-}
220-
221-// FinishTransaction completes and removes the transaction from active list
222-func (wtm *WidgetTransactionManager) FinishTransaction(id string, success bool, errorMsg string) {
223- wtm.mu.Lock()
224- defer wtm.mu.Unlock()
225-
226- tx, exists := wtm.activeTransactions[id]
227- if !exists {
228- log.Printf("[widget-tx] Transaction %s not found for finishing", id)
229- return
230- }
231-
232- // Record result
233- result := TransactionResult{
234- TransactionID: id,
235- Success: success,
236- OperationCount: len(tx.operations),
237- Duration: time.Since(tx.startTime),
238- ErrorMessage: errorMsg,
239- Timestamp: time.Now(),
240- }
241-
242- // Add to history
243- if len(wtm.transactionHistory) >= wtm.maxHistory {
244- wtm.transactionHistory = wtm.transactionHistory[1:] // Remove oldest
245- }
246- wtm.transactionHistory = append(wtm.transactionHistory, result)
247-
248- // Remove from active transactions
249- delete(wtm.activeTransactions, id)
250-
251- log.Printf("[widget-tx] Finished transaction %s: success=%v, duration=%v", id, success, result.Duration)
252-}
253-
254-// Common widget operations for convenience
255-
256-// CreateWidgetUnparentOperation creates an operation to unparent a widget
257-func CreateWidgetUnparentOperation(id string, widget *SafeWidget) *WidgetOperation {
258- return &WidgetOperation{
259- ID: id,
260- Description: fmt.Sprintf("Unparent widget %s", widget.String()),
261- Priority: 100, // High priority for cleanup operations
262- Execute: func() error {
263- if widget == nil || !widget.IsValid() {
264- return fmt.Errorf("invalid widget for unparent operation")
265- }
266-
267- return widget.Execute(func(ptr uintptr) error {
268- if webkit.WidgetGetParent(ptr) != 0 {
269- webkit.WidgetUnparent(ptr)
270- }
271- return nil
272- })
273- },
274- Rollback: func() error {
275- // Unparent operations are typically not reversible
276- log.Printf("[widget-tx] Unparent rollback for %s (no-op)", id)
277- return nil
278- },
279- }
280-}
281-
282-// CreateWidgetReparentOperation creates an operation to reparent a widget
283-func CreateWidgetReparentOperation(id string, widget *SafeWidget, newParent uintptr, isStart bool) *WidgetOperation {
284- var oldParent uintptr
285-
286- return &WidgetOperation{
287- ID: id,
288- Description: fmt.Sprintf("Reparent widget %s", widget.String()),
289- Priority: 200, // Medium priority
290- Execute: func() error {
291- if widget == nil || !widget.IsValid() {
292- return fmt.Errorf("invalid widget for reparent operation")
293- }
294- if newParent == 0 {
295- return fmt.Errorf("invalid new parent for reparent operation")
296- }
297-
298- return widget.Execute(func(ptr uintptr) error {
299- // Store old parent for rollback
300- oldParent = webkit.WidgetGetParent(ptr)
301-
302- // Unparent first if needed
303- if oldParent != 0 {
304- webkit.WidgetUnparent(ptr)
305- }
306-
307- // Reparent to new parent
308- if isStart {
309- webkit.PanedSetStartChild(newParent, ptr)
310- } else {
311- webkit.PanedSetEndChild(newParent, ptr)
312- }
313-
314- return nil
315- })
316- },
317- Rollback: func() error {
318- if widget == nil || !widget.IsValid() {
319- return nil // Widget is gone, nothing to rollback
320- }
321-
322- return widget.Execute(func(ptr uintptr) error {
323- // Remove from current parent
324- current := webkit.WidgetGetParent(ptr)
325- if current != 0 {
326- webkit.WidgetUnparent(ptr)
327- }
328-
329- // Restore to old parent if it existed
330- if oldParent != 0 {
331- // Note: This is simplified - in practice we'd need to know which child position to restore
332- log.Printf("[widget-tx] Widget reparent rollback: restoring to parent %#x", oldParent)
333- }
334-
335- return nil
336- })
337- },
338- }
339-}
340-
341-// CreateWidgetShowOperation creates an operation to show/hide a widget
342-func CreateWidgetShowOperation(id string, widget *SafeWidget, visible bool) *WidgetOperation {
343- var wasVisible bool
344-
345- return &WidgetOperation{
346- ID: id,
347- Description: fmt.Sprintf("Set widget %s visibility to %v", widget.String(), visible),
348- Priority: 50, // Low priority for visibility operations
349- Execute: func() error {
350- if widget == nil || !widget.IsValid() {
351- return fmt.Errorf("invalid widget for show operation")
352- }
353-
354- return widget.Execute(func(ptr uintptr) error {
355- // Store current visibility for rollback
356- wasVisible = webkit.WidgetGetVisible(ptr)
357-
358- webkit.WidgetSetVisible(ptr, visible)
359- return nil
360- })
361- },
362- Rollback: func() error {
363- if widget == nil || !widget.IsValid() {
364- return nil // Widget is gone, nothing to rollback
365- }
366-
367- return widget.Execute(func(ptr uintptr) error {
368- webkit.WidgetSetVisible(ptr, wasVisible)
369- return nil
370- })
371- },
372- }
373-}
374-
375-// CreateCSSClassOperation creates an operation to add/remove CSS classes
376-func CreateCSSClassOperation(id string, widget *SafeWidget, className string, add bool) *WidgetOperation {
377- var hadClass bool
378-
379- return &WidgetOperation{
380- ID: id,
381- Description: fmt.Sprintf("CSS class %s: %s on widget %s", map[bool]string{true: "add", false: "remove"}[add], className, widget.String()),
382- Priority: 10, // Very low priority for styling operations
383- Execute: func() error {
384- if widget == nil || !widget.IsValid() {
385- return fmt.Errorf("invalid widget for CSS operation")
386- }
387-
388- return widget.Execute(func(ptr uintptr) error {
389- // Store current state for rollback
390- hadClass = webkit.WidgetHasCSSClass(ptr, className)
391-
392- if add {
393- webkit.WidgetAddCSSClass(ptr, className)
394- } else {
395- webkit.WidgetRemoveCSSClass(ptr, className)
396- }
397- return nil
398- })
399- },
400- Rollback: func() error {
401- if widget == nil || !widget.IsValid() {
402- return nil // Widget is gone, nothing to rollback
403- }
404-
405- return widget.Execute(func(ptr uintptr) error {
406- if hadClass {
407- webkit.WidgetAddCSSClass(ptr, className)
408- } else {
409- webkit.WidgetRemoveCSSClass(ptr, className)
410- }
411- return nil
412- })
413- },
414- }
415-}
416-
417-// GetTransactionStats returns statistics about transactions
418-func (wtm *WidgetTransactionManager) GetTransactionStats() map[string]interface{} {
419- wtm.mu.RLock()
420- defer wtm.mu.RUnlock()
421-
422- totalTransactions := len(wtm.transactionHistory)
423- successfulTransactions := 0
424- totalDuration := time.Duration(0)
425-
426- for _, result := range wtm.transactionHistory {
427- if result.Success {
428- successfulTransactions++
429- }
430- totalDuration += result.Duration
431- }
432-
433- var avgDuration time.Duration
434- if totalTransactions > 0 {
435- avgDuration = totalDuration / time.Duration(totalTransactions)
436- }
437-
438- return map[string]interface{}{
439- "total_transactions": totalTransactions,
440- "successful_transactions": successfulTransactions,
441- "success_rate": float64(successfulTransactions) / float64(max(totalTransactions, 1)),
442- "active_transactions": len(wtm.activeTransactions),
443- "avg_duration_ms": avgDuration.Milliseconds(),
444- "global_timeout_sec": wtm.globalTimeout.Seconds(),
445- }
446-}
@@ -1,446 +0,0 @@
1-// workspace_widget_transaction.go - Atomic widget operations for bulletproof GTK widget management
2-package browser
3-
4-import (
5- "fmt"
6- "log"
7- "sync"
8- "time"
9-
10- "github.com/bnema/dumber/pkg/webkit"
11-)
12-
13-// WidgetOperation represents a single widget operation that can be executed or rolled back
14-type WidgetOperation struct {
15- ID string
16- Description string
17- Execute func() error
18- Rollback func() error
19- Executed bool
20- Priority int // Higher priority operations execute first
21-}
22-
23-// WidgetTransaction manages a set of widget operations atomically
24-type WidgetTransaction struct {
25- ID string
26- operations []*WidgetOperation
27- rollbacks []func() error
28- executed []bool
29- committed bool
30- rolledBack bool
31- startTime time.Time
32- timeout time.Duration
33- mu sync.Mutex
34-}
35-
36-// WidgetTransactionManager manages widget transactions for thread safety
37-type WidgetTransactionManager struct {
38- activeTransactions map[string]*WidgetTransaction
39- transactionHistory []TransactionResult
40- mu sync.RWMutex
41- globalTimeout time.Duration
42- maxHistory int
43-}
44-
45-// TransactionResult captures the result of a transaction
46-type TransactionResult struct {
47- TransactionID string
48- Success bool
49- OperationCount int
50- Duration time.Duration
51- ErrorMessage string
52- Timestamp time.Time
53-}
54-
55-// NewWidgetTransactionManager creates a new transaction manager
56-func NewWidgetTransactionManager() *WidgetTransactionManager {
57- return &WidgetTransactionManager{
58- activeTransactions: make(map[string]*WidgetTransaction),
59- transactionHistory: make([]TransactionResult, 0, 100),
60- globalTimeout: 30 * time.Second,
61- maxHistory: 100,
62- }
63-}
64-
65-// BeginTransaction starts a new widget transaction
66-func (wtm *WidgetTransactionManager) BeginTransaction(id string) *WidgetTransaction {
67- wtm.mu.Lock()
68- defer wtm.mu.Unlock()
69-
70- // Check if transaction already exists
71- if _, exists := wtm.activeTransactions[id]; exists {
72- log.Printf("[widget-tx] Transaction %s already exists, returning existing", id)
73- return wtm.activeTransactions[id]
74- }
75-
76- tx := &WidgetTransaction{
77- ID: id,
78- operations: make([]*WidgetOperation, 0),
79- rollbacks: make([]func() error, 0),
80- executed: make([]bool, 0),
81- startTime: time.Now(),
82- timeout: wtm.globalTimeout,
83- }
84-
85- wtm.activeTransactions[id] = tx
86- log.Printf("[widget-tx] Started transaction: %s", id)
87- return tx
88-}
89-
90-// AddOperation adds an operation to the transaction
91-func (tx *WidgetTransaction) AddOperation(op *WidgetOperation) error {
92- tx.mu.Lock()
93- defer tx.mu.Unlock()
94-
95- if tx.committed || tx.rolledBack {
96- return fmt.Errorf("cannot add operation to finalized transaction")
97- }
98-
99- // Check for timeout
100- if time.Since(tx.startTime) > tx.timeout {
101- return fmt.Errorf("transaction timed out")
102- }
103-
104- tx.operations = append(tx.operations, op)
105- tx.executed = append(tx.executed, false)
106-
107- log.Printf("[widget-tx] Added operation %s to transaction %s", op.ID, tx.ID)
108- return nil
109-}
110-
111-// Execute runs all operations in the transaction
112-func (tx *WidgetTransaction) Execute() error {
113- tx.mu.Lock()
114- defer tx.mu.Unlock()
115-
116- if tx.committed || tx.rolledBack {
117- return fmt.Errorf("transaction already finalized")
118- }
119-
120- // Sort operations by priority (higher priority first)
121- for i := 0; i < len(tx.operations)-1; i++ {
122- for j := i + 1; j < len(tx.operations); j++ {
123- if tx.operations[i].Priority < tx.operations[j].Priority {
124- tx.operations[i], tx.operations[j] = tx.operations[j], tx.operations[i]
125- tx.executed[i], tx.executed[j] = tx.executed[j], tx.executed[i]
126- }
127- }
128- }
129-
130- log.Printf("[widget-tx] Executing transaction %s with %d operations", tx.ID, len(tx.operations))
131-
132- // Execute operations one by one
133- for i, op := range tx.operations {
134- if time.Since(tx.startTime) > tx.timeout {
135- // Timeout - rollback executed operations
136- tx.rollbackExecuted()
137- return fmt.Errorf("transaction %s timed out during execution", tx.ID)
138- }
139-
140- log.Printf("[widget-tx] Executing operation %s (%s)", op.ID, op.Description)
141-
142- if err := op.Execute(); err != nil {
143- log.Printf("[widget-tx] Operation %s failed: %v", op.ID, err)
144-
145- // Rollback all executed operations
146- tx.rollbackExecuted()
147- return fmt.Errorf("operation %s failed: %w", op.ID, err)
148- }
149-
150- tx.executed[i] = true
151- op.Executed = true
152-
153- // Store rollback function if provided
154- if op.Rollback != nil {
155- tx.rollbacks = append([]func() error{op.Rollback}, tx.rollbacks...) // Prepend for reverse order
156- }
157- }
158-
159- log.Printf("[widget-tx] Transaction %s executed successfully", tx.ID)
160- return nil
161-}
162-
163-// Commit finalizes the transaction (prevents rollback)
164-func (tx *WidgetTransaction) Commit() error {
165- tx.mu.Lock()
166- defer tx.mu.Unlock()
167-
168- if tx.committed {
169- return fmt.Errorf("transaction already committed")
170- }
171- if tx.rolledBack {
172- return fmt.Errorf("cannot commit rolled back transaction")
173- }
174-
175- tx.committed = true
176- tx.rollbacks = nil // Clear rollback functions to prevent accidental use
177-
178- log.Printf("[widget-tx] Transaction %s committed", tx.ID)
179- return nil
180-}
181-
182-// Rollback reverses all executed operations
183-func (tx *WidgetTransaction) Rollback() error {
184- tx.mu.Lock()
185- defer tx.mu.Unlock()
186-
187- if tx.committed {
188- return fmt.Errorf("cannot rollback committed transaction")
189- }
190- if tx.rolledBack {
191- return fmt.Errorf("transaction already rolled back")
192- }
193-
194- return tx.rollbackExecuted()
195-}
196-
197-// rollbackExecuted performs the actual rollback (internal, assumes lock held)
198-func (tx *WidgetTransaction) rollbackExecuted() error {
199- log.Printf("[widget-tx] Rolling back transaction %s", tx.ID)
200-
201- var rollbackErrors []error
202-
203- // Execute rollbacks in reverse order
204- for _, rollback := range tx.rollbacks {
205- if err := rollback(); err != nil {
206- rollbackErrors = append(rollbackErrors, err)
207- log.Printf("[widget-tx] Rollback operation failed: %v", err)
208- }
209- }
210-
211- tx.rolledBack = true
212-
213- if len(rollbackErrors) > 0 {
214- return fmt.Errorf("rollback completed with %d errors: %v", len(rollbackErrors), rollbackErrors[0])
215- }
216-
217- log.Printf("[widget-tx] Transaction %s rolled back successfully", tx.ID)
218- return nil
219-}
220-
221-// FinishTransaction completes and removes the transaction from active list
222-func (wtm *WidgetTransactionManager) FinishTransaction(id string, success bool, errorMsg string) {
223- wtm.mu.Lock()
224- defer wtm.mu.Unlock()
225-
226- tx, exists := wtm.activeTransactions[id]
227- if !exists {
228- log.Printf("[widget-tx] Transaction %s not found for finishing", id)
229- return
230- }
231-
232- // Record result
233- result := TransactionResult{
234- TransactionID: id,
235- Success: success,
236- OperationCount: len(tx.operations),
237- Duration: time.Since(tx.startTime),
238- ErrorMessage: errorMsg,
239- Timestamp: time.Now(),
240- }
241-
242- // Add to history
243- if len(wtm.transactionHistory) >= wtm.maxHistory {
244- wtm.transactionHistory = wtm.transactionHistory[1:] // Remove oldest
245- }
246- wtm.transactionHistory = append(wtm.transactionHistory, result)
247-
248- // Remove from active transactions
249- delete(wtm.activeTransactions, id)
250-
251- log.Printf("[widget-tx] Finished transaction %s: success=%v, duration=%v", id, success, result.Duration)
252-}
253-
254-// Common widget operations for convenience
255-
256-// CreateWidgetUnparentOperation creates an operation to unparent a widget
257-func CreateWidgetUnparentOperation(id string, widget *SafeWidget) *WidgetOperation {
258- return &WidgetOperation{
259- ID: id,
260- Description: fmt.Sprintf("Unparent widget %s", widget.String()),
261- Priority: 100, // High priority for cleanup operations
262- Execute: func() error {
263- if widget == nil || !widget.IsValid() {
264- return fmt.Errorf("invalid widget for unparent operation")
265- }
266-
267- return widget.Execute(func(ptr uintptr) error {
268- if webkit.WidgetGetParent(ptr) != 0 {
269- webkit.WidgetUnparent(ptr)
270- }
271- return nil
272- })
273- },
274- Rollback: func() error {
275- // Unparent operations are typically not reversible
276- log.Printf("[widget-tx] Unparent rollback for %s (no-op)", id)
277- return nil
278- },
279- }
280-}
281-
282-// CreateWidgetReparentOperation creates an operation to reparent a widget
283-func CreateWidgetReparentOperation(id string, widget *SafeWidget, newParent uintptr, isStart bool) *WidgetOperation {
284- var oldParent uintptr
285-
286- return &WidgetOperation{
287- ID: id,
288- Description: fmt.Sprintf("Reparent widget %s", widget.String()),
289- Priority: 200, // Medium priority
290- Execute: func() error {
291- if widget == nil || !widget.IsValid() {
292- return fmt.Errorf("invalid widget for reparent operation")
293- }
294- if newParent == 0 {
295- return fmt.Errorf("invalid new parent for reparent operation")
296- }
297-
298- return widget.Execute(func(ptr uintptr) error {
299- // Store old parent for rollback
300- oldParent = webkit.WidgetGetParent(ptr)
301-
302- // Unparent first if needed
303- if oldParent != 0 {
304- webkit.WidgetUnparent(ptr)
305- }
306-
307- // Reparent to new parent
308- if isStart {
309- webkit.PanedSetStartChild(newParent, ptr)
310- } else {
311- webkit.PanedSetEndChild(newParent, ptr)
312- }
313-
314- return nil
315- })
316- },
317- Rollback: func() error {
318- if widget == nil || !widget.IsValid() {
319- return nil // Widget is gone, nothing to rollback
320- }
321-
322- return widget.Execute(func(ptr uintptr) error {
323- // Remove from current parent
324- current := webkit.WidgetGetParent(ptr)
325- if current != 0 {
326- webkit.WidgetUnparent(ptr)
327- }
328-
329- // Restore to old parent if it existed
330- if oldParent != 0 {
331- // Note: This is simplified - in practice we'd need to know which child position to restore
332- log.Printf("[widget-tx] Widget reparent rollback: restoring to parent %#x", oldParent)
333- }
334-
335- return nil
336- })
337- },
338- }
339-}
340-
341-// CreateWidgetShowOperation creates an operation to show/hide a widget
342-func CreateWidgetShowOperation(id string, widget *SafeWidget, visible bool) *WidgetOperation {
343- var wasVisible bool
344-
345- return &WidgetOperation{
346- ID: id,
347- Description: fmt.Sprintf("Set widget %s visibility to %v", widget.String(), visible),
348- Priority: 50, // Low priority for visibility operations
349- Execute: func() error {
350- if widget == nil || !widget.IsValid() {
351- return fmt.Errorf("invalid widget for show operation")
352- }
353-
354- return widget.Execute(func(ptr uintptr) error {
355- // Store current visibility for rollback
356- wasVisible = webkit.WidgetGetVisible(ptr)
357-
358- webkit.WidgetSetVisible(ptr, visible)
359- return nil
360- })
361- },
362- Rollback: func() error {
363- if widget == nil || !widget.IsValid() {
364- return nil // Widget is gone, nothing to rollback
365- }
366-
367- return widget.Execute(func(ptr uintptr) error {
368- webkit.WidgetSetVisible(ptr, wasVisible)
369- return nil
370- })
371- },
372- }
373-}
374-
375-// CreateCSSClassOperation creates an operation to add/remove CSS classes
376-func CreateCSSClassOperation(id string, widget *SafeWidget, className string, add bool) *WidgetOperation {
377- var hadClass bool
378-
379- return &WidgetOperation{
380- ID: id,
381- Description: fmt.Sprintf("CSS class %s: %s on widget %s", map[bool]string{true: "add", false: "remove"}[add], className, widget.String()),
382- Priority: 10, // Very low priority for styling operations
383- Execute: func() error {
384- if widget == nil || !widget.IsValid() {
385- return fmt.Errorf("invalid widget for CSS operation")
386- }
387-
388- return widget.Execute(func(ptr uintptr) error {
389- // Store current state for rollback
390- hadClass = webkit.WidgetHasCSSClass(ptr, className)
391-
392- if add {
393- webkit.WidgetAddCSSClass(ptr, className)
394- } else {
395- webkit.WidgetRemoveCSSClass(ptr, className)
396- }
397- return nil
398- })
399- },
400- Rollback: func() error {
401- if widget == nil || !widget.IsValid() {
402- return nil // Widget is gone, nothing to rollback
403- }
404-
405- return widget.Execute(func(ptr uintptr) error {
406- if hadClass {
407- webkit.WidgetAddCSSClass(ptr, className)
408- } else {
409- webkit.WidgetRemoveCSSClass(ptr, className)
410- }
411- return nil
412- })
413- },
414- }
415-}
416-
417-// GetTransactionStats returns statistics about transactions
418-func (wtm *WidgetTransactionManager) GetTransactionStats() map[string]interface{} {
419- wtm.mu.RLock()
420- defer wtm.mu.RUnlock()
421-
422- totalTransactions := len(wtm.transactionHistory)
423- successfulTransactions := 0
424- totalDuration := time.Duration(0)
425-
426- for _, result := range wtm.transactionHistory {
427- if result.Success {
428- successfulTransactions++
429- }
430- totalDuration += result.Duration
431- }
432-
433- var avgDuration time.Duration
434- if totalTransactions > 0 {
435- avgDuration = totalDuration / time.Duration(totalTransactions)
436- }
437-
438- return map[string]interface{}{
439- "total_transactions": totalTransactions,
440- "successful_transactions": successfulTransactions,
441- "success_rate": float64(successfulTransactions) / float64(max(totalTransactions, 1)),
442- "active_transactions": len(wtm.activeTransactions),
443- "avg_duration_ms": avgDuration.Milliseconds(),
444- "global_timeout_sec": wtm.globalTimeout.Seconds(),
445- }
446-}

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