detached 2027e70

fix: resolve stacked pane title updates and Zellij-style layout positioning

This commit fixes two critical issues with stacked pane title bar behavior:

1. **Title Update Timing**: Previously, panes transitioning from active to inactive
   would not show their current page titles in title bars. Fixed by updating title
   bars at the correct moments:
   - During stack navigation (NavigateStack)
   - During new pane creation (finalizeStackCreation)

2. **GTK Widget Positioning**: Fixed incorrect Zellij-style layout where title bars
   appeared in wrong positions. Corrected GTK widget insertion order:
   - addPaneToExistingStack(): Now uses BoxInsertChildAfter for correct positioning
   - updateTitleBarLabel(): Now maintains proper widget order when replacing title bars

The layout now properly follows Zellij-style behavior:
- 2 panes: Title bar appears above OR below active pane depending on which is focused
- 3+ panes: Title bars appear above AND below active pane in correct order

User-visible changes:
- Collapsed panes now show current page titles immediately when switching
- Title bars appear in correct positions (above/below active pane)
- Proper visual layout maintained during pane creation and navigation

Technical changes:
- Enhanced NavigateStack() with title update before visibility changes
- Enhanced finalizeStackCreation() with title update for previously active pane
- Fixed addPaneToExistingStack() widget insertion using BoxInsertChildAfter()
- Fixed updateTitleBarLabel() positioning logic for title bar replacement
commit
2027e707392aa53ef53e95e9c35db8ea916459ce
author
bamen · · 2025-09-25 20:25 UTC
committer
bamen · · 2025-09-25 19:04 UTC
parent
0a17c0a
signature
unsigned
2 files · +67 -6
M CHANGELOG.md +1 -0
@@ -21,6 +21,7 @@ All notable changes to this project will be documented in this file.
2121 - **WebView title updates**: Enhanced webview title change handling to properly update stacked pane title bars in addition to database storage
2222 - **GTK4 widget lifecycle**: Improved widget parenting/unparenting operations with proper GTK4 validation and automatic cleanup to prevent critical warnings
2323 - **Stacked pane close behavior**: Fixed remaining pane becoming non-interactable (gray background) when closing stacked panes via Ctrl+W or Ctrl+P+X. Added proper widget visibility restoration and focus management during stack-to-regular conversion
24+- **Stacked pane title updates**: Fixed collapsed panes not showing current page titles when switching between stacked panes. Now follows Zellij-style layout where hidden panes display their up-to-date page titles in title bars with correct positioning above and below the active pane
2425
2526 ## [0.10.0] - 2025-09-24
2627
@@ -21,6 +21,7 @@ All notable changes to this project will be documented in this file.
21 - **WebView title updates**: Enhanced webview title change handling to properly update stacked pane title bars in addition to database storage21 - **WebView title updates**: Enhanced webview title change handling to properly update stacked pane title bars in addition to database storage
22 - **GTK4 widget lifecycle**: Improved widget parenting/unparenting operations with proper GTK4 validation and automatic cleanup to prevent critical warnings22 - **GTK4 widget lifecycle**: Improved widget parenting/unparenting operations with proper GTK4 validation and automatic cleanup to prevent critical warnings
23 - **Stacked pane close behavior**: Fixed remaining pane becoming non-interactable (gray background) when closing stacked panes via Ctrl+W or Ctrl+P+X. Added proper widget visibility restoration and focus management during stack-to-regular conversion23 - **Stacked pane close behavior**: Fixed remaining pane becoming non-interactable (gray background) when closing stacked panes via Ctrl+W or Ctrl+P+X. Added proper widget visibility restoration and focus management during stack-to-regular conversion
24+- **Stacked pane title updates**: Fixed collapsed panes not showing current page titles when switching between stacked panes. Now follows Zellij-style layout where hidden panes display their up-to-date page titles in title bars with correct positioning above and below the active pane
24 25
25 ## [0.10.0] - 2025-09-2426 ## [0.10.0] - 2025-09-24
26 27
M internal/app/browser/stacked_panes.go +66 -6
@@ -134,9 +134,29 @@ func (spm *StackedPaneManager) addPaneToExistingStack(target, newLeaf *paneNode)
134134 webkit.WidgetUnparent(newLeaf.container)
135135 }
136136
137- // Add the new widgets to the internal stack box (not the wrapper)
138- webkit.BoxAppend(stackNode.stackWrapper, newLeaf.titleBar)
139- webkit.BoxAppend(stackNode.stackWrapper, newLeaf.container)
137+ // Insert widgets at correct position for Zellij-style layout
138+ // Each pane has 2 widgets (titleBar, container), so position = insertIndex * 2
139+
140+ // Find the widget to insert after (based on insertIndex)
141+ var insertAfterWidget uintptr = 0
142+ if insertIndex > 0 {
143+ // Insert after the previous pane's container
144+ prevPane := stackNode.stackedPanes[insertIndex-1]
145+ insertAfterWidget = prevPane.container
146+ }
147+
148+ if insertAfterWidget != 0 {
149+ // Insert titleBar after the previous pane's container
150+ webkit.BoxInsertChildAfter(stackNode.stackWrapper, newLeaf.titleBar, insertAfterWidget)
151+ // Insert container after the newly inserted titleBar
152+ webkit.BoxInsertChildAfter(stackNode.stackWrapper, newLeaf.container, newLeaf.titleBar)
153+ log.Printf("[workspace] inserted widgets at position %d (after widget %p)", insertIndex, insertAfterWidget)
154+ } else {
155+ // Insert at the beginning (insertIndex = 0)
156+ webkit.BoxPrepend(stackNode.stackWrapper, newLeaf.container)
157+ webkit.BoxPrepend(stackNode.stackWrapper, newLeaf.titleBar)
158+ log.Printf("[workspace] prepended widgets at position 0")
159+ }
140160
141161 return stackNode, insertIndex, nil
142162 }
@@ -286,6 +306,19 @@ func (spm *StackedPaneManager) finalizeStackCreation(stackNode, newLeaf *paneNod
286306 // Mark stack operation timestamp to prevent focus conflicts
287307 spm.wm.lastStackOperation = time.Now()
288308
309+ // This ensures Zellij-style layout where the previously active pane shows its current title
310+ currentActiveIndex := stackNode.activeStackIndex
311+ if currentActiveIndex >= 0 && currentActiveIndex < len(stackNode.stackedPanes) && currentActiveIndex != insertIndex {
312+ currentlyActivePane := stackNode.stackedPanes[currentActiveIndex]
313+ if currentlyActivePane.pane != nil && currentlyActivePane.pane.webView != nil && currentlyActivePane.titleBar != 0 {
314+ currentTitle := currentlyActivePane.pane.webView.GetTitle()
315+ if currentTitle != "" {
316+ spm.updateTitleBarLabel(currentlyActivePane, currentTitle)
317+ log.Printf("[workspace] updated title bar for pane becoming inactive during stack creation: %s", currentTitle)
318+ }
319+ }
320+ }
321+
289322 // Immediately switch to the new pane (don't use deprecated IdleAdd)
290323 stackNode.activeStackIndex = insertIndex
291324 spm.UpdateStackVisibility(stackNode)
@@ -400,6 +433,17 @@ func (spm *StackedPaneManager) NavigateStack(direction string) bool {
400433 return false // No change
401434 }
402435
436+ // Update title bar for the pane transitioning from ACTIVE to INACTIVE
437+ // This ensures the collapsed pane shows its current page title (Zellij-style layout)
438+ currentActivePane := stackNode.stackedPanes[currentIndex]
439+ if currentActivePane.pane != nil && currentActivePane.pane.webView != nil && currentActivePane.titleBar != 0 {
440+ currentTitle := currentActivePane.pane.webView.GetTitle()
441+ if currentTitle != "" {
442+ spm.updateTitleBarLabel(currentActivePane, currentTitle)
443+ log.Printf("[workspace] updated title bar for pane transitioning to INACTIVE: %s", currentTitle)
444+ }
445+ }
446+
403447 // Update active stack index and visibility
404448 stackNode.activeStackIndex = newIndex
405449 spm.UpdateStackVisibility(stackNode)
@@ -515,16 +559,32 @@ func (spm *StackedPaneManager) updateTitleBarLabel(node *paneNode, title string)
515559
516560 // Replace the old title bar in the stack
517561 if node.parent != nil && node.parent.isStacked && node.parent.stackWrapper != 0 {
562+ // Find the correct insertion position based on the pane's index in the stack
563+ paneIndex := -1
564+ for i, stackedPane := range node.parent.stackedPanes {
565+ if stackedPane == node {
566+ paneIndex = i
567+ break
568+ }
569+ }
570+
518571 // Remove old title bar
519572 webkit.WidgetUnparent(node.titleBar)
520573
521574 // Update the node's title bar reference
522575 node.titleBar = newTitleBar
523576
524- // Add new title bar to the stack (need to find correct position)
525- webkit.BoxPrepend(node.parent.stackWrapper, newTitleBar)
577+ // Insert the new title bar at the correct position
578+ if paneIndex == 0 {
579+ // First pane - insert at the beginning
580+ webkit.BoxPrepend(node.parent.stackWrapper, newTitleBar)
581+ } else {
582+ // Insert after the previous pane's container
583+ prevPane := node.parent.stackedPanes[paneIndex-1]
584+ webkit.BoxInsertChildAfter(node.parent.stackWrapper, newTitleBar, prevPane.container)
585+ }
526586
527- log.Printf("[workspace] replaced title bar for pane: %p", node)
587+ log.Printf("[workspace] replaced title bar for pane %d: %p", paneIndex, node)
528588 }
529589 }
530590
@@ -134,9 +134,29 @@ func (spm *StackedPaneManager) addPaneToExistingStack(target, newLeaf *paneNode)
134 webkit.WidgetUnparent(newLeaf.container)134 webkit.WidgetUnparent(newLeaf.container)
135 }135 }
136 136
137- // Add the new widgets to the internal stack box (not the wrapper)137+ // Insert widgets at correct position for Zellij-style layout
138- webkit.BoxAppend(stackNode.stackWrapper, newLeaf.titleBar)138+ // Each pane has 2 widgets (titleBar, container), so position = insertIndex * 2
139- webkit.BoxAppend(stackNode.stackWrapper, newLeaf.container)139+
140+ // Find the widget to insert after (based on insertIndex)
141+ var insertAfterWidget uintptr = 0
142+ if insertIndex > 0 {
143+ // Insert after the previous pane's container
144+ prevPane := stackNode.stackedPanes[insertIndex-1]
145+ insertAfterWidget = prevPane.container
146+ }
147+
148+ if insertAfterWidget != 0 {
149+ // Insert titleBar after the previous pane's container
150+ webkit.BoxInsertChildAfter(stackNode.stackWrapper, newLeaf.titleBar, insertAfterWidget)
151+ // Insert container after the newly inserted titleBar
152+ webkit.BoxInsertChildAfter(stackNode.stackWrapper, newLeaf.container, newLeaf.titleBar)
153+ log.Printf("[workspace] inserted widgets at position %d (after widget %p)", insertIndex, insertAfterWidget)
154+ } else {
155+ // Insert at the beginning (insertIndex = 0)
156+ webkit.BoxPrepend(stackNode.stackWrapper, newLeaf.container)
157+ webkit.BoxPrepend(stackNode.stackWrapper, newLeaf.titleBar)
158+ log.Printf("[workspace] prepended widgets at position 0")
159+ }
140 160
141 return stackNode, insertIndex, nil161 return stackNode, insertIndex, nil
142 }162 }
@@ -286,6 +306,19 @@ func (spm *StackedPaneManager) finalizeStackCreation(stackNode, newLeaf *paneNod
286 // Mark stack operation timestamp to prevent focus conflicts306 // Mark stack operation timestamp to prevent focus conflicts
287 spm.wm.lastStackOperation = time.Now()307 spm.wm.lastStackOperation = time.Now()
288 308
309+ // This ensures Zellij-style layout where the previously active pane shows its current title
310+ currentActiveIndex := stackNode.activeStackIndex
311+ if currentActiveIndex >= 0 && currentActiveIndex < len(stackNode.stackedPanes) && currentActiveIndex != insertIndex {
312+ currentlyActivePane := stackNode.stackedPanes[currentActiveIndex]
313+ if currentlyActivePane.pane != nil && currentlyActivePane.pane.webView != nil && currentlyActivePane.titleBar != 0 {
314+ currentTitle := currentlyActivePane.pane.webView.GetTitle()
315+ if currentTitle != "" {
316+ spm.updateTitleBarLabel(currentlyActivePane, currentTitle)
317+ log.Printf("[workspace] updated title bar for pane becoming inactive during stack creation: %s", currentTitle)
318+ }
319+ }
320+ }
321+
289 // Immediately switch to the new pane (don't use deprecated IdleAdd)322 // Immediately switch to the new pane (don't use deprecated IdleAdd)
290 stackNode.activeStackIndex = insertIndex323 stackNode.activeStackIndex = insertIndex
291 spm.UpdateStackVisibility(stackNode)324 spm.UpdateStackVisibility(stackNode)
@@ -400,6 +433,17 @@ func (spm *StackedPaneManager) NavigateStack(direction string) bool {
400 return false // No change433 return false // No change
401 }434 }
402 435
436+ // Update title bar for the pane transitioning from ACTIVE to INACTIVE
437+ // This ensures the collapsed pane shows its current page title (Zellij-style layout)
438+ currentActivePane := stackNode.stackedPanes[currentIndex]
439+ if currentActivePane.pane != nil && currentActivePane.pane.webView != nil && currentActivePane.titleBar != 0 {
440+ currentTitle := currentActivePane.pane.webView.GetTitle()
441+ if currentTitle != "" {
442+ spm.updateTitleBarLabel(currentActivePane, currentTitle)
443+ log.Printf("[workspace] updated title bar for pane transitioning to INACTIVE: %s", currentTitle)
444+ }
445+ }
446+
403 // Update active stack index and visibility447 // Update active stack index and visibility
404 stackNode.activeStackIndex = newIndex448 stackNode.activeStackIndex = newIndex
405 spm.UpdateStackVisibility(stackNode)449 spm.UpdateStackVisibility(stackNode)
@@ -515,16 +559,32 @@ func (spm *StackedPaneManager) updateTitleBarLabel(node *paneNode, title string)
515 559
516 // Replace the old title bar in the stack560 // Replace the old title bar in the stack
517 if node.parent != nil && node.parent.isStacked && node.parent.stackWrapper != 0 {561 if node.parent != nil && node.parent.isStacked && node.parent.stackWrapper != 0 {
562+ // Find the correct insertion position based on the pane's index in the stack
563+ paneIndex := -1
564+ for i, stackedPane := range node.parent.stackedPanes {
565+ if stackedPane == node {
566+ paneIndex = i
567+ break
568+ }
569+ }
570+
518 // Remove old title bar571 // Remove old title bar
519 webkit.WidgetUnparent(node.titleBar)572 webkit.WidgetUnparent(node.titleBar)
520 573
521 // Update the node's title bar reference574 // Update the node's title bar reference
522 node.titleBar = newTitleBar575 node.titleBar = newTitleBar
523 576
524- // Add new title bar to the stack (need to find correct position)577+ // Insert the new title bar at the correct position
525- webkit.BoxPrepend(node.parent.stackWrapper, newTitleBar)578+ if paneIndex == 0 {
579+ // First pane - insert at the beginning
580+ webkit.BoxPrepend(node.parent.stackWrapper, newTitleBar)
581+ } else {
582+ // Insert after the previous pane's container
583+ prevPane := node.parent.stackedPanes[paneIndex-1]
584+ webkit.BoxInsertChildAfter(node.parent.stackWrapper, newTitleBar, prevPane.container)
585+ }
526 586
527- log.Printf("[workspace] replaced title bar for pane: %p", node)587+ log.Printf("[workspace] replaced title bar for pane %d: %p", paneIndex, node)
528 }588 }
529 }589 }
530 590

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