detached 9001867

fix: track pane IDs in StackedView to enable favicon and title updates

commit
9001867fd4ef1e0acd1e25b7e34e81e74ce01515
author
brice · · 2025-12-23 22:11 UTC
committer
brice · · 2025-12-24 16:11 UTC
parent
12c9f08
signature
unsigned
6 files · +80 -79
M internal/ui/component/stacked_pane_manager.go +1 -1
@@ -76,7 +76,7 @@ func (spm *StackedPaneManager) AddPaneToStack(
7676 Int("stack_size_before", stackedView.Count()).
7777 Msg("StackedPaneManager: inserting pane after active position")
7878
79- stackedView.InsertPaneAfter(ctx, currentActiveIndex, title, "", widget)
79+ stackedView.InsertPaneAfter(ctx, currentActiveIndex, string(newPaneView.PaneID()), title, "", widget)
8080
8181 // Register the new pane in the TreeRenderer's tracking
8282 tr.RegisterPaneInStack(string(newPaneView.PaneID()), stackedView)
@@ -76,7 +76,7 @@ func (spm *StackedPaneManager) AddPaneToStack(
76 Int("stack_size_before", stackedView.Count()).76 Int("stack_size_before", stackedView.Count()).
77 Msg("StackedPaneManager: inserting pane after active position")77 Msg("StackedPaneManager: inserting pane after active position")
78 78
79- stackedView.InsertPaneAfter(ctx, currentActiveIndex, title, "", widget)79+ stackedView.InsertPaneAfter(ctx, currentActiveIndex, string(newPaneView.PaneID()), title, "", widget)
80 80
81 // Register the new pane in the TreeRenderer's tracking81 // Register the new pane in the TreeRenderer's tracking
82 tr.RegisterPaneInStack(string(newPaneView.PaneID()), stackedView)82 tr.RegisterPaneInStack(string(newPaneView.PaneID()), stackedView)
M internal/ui/coordinator/content.go +21 -39
@@ -472,7 +472,7 @@ func (c *ContentCoordinator) onTitleChanged(ctx context.Context, paneID entity.P
472472 if tr != nil {
473473 stackedView := tr.GetStackedViewForPane(string(paneID))
474474 if stackedView != nil {
475- c.updateStackedPaneTitle(ctx, ws, stackedView, paneID, title)
475+ c.updateStackedPaneTitle(ctx, stackedView, paneID, title)
476476 }
477477 }
478478 }
@@ -496,32 +496,23 @@ func (c *ContentCoordinator) onTitleChanged(ctx context.Context, paneID entity.P
496496 // updateStackedPaneTitle updates the title of a pane in a StackedView.
497497 func (c *ContentCoordinator) updateStackedPaneTitle(
498498 ctx context.Context,
499- ws *entity.Workspace,
500499 sv *layout.StackedView,
501500 paneID entity.PaneID,
502501 title string,
503502 ) {
504503 log := logging.FromContext(ctx)
505504
506- if ws == nil {
505+ // Find the pane's index directly in the StackedView
506+ index := sv.FindPaneIndex(string(paneID))
507+ if index < 0 {
508+ log.Debug().
509+ Str("pane_id", string(paneID)).
510+ Msg("pane not found in StackedView for title update")
507511 return
508512 }
509513
510- paneNode := ws.FindPane(paneID)
511- if paneNode == nil {
512- return
513- }
514-
515- // If the pane is in a stacked parent, find its index
516- if paneNode.Parent != nil && paneNode.Parent.IsStacked {
517- for i, child := range paneNode.Parent.Children {
518- if child.Pane != nil && child.Pane.ID == paneID {
519- if err := sv.UpdateTitle(i, title); err != nil {
520- log.Warn().Err(err).Int("index", i).Msg("failed to update stacked pane title")
521- }
522- return
523- }
524- }
514+ if err := sv.UpdateTitle(index, title); err != nil {
515+ log.Warn().Err(err).Int("index", index).Msg("failed to update stacked pane title")
525516 }
526517 }
527518
@@ -545,13 +536,13 @@ func (c *ContentCoordinator) onFaviconChanged(ctx context.Context, paneID entity
545536 }
546537
547538 // Update StackedView favicon if this pane is in a stack
548- ws, wsView := c.getActiveWS()
539+ _, wsView := c.getActiveWS()
549540 if wsView != nil {
550541 tr := wsView.TreeRenderer()
551542 if tr != nil {
552543 stackedView := tr.GetStackedViewForPane(string(paneID))
553544 if stackedView != nil {
554- c.updateStackedPaneFavicon(ctx, ws, stackedView, paneID, favicon)
545+ c.updateStackedPaneFavicon(ctx, stackedView, paneID, favicon)
555546 }
556547 }
557548 }
@@ -566,32 +557,23 @@ func (c *ContentCoordinator) onFaviconChanged(ctx context.Context, paneID entity
566557 // updateStackedPaneFavicon updates the favicon of a pane in a StackedView.
567558 func (c *ContentCoordinator) updateStackedPaneFavicon(
568559 ctx context.Context,
569- ws *entity.Workspace,
570560 sv *layout.StackedView,
571561 paneID entity.PaneID,
572562 favicon *gdk.Texture,
573563 ) {
574564 log := logging.FromContext(ctx)
575565
576- if ws == nil {
566+ // Find the pane's index directly in the StackedView
567+ index := sv.FindPaneIndex(string(paneID))
568+ if index < 0 {
569+ log.Debug().
570+ Str("pane_id", string(paneID)).
571+ Msg("pane not found in StackedView for favicon update")
577572 return
578573 }
579574
580- paneNode := ws.FindPane(paneID)
581- if paneNode == nil {
582- return
583- }
584-
585- // If the pane is in a stacked parent, find its index
586- if paneNode.Parent != nil && paneNode.Parent.IsStacked {
587- for i, child := range paneNode.Parent.Children {
588- if child.Pane != nil && child.Pane.ID == paneID {
589- if err := sv.UpdateFaviconTexture(i, favicon); err != nil {
590- log.Warn().Err(err).Int("index", i).Msg("failed to update stacked pane favicon")
591- }
592- return
593- }
594- }
575+ if err := sv.UpdateFaviconTexture(index, favicon); err != nil {
576+ log.Warn().Err(err).Int("index", index).Msg("failed to update stacked pane favicon")
595577 }
596578 }
597579
@@ -624,13 +606,13 @@ func (c *ContentCoordinator) PreloadCachedFavicon(ctx context.Context, paneID en
624606 }
625607
626608 // Update stacked pane favicon if applicable
627- ws, wsView := c.getActiveWS()
609+ _, wsView := c.getActiveWS()
628610 if wsView != nil {
629611 tr := wsView.TreeRenderer()
630612 if tr != nil {
631613 stackedView := tr.GetStackedViewForPane(string(paneID))
632614 if stackedView != nil {
633- c.updateStackedPaneFavicon(ctx, ws, stackedView, paneID, texture)
615+ c.updateStackedPaneFavicon(ctx, stackedView, paneID, texture)
634616 }
635617 }
636618 }
@@ -472,7 +472,7 @@ func (c *ContentCoordinator) onTitleChanged(ctx context.Context, paneID entity.P
472 if tr != nil {472 if tr != nil {
473 stackedView := tr.GetStackedViewForPane(string(paneID))473 stackedView := tr.GetStackedViewForPane(string(paneID))
474 if stackedView != nil {474 if stackedView != nil {
475- c.updateStackedPaneTitle(ctx, ws, stackedView, paneID, title)475+ c.updateStackedPaneTitle(ctx, stackedView, paneID, title)
476 }476 }
477 }477 }
478 }478 }
@@ -496,32 +496,23 @@ func (c *ContentCoordinator) onTitleChanged(ctx context.Context, paneID entity.P
496 // updateStackedPaneTitle updates the title of a pane in a StackedView.496 // updateStackedPaneTitle updates the title of a pane in a StackedView.
497 func (c *ContentCoordinator) updateStackedPaneTitle(497 func (c *ContentCoordinator) updateStackedPaneTitle(
498 ctx context.Context,498 ctx context.Context,
499- ws *entity.Workspace,
500 sv *layout.StackedView,499 sv *layout.StackedView,
501 paneID entity.PaneID,500 paneID entity.PaneID,
502 title string,501 title string,
503 ) {502 ) {
504 log := logging.FromContext(ctx)503 log := logging.FromContext(ctx)
505 504
506- if ws == nil {505+ // Find the pane's index directly in the StackedView
506+ index := sv.FindPaneIndex(string(paneID))
507+ if index < 0 {
508+ log.Debug().
509+ Str("pane_id", string(paneID)).
510+ Msg("pane not found in StackedView for title update")
507 return511 return
508 }512 }
509 513
510- paneNode := ws.FindPane(paneID)514+ if err := sv.UpdateTitle(index, title); err != nil {
511- if paneNode == nil {515+ log.Warn().Err(err).Int("index", index).Msg("failed to update stacked pane title")
512- return
513- }
514-
515- // If the pane is in a stacked parent, find its index
516- if paneNode.Parent != nil && paneNode.Parent.IsStacked {
517- for i, child := range paneNode.Parent.Children {
518- if child.Pane != nil && child.Pane.ID == paneID {
519- if err := sv.UpdateTitle(i, title); err != nil {
520- log.Warn().Err(err).Int("index", i).Msg("failed to update stacked pane title")
521- }
522- return
523- }
524- }
525 }516 }
526 }517 }
527 518
@@ -545,13 +536,13 @@ func (c *ContentCoordinator) onFaviconChanged(ctx context.Context, paneID entity
545 }536 }
546 537
547 // Update StackedView favicon if this pane is in a stack538 // Update StackedView favicon if this pane is in a stack
548- ws, wsView := c.getActiveWS()539+ _, wsView := c.getActiveWS()
549 if wsView != nil {540 if wsView != nil {
550 tr := wsView.TreeRenderer()541 tr := wsView.TreeRenderer()
551 if tr != nil {542 if tr != nil {
552 stackedView := tr.GetStackedViewForPane(string(paneID))543 stackedView := tr.GetStackedViewForPane(string(paneID))
553 if stackedView != nil {544 if stackedView != nil {
554- c.updateStackedPaneFavicon(ctx, ws, stackedView, paneID, favicon)545+ c.updateStackedPaneFavicon(ctx, stackedView, paneID, favicon)
555 }546 }
556 }547 }
557 }548 }
@@ -566,32 +557,23 @@ func (c *ContentCoordinator) onFaviconChanged(ctx context.Context, paneID entity
566 // updateStackedPaneFavicon updates the favicon of a pane in a StackedView.557 // updateStackedPaneFavicon updates the favicon of a pane in a StackedView.
567 func (c *ContentCoordinator) updateStackedPaneFavicon(558 func (c *ContentCoordinator) updateStackedPaneFavicon(
568 ctx context.Context,559 ctx context.Context,
569- ws *entity.Workspace,
570 sv *layout.StackedView,560 sv *layout.StackedView,
571 paneID entity.PaneID,561 paneID entity.PaneID,
572 favicon *gdk.Texture,562 favicon *gdk.Texture,
573 ) {563 ) {
574 log := logging.FromContext(ctx)564 log := logging.FromContext(ctx)
575 565
576- if ws == nil {566+ // Find the pane's index directly in the StackedView
567+ index := sv.FindPaneIndex(string(paneID))
568+ if index < 0 {
569+ log.Debug().
570+ Str("pane_id", string(paneID)).
571+ Msg("pane not found in StackedView for favicon update")
577 return572 return
578 }573 }
579 574
580- paneNode := ws.FindPane(paneID)575+ if err := sv.UpdateFaviconTexture(index, favicon); err != nil {
581- if paneNode == nil {576+ log.Warn().Err(err).Int("index", index).Msg("failed to update stacked pane favicon")
582- return
583- }
584-
585- // If the pane is in a stacked parent, find its index
586- if paneNode.Parent != nil && paneNode.Parent.IsStacked {
587- for i, child := range paneNode.Parent.Children {
588- if child.Pane != nil && child.Pane.ID == paneID {
589- if err := sv.UpdateFaviconTexture(i, favicon); err != nil {
590- log.Warn().Err(err).Int("index", i).Msg("failed to update stacked pane favicon")
591- }
592- return
593- }
594- }
595 }577 }
596 }578 }
597 579
@@ -624,13 +606,13 @@ func (c *ContentCoordinator) PreloadCachedFavicon(ctx context.Context, paneID en
624 }606 }
625 607
626 // Update stacked pane favicon if applicable608 // Update stacked pane favicon if applicable
627- ws, wsView := c.getActiveWS()609+ _, wsView := c.getActiveWS()
628 if wsView != nil {610 if wsView != nil {
629 tr := wsView.TreeRenderer()611 tr := wsView.TreeRenderer()
630 if tr != nil {612 if tr != nil {
631 stackedView := tr.GetStackedViewForPane(string(paneID))613 stackedView := tr.GetStackedViewForPane(string(paneID))
632 if stackedView != nil {614 if stackedView != nil {
633- c.updateStackedPaneFavicon(ctx, ws, stackedView, paneID, texture)615+ c.updateStackedPaneFavicon(ctx, stackedView, paneID, texture)
634 }616 }
635 }617 }
636 }618 }
M internal/ui/coordinator/workspace.go +2 -2
@@ -287,7 +287,7 @@ func (c *WorkspaceCoordinator) doIncrementalStackSplit(
287287
288288 // 4. Wrap the new PaneView in a StackedView
289289 newStackedView := layout.NewStackedView(factory)
290- newStackedView.AddPane(ctx, output.NewPaneNode.Pane.Title, "", newPaneView.Widget())
290+ newStackedView.AddPane(ctx, string(output.NewPaneNode.Pane.ID), output.NewPaneNode.Pane.Title, "", newPaneView.Widget())
291291
292292 tr := wsView.TreeRenderer()
293293
@@ -461,7 +461,7 @@ func (c *WorkspaceCoordinator) doIncrementalSplit(
461461
462462 // 5. Wrap the new PaneView in a StackedView
463463 newStackedView := layout.NewStackedView(factory)
464- newStackedView.AddPane(ctx, output.NewPaneNode.Pane.Title, "", newPaneView.Widget())
464+ newStackedView.AddPane(ctx, string(output.NewPaneNode.Pane.ID), output.NewPaneNode.Pane.Title, "", newPaneView.Widget())
465465
466466 orientation, existingFirst, ok := splitOrientation(direction)
467467 if !ok {
@@ -287,7 +287,7 @@ func (c *WorkspaceCoordinator) doIncrementalStackSplit(
287 287
288 // 4. Wrap the new PaneView in a StackedView288 // 4. Wrap the new PaneView in a StackedView
289 newStackedView := layout.NewStackedView(factory)289 newStackedView := layout.NewStackedView(factory)
290- newStackedView.AddPane(ctx, output.NewPaneNode.Pane.Title, "", newPaneView.Widget())290+ newStackedView.AddPane(ctx, string(output.NewPaneNode.Pane.ID), output.NewPaneNode.Pane.Title, "", newPaneView.Widget())
291 291
292 tr := wsView.TreeRenderer()292 tr := wsView.TreeRenderer()
293 293
@@ -461,7 +461,7 @@ func (c *WorkspaceCoordinator) doIncrementalSplit(
461 461
462 // 5. Wrap the new PaneView in a StackedView462 // 5. Wrap the new PaneView in a StackedView
463 newStackedView := layout.NewStackedView(factory)463 newStackedView := layout.NewStackedView(factory)
464- newStackedView.AddPane(ctx, output.NewPaneNode.Pane.Title, "", newPaneView.Widget())464+ newStackedView.AddPane(ctx, string(output.NewPaneNode.Pane.ID), output.NewPaneNode.Pane.Title, "", newPaneView.Widget())
465 465
466 orientation, existingFirst, ok := splitOrientation(direction)466 orientation, existingFirst, ok := splitOrientation(direction)
467 if !ok {467 if !ok {
M internal/ui/layout/stacked.go +19 -2
@@ -21,6 +21,7 @@ const stackedTitleMaxWidthChars = 30
2121
2222 // stackedPane represents a single pane within a stacked container.
2323 type stackedPane struct {
24+ paneID string // unique identifier for this pane
2425 titleBar BoxWidget // horizontal box with favicon + title label
2526 container Widget // the pane view widget
2627 title string
@@ -58,7 +59,7 @@ func NewStackedView(factory WidgetFactory) *StackedView {
5859
5960 // AddPane adds a new pane to the stack.
6061 // The new pane becomes active (visible).
61-func (sv *StackedView) AddPane(ctx context.Context, title, faviconIconName string, container Widget) int {
62+func (sv *StackedView) AddPane(ctx context.Context, paneID, title, faviconIconName string, container Widget) int {
6263 log := logging.FromContext(ctx)
6364 sv.mu.Lock()
6465 defer sv.mu.Unlock()
@@ -101,6 +102,7 @@ func (sv *StackedView) AddPane(ctx context.Context, title, faviconIconName strin
101102 titleButton.SetHexpand(true) // But fill horizontal space
102103
103104 pane := &stackedPane{
105+ paneID: paneID,
104106 titleBar: titleBar,
105107 container: container,
106108 title: title,
@@ -148,7 +150,7 @@ func (sv *StackedView) AddPane(ctx context.Context, title, faviconIconName strin
148150 // The new pane becomes active (visible).
149151 // Returns the index where the pane was inserted.
150152 func (sv *StackedView) InsertPaneAfter(
151- ctx context.Context, afterIndex int, title, faviconIconName string, container Widget,
153+ ctx context.Context, afterIndex int, paneID, title, faviconIconName string, container Widget,
152154 ) int {
153155 log := logging.FromContext(ctx)
154156 sv.mu.Lock()
@@ -202,6 +204,7 @@ func (sv *StackedView) InsertPaneAfter(
202204 titleButton.SetHexpand(true)
203205
204206 pane := &stackedPane{
207+ paneID: paneID,
205208 titleBar: titleBar,
206209 container: container,
207210 title: title,
@@ -420,6 +423,20 @@ func (sv *StackedView) Count() int {
420423 return len(sv.panes)
421424 }
422425
426+// FindPaneIndex returns the index of the pane with the given ID.
427+// Returns -1 if not found.
428+func (sv *StackedView) FindPaneIndex(paneID string) int {
429+ sv.mu.RLock()
430+ defer sv.mu.RUnlock()
431+
432+ for i, pane := range sv.panes {
433+ if pane.paneID == paneID {
434+ return i
435+ }
436+ }
437+ return -1
438+}
439+
423440 // SetOnActivate sets the callback for when a pane is activated via title bar click.
424441 func (sv *StackedView) SetOnActivate(fn func(index int)) {
425442 sv.mu.Lock()
@@ -21,6 +21,7 @@ const stackedTitleMaxWidthChars = 30
21 21
22 // stackedPane represents a single pane within a stacked container.22 // stackedPane represents a single pane within a stacked container.
23 type stackedPane struct {23 type stackedPane struct {
24+ paneID string // unique identifier for this pane
24 titleBar BoxWidget // horizontal box with favicon + title label25 titleBar BoxWidget // horizontal box with favicon + title label
25 container Widget // the pane view widget26 container Widget // the pane view widget
26 title string27 title string
@@ -58,7 +59,7 @@ func NewStackedView(factory WidgetFactory) *StackedView {
58 59
59 // AddPane adds a new pane to the stack.60 // AddPane adds a new pane to the stack.
60 // The new pane becomes active (visible).61 // The new pane becomes active (visible).
61-func (sv *StackedView) AddPane(ctx context.Context, title, faviconIconName string, container Widget) int {62+func (sv *StackedView) AddPane(ctx context.Context, paneID, title, faviconIconName string, container Widget) int {
62 log := logging.FromContext(ctx)63 log := logging.FromContext(ctx)
63 sv.mu.Lock()64 sv.mu.Lock()
64 defer sv.mu.Unlock()65 defer sv.mu.Unlock()
@@ -101,6 +102,7 @@ func (sv *StackedView) AddPane(ctx context.Context, title, faviconIconName strin
101 titleButton.SetHexpand(true) // But fill horizontal space102 titleButton.SetHexpand(true) // But fill horizontal space
102 103
103 pane := &stackedPane{104 pane := &stackedPane{
105+ paneID: paneID,
104 titleBar: titleBar,106 titleBar: titleBar,
105 container: container,107 container: container,
106 title: title,108 title: title,
@@ -148,7 +150,7 @@ func (sv *StackedView) AddPane(ctx context.Context, title, faviconIconName strin
148 // The new pane becomes active (visible).150 // The new pane becomes active (visible).
149 // Returns the index where the pane was inserted.151 // Returns the index where the pane was inserted.
150 func (sv *StackedView) InsertPaneAfter(152 func (sv *StackedView) InsertPaneAfter(
151- ctx context.Context, afterIndex int, title, faviconIconName string, container Widget,153+ ctx context.Context, afterIndex int, paneID, title, faviconIconName string, container Widget,
152 ) int {154 ) int {
153 log := logging.FromContext(ctx)155 log := logging.FromContext(ctx)
154 sv.mu.Lock()156 sv.mu.Lock()
@@ -202,6 +204,7 @@ func (sv *StackedView) InsertPaneAfter(
202 titleButton.SetHexpand(true)204 titleButton.SetHexpand(true)
203 205
204 pane := &stackedPane{206 pane := &stackedPane{
207+ paneID: paneID,
205 titleBar: titleBar,208 titleBar: titleBar,
206 container: container,209 container: container,
207 title: title,210 title: title,
@@ -420,6 +423,20 @@ func (sv *StackedView) Count() int {
420 return len(sv.panes)423 return len(sv.panes)
421 }424 }
422 425
426+// FindPaneIndex returns the index of the pane with the given ID.
427+// Returns -1 if not found.
428+func (sv *StackedView) FindPaneIndex(paneID string) int {
429+ sv.mu.RLock()
430+ defer sv.mu.RUnlock()
431+
432+ for i, pane := range sv.panes {
433+ if pane.paneID == paneID {
434+ return i
435+ }
436+ }
437+ return -1
438+}
439+
423 // SetOnActivate sets the callback for when a pane is activated via title bar click.440 // SetOnActivate sets the callback for when a pane is activated via title bar click.
424 func (sv *StackedView) SetOnActivate(fn func(index int)) {441 func (sv *StackedView) SetOnActivate(fn func(index int)) {
425 sv.mu.Lock()442 sv.mu.Lock()
M internal/ui/layout/stacked_test.go +31 -31
@@ -99,7 +99,7 @@ func TestAddPane_SinglePane_BecomesActive(t *testing.T) {
9999 sv := layout.NewStackedView(mockFactory)
100100
101101 // Act
102- index := sv.AddPane(ctx, "Test Page", "web-browser-symbolic", mockContainer)
102+ index := sv.AddPane(ctx, "pane-1", "Test Page", "web-browser-symbolic", mockContainer)
103103
104104 // Assert
105105 assert.Equal(t, 0, index)
@@ -139,8 +139,8 @@ func TestAddPane_MultiplePanes_LastBecomesActive(t *testing.T) {
139139 sv := layout.NewStackedView(mockFactory)
140140
141141 // Act
142- sv.AddPane(ctx, "Page 1", "", mockContainer1)
143- index := sv.AddPane(ctx, "Page 2", "", mockContainer2)
142+ sv.AddPane(ctx, "pane-1", "Page 1", "", mockContainer1)
143+ index := sv.AddPane(ctx, "pane-2", "Page 2", "", mockContainer2)
144144
145145 // Assert
146146 assert.Equal(t, 1, index)
@@ -175,9 +175,9 @@ func TestRemovePane_MiddlePane(t *testing.T) {
175175 }
176176
177177 sv := layout.NewStackedView(mockFactory)
178- sv.AddPane(ctx, "Page 1", "", containers[0])
179- sv.AddPane(ctx, "Page 2", "", containers[1])
180- sv.AddPane(ctx, "Page 3", "", containers[2])
178+ sv.AddPane(ctx, "pane-1", "Page 1", "", containers[0])
179+ sv.AddPane(ctx, "pane-2", "Page 2", "", containers[1])
180+ sv.AddPane(ctx, "pane-3", "Page 3", "", containers[2])
181181
182182 // Remove middle pane (index 1)
183183 // The parent of titleBar is the button widget
@@ -206,7 +206,7 @@ func TestRemovePane_LastPane_ReturnsError(t *testing.T) {
206206 mockTitleBar.EXPECT().AddCssClass("active").Once()
207207
208208 sv := layout.NewStackedView(mockFactory)
209- sv.AddPane(ctx, "Only Page", "", mockContainer)
209+ sv.AddPane(ctx, "pane-1", "Only Page", "", mockContainer)
210210
211211 // Act
212212 err := sv.RemovePane(ctx, 0)
@@ -243,7 +243,7 @@ func TestRemovePane_IndexOutOfBounds(t *testing.T) {
243243 mockTitleBar.EXPECT().AddCssClass("active").Once()
244244
245245 sv := layout.NewStackedView(mockFactory)
246- sv.AddPane(ctx, "Page", "", mockContainer)
246+ sv.AddPane(ctx, "pane-1", "Page", "", mockContainer)
247247
248248 // Act
249249 err := sv.RemovePane(ctx, 5)
@@ -276,8 +276,8 @@ func TestSetActive_ValidIndex(t *testing.T) {
276276 }
277277
278278 sv := layout.NewStackedView(mockFactory)
279- sv.AddPane(ctx, "Page 1", "", containers[0])
280- sv.AddPane(ctx, "Page 2", "", containers[1])
279+ sv.AddPane(ctx, "pane-1", "Page 1", "", containers[0])
280+ sv.AddPane(ctx, "pane-2", "Page 2", "", containers[1])
281281
282282 // Act - switch back to first pane
283283 err := sv.SetActive(ctx, 0)
@@ -301,7 +301,7 @@ func TestSetActive_OutOfBounds(t *testing.T) {
301301 mockTitleBar.EXPECT().AddCssClass("active").Once()
302302
303303 sv := layout.NewStackedView(mockFactory)
304- sv.AddPane(ctx, "Page", "", mockContainer)
304+ sv.AddPane(ctx, "pane-1", "Page", "", mockContainer)
305305
306306 // Act
307307 err := sv.SetActive(ctx, 5)
@@ -334,7 +334,7 @@ func TestUpdateTitle(t *testing.T) {
334334 mockTitleBar.EXPECT().AddCssClass("active").Once()
335335
336336 sv := layout.NewStackedView(mockFactory)
337- sv.AddPane(ctx, "Old Title", "", mockContainer)
337+ sv.AddPane(ctx, "pane-1", "Old Title", "", mockContainer)
338338
339339 mockLabel.EXPECT().SetText("New Title").Once()
340340
@@ -368,7 +368,7 @@ func TestUpdateFavicon(t *testing.T) {
368368 mockTitleBar.EXPECT().AddCssClass("active").Once()
369369
370370 sv := layout.NewStackedView(mockFactory)
371- sv.AddPane(ctx, "Page", "", mockContainer)
371+ sv.AddPane(ctx, "pane-1", "Page", "", mockContainer)
372372
373373 mockFavicon.EXPECT().SetFromIconName("new-icon").Once()
374374
@@ -393,7 +393,7 @@ func TestGetContainer(t *testing.T) {
393393 mockTitleBar.EXPECT().AddCssClass("active").Once()
394394
395395 sv := layout.NewStackedView(mockFactory)
396- sv.AddPane(ctx, "Page", "", mockContainer)
396+ sv.AddPane(ctx, "pane-1", "Page", "", mockContainer)
397397
398398 // Act
399399 container, err := sv.GetContainer(0)
@@ -452,8 +452,8 @@ func TestNavigateNext_WrapsAround(t *testing.T) {
452452 }
453453
454454 sv := layout.NewStackedView(mockFactory)
455- sv.AddPane(ctx, "Page 1", "", containers[0])
456- sv.AddPane(ctx, "Page 2", "", containers[1])
455+ sv.AddPane(ctx, "pane-1", "Page 1", "", containers[0])
456+ sv.AddPane(ctx, "pane-2", "Page 2", "", containers[1])
457457
458458 // Currently at index 1 (last added)
459459 assert.Equal(t, 1, sv.ActiveIndex())
@@ -490,8 +490,8 @@ func TestNavigatePrevious_WrapsAround(t *testing.T) {
490490 }
491491
492492 sv := layout.NewStackedView(mockFactory)
493- sv.AddPane(ctx, "Page 1", "", containers[0])
494- sv.AddPane(ctx, "Page 2", "", containers[1])
493+ sv.AddPane(ctx, "pane-1", "Page 1", "", containers[0])
494+ sv.AddPane(ctx, "pane-2", "Page 2", "", containers[1])
495495
496496 // Set to first pane
497497 sv.SetActive(ctx, 0)
@@ -617,7 +617,7 @@ func TestInsertPaneAfter_AtBeginning(t *testing.T) {
617617 mockTitleBar1.EXPECT().AddCssClass("active").Once()
618618
619619 sv := layout.NewStackedView(mockFactory)
620- sv.AddPane(ctx, "Page 1", "", mockContainer1)
620+ sv.AddPane(ctx, "pane-1", "Page 1", "", mockContainer1)
621621
622622 // Second pane - inserted at beginning (afterIndex=-1)
623623 mockTitleBar2, mockContainer2 := setupInsertPaneMocks(t, mockFactory, mockBox, nil)
@@ -632,7 +632,7 @@ func TestInsertPaneAfter_AtBeginning(t *testing.T) {
632632 mockTitleBar2.EXPECT().AddCssClass("active").Once()
633633
634634 // Act
635- index := sv.InsertPaneAfter(ctx, -1, "Page 0", "", mockContainer2)
635+ index := sv.InsertPaneAfter(ctx, -1, "pane-0", "Page 0", "", mockContainer2)
636636
637637 // Assert
638638 assert.Equal(t, 0, index)
@@ -658,8 +658,8 @@ func TestInsertPaneAfter_InMiddle(t *testing.T) {
658658 }
659659
660660 sv := layout.NewStackedView(mockFactory)
661- sv.AddPane(ctx, "Page 1", "", containers[0])
662- sv.AddPane(ctx, "Page 2", "", containers[1])
661+ sv.AddPane(ctx, "pane-1", "Page 1", "", containers[0])
662+ sv.AddPane(ctx, "pane-2", "Page 2", "", containers[1])
663663
664664 // Set active to first pane
665665 require.NoError(t, sv.SetActive(ctx, 0))
@@ -672,7 +672,7 @@ func TestInsertPaneAfter_InMiddle(t *testing.T) {
672672 mockTitleBar3.EXPECT().RemoveCssClass("active").Maybe()
673673
674674 // Act
675- index := sv.InsertPaneAfter(ctx, 0, "Page 1.5", "", mockContainer3)
675+ index := sv.InsertPaneAfter(ctx, 0, "pane-1.5", "Page 1.5", "", mockContainer3)
676676
677677 // Assert
678678 assert.Equal(t, 1, index)
@@ -695,7 +695,7 @@ func TestInsertPaneAfter_AtEnd(t *testing.T) {
695695 mockTitleBar1.EXPECT().AddCssClass("active").Once()
696696
697697 sv := layout.NewStackedView(mockFactory)
698- sv.AddPane(ctx, "Page 1", "", mockContainer1)
698+ sv.AddPane(ctx, "pane-1", "Page 1", "", mockContainer1)
699699
700700 // Insert after last pane (afterIndex=0, becomes index 1)
701701 mockTitleBar2, mockContainer2 := setupInsertPaneMocks(t, mockFactory, mockBox, mockContainer1)
@@ -709,7 +709,7 @@ func TestInsertPaneAfter_AtEnd(t *testing.T) {
709709 mockTitleBar2.EXPECT().AddCssClass("active").Once()
710710
711711 // Act
712- index := sv.InsertPaneAfter(ctx, 0, "Page 2", "", mockContainer2)
712+ index := sv.InsertPaneAfter(ctx, 0, "pane-2", "Page 2", "", mockContainer2)
713713
714714 // Assert
715715 assert.Equal(t, 1, index)
@@ -742,9 +742,9 @@ func TestInsertPaneAfter_MaintainsOrder(t *testing.T) {
742742 }
743743
744744 sv := layout.NewStackedView(mockFactory)
745- sv.AddPane(ctx, "A", "", containers[0]) // index 0
746- sv.AddPane(ctx, "B", "", containers[1]) // index 1
747- sv.AddPane(ctx, "C", "", containers[2]) // index 2
745+ sv.AddPane(ctx, "pane-a", "A", "", containers[0]) // index 0
746+ sv.AddPane(ctx, "pane-b", "B", "", containers[1]) // index 1
747+ sv.AddPane(ctx, "pane-c", "C", "", containers[2]) // index 2
748748
749749 // Navigate to pane B (index 1)
750750 require.NoError(t, sv.SetActive(ctx, 1))
@@ -757,7 +757,7 @@ func TestInsertPaneAfter_MaintainsOrder(t *testing.T) {
757757 mockTitleBar4.EXPECT().RemoveCssClass("active").Maybe()
758758
759759 // Act
760- index := sv.InsertPaneAfter(ctx, 1, "D", "", mockContainer4)
760+ index := sv.InsertPaneAfter(ctx, 1, "pane-d", "D", "", mockContainer4)
761761
762762 // Assert: Order should be A(0), B(1), D(2), C(3)
763763 assert.Equal(t, 2, index)
@@ -791,7 +791,7 @@ func TestInsertPaneAfter_InvalidIndexClamped(t *testing.T) {
791791 mockTitleBar1.EXPECT().AddCssClass("active").Once()
792792
793793 sv := layout.NewStackedView(mockFactory)
794- sv.AddPane(ctx, "Page 1", "", mockContainer1)
794+ sv.AddPane(ctx, "pane-1", "Page 1", "", mockContainer1)
795795
796796 // Try to insert at invalid index (100) - should clamp to end
797797 mockTitleBar2, mockContainer2 := setupInsertPaneMocks(t, mockFactory, mockBox, mockContainer1)
@@ -805,7 +805,7 @@ func TestInsertPaneAfter_InvalidIndexClamped(t *testing.T) {
805805 mockTitleBar2.EXPECT().AddCssClass("active").Once()
806806
807807 // Act - afterIndex=100 should be clamped to 0 (last valid index)
808- index := sv.InsertPaneAfter(ctx, 100, "Page 2", "", mockContainer2)
808+ index := sv.InsertPaneAfter(ctx, 100, "pane-2", "Page 2", "", mockContainer2)
809809
810810 // Assert
811811 assert.Equal(t, 1, index)
@@ -99,7 +99,7 @@ func TestAddPane_SinglePane_BecomesActive(t *testing.T) {
99 sv := layout.NewStackedView(mockFactory)99 sv := layout.NewStackedView(mockFactory)
100 100
101 // Act101 // Act
102- index := sv.AddPane(ctx, "Test Page", "web-browser-symbolic", mockContainer)102+ index := sv.AddPane(ctx, "pane-1", "Test Page", "web-browser-symbolic", mockContainer)
103 103
104 // Assert104 // Assert
105 assert.Equal(t, 0, index)105 assert.Equal(t, 0, index)
@@ -139,8 +139,8 @@ func TestAddPane_MultiplePanes_LastBecomesActive(t *testing.T) {
139 sv := layout.NewStackedView(mockFactory)139 sv := layout.NewStackedView(mockFactory)
140 140
141 // Act141 // Act
142- sv.AddPane(ctx, "Page 1", "", mockContainer1)142+ sv.AddPane(ctx, "pane-1", "Page 1", "", mockContainer1)
143- index := sv.AddPane(ctx, "Page 2", "", mockContainer2)143+ index := sv.AddPane(ctx, "pane-2", "Page 2", "", mockContainer2)
144 144
145 // Assert145 // Assert
146 assert.Equal(t, 1, index)146 assert.Equal(t, 1, index)
@@ -175,9 +175,9 @@ func TestRemovePane_MiddlePane(t *testing.T) {
175 }175 }
176 176
177 sv := layout.NewStackedView(mockFactory)177 sv := layout.NewStackedView(mockFactory)
178- sv.AddPane(ctx, "Page 1", "", containers[0])178+ sv.AddPane(ctx, "pane-1", "Page 1", "", containers[0])
179- sv.AddPane(ctx, "Page 2", "", containers[1])179+ sv.AddPane(ctx, "pane-2", "Page 2", "", containers[1])
180- sv.AddPane(ctx, "Page 3", "", containers[2])180+ sv.AddPane(ctx, "pane-3", "Page 3", "", containers[2])
181 181
182 // Remove middle pane (index 1)182 // Remove middle pane (index 1)
183 // The parent of titleBar is the button widget183 // The parent of titleBar is the button widget
@@ -206,7 +206,7 @@ func TestRemovePane_LastPane_ReturnsError(t *testing.T) {
206 mockTitleBar.EXPECT().AddCssClass("active").Once()206 mockTitleBar.EXPECT().AddCssClass("active").Once()
207 207
208 sv := layout.NewStackedView(mockFactory)208 sv := layout.NewStackedView(mockFactory)
209- sv.AddPane(ctx, "Only Page", "", mockContainer)209+ sv.AddPane(ctx, "pane-1", "Only Page", "", mockContainer)
210 210
211 // Act211 // Act
212 err := sv.RemovePane(ctx, 0)212 err := sv.RemovePane(ctx, 0)
@@ -243,7 +243,7 @@ func TestRemovePane_IndexOutOfBounds(t *testing.T) {
243 mockTitleBar.EXPECT().AddCssClass("active").Once()243 mockTitleBar.EXPECT().AddCssClass("active").Once()
244 244
245 sv := layout.NewStackedView(mockFactory)245 sv := layout.NewStackedView(mockFactory)
246- sv.AddPane(ctx, "Page", "", mockContainer)246+ sv.AddPane(ctx, "pane-1", "Page", "", mockContainer)
247 247
248 // Act248 // Act
249 err := sv.RemovePane(ctx, 5)249 err := sv.RemovePane(ctx, 5)
@@ -276,8 +276,8 @@ func TestSetActive_ValidIndex(t *testing.T) {
276 }276 }
277 277
278 sv := layout.NewStackedView(mockFactory)278 sv := layout.NewStackedView(mockFactory)
279- sv.AddPane(ctx, "Page 1", "", containers[0])279+ sv.AddPane(ctx, "pane-1", "Page 1", "", containers[0])
280- sv.AddPane(ctx, "Page 2", "", containers[1])280+ sv.AddPane(ctx, "pane-2", "Page 2", "", containers[1])
281 281
282 // Act - switch back to first pane282 // Act - switch back to first pane
283 err := sv.SetActive(ctx, 0)283 err := sv.SetActive(ctx, 0)
@@ -301,7 +301,7 @@ func TestSetActive_OutOfBounds(t *testing.T) {
301 mockTitleBar.EXPECT().AddCssClass("active").Once()301 mockTitleBar.EXPECT().AddCssClass("active").Once()
302 302
303 sv := layout.NewStackedView(mockFactory)303 sv := layout.NewStackedView(mockFactory)
304- sv.AddPane(ctx, "Page", "", mockContainer)304+ sv.AddPane(ctx, "pane-1", "Page", "", mockContainer)
305 305
306 // Act306 // Act
307 err := sv.SetActive(ctx, 5)307 err := sv.SetActive(ctx, 5)
@@ -334,7 +334,7 @@ func TestUpdateTitle(t *testing.T) {
334 mockTitleBar.EXPECT().AddCssClass("active").Once()334 mockTitleBar.EXPECT().AddCssClass("active").Once()
335 335
336 sv := layout.NewStackedView(mockFactory)336 sv := layout.NewStackedView(mockFactory)
337- sv.AddPane(ctx, "Old Title", "", mockContainer)337+ sv.AddPane(ctx, "pane-1", "Old Title", "", mockContainer)
338 338
339 mockLabel.EXPECT().SetText("New Title").Once()339 mockLabel.EXPECT().SetText("New Title").Once()
340 340
@@ -368,7 +368,7 @@ func TestUpdateFavicon(t *testing.T) {
368 mockTitleBar.EXPECT().AddCssClass("active").Once()368 mockTitleBar.EXPECT().AddCssClass("active").Once()
369 369
370 sv := layout.NewStackedView(mockFactory)370 sv := layout.NewStackedView(mockFactory)
371- sv.AddPane(ctx, "Page", "", mockContainer)371+ sv.AddPane(ctx, "pane-1", "Page", "", mockContainer)
372 372
373 mockFavicon.EXPECT().SetFromIconName("new-icon").Once()373 mockFavicon.EXPECT().SetFromIconName("new-icon").Once()
374 374
@@ -393,7 +393,7 @@ func TestGetContainer(t *testing.T) {
393 mockTitleBar.EXPECT().AddCssClass("active").Once()393 mockTitleBar.EXPECT().AddCssClass("active").Once()
394 394
395 sv := layout.NewStackedView(mockFactory)395 sv := layout.NewStackedView(mockFactory)
396- sv.AddPane(ctx, "Page", "", mockContainer)396+ sv.AddPane(ctx, "pane-1", "Page", "", mockContainer)
397 397
398 // Act398 // Act
399 container, err := sv.GetContainer(0)399 container, err := sv.GetContainer(0)
@@ -452,8 +452,8 @@ func TestNavigateNext_WrapsAround(t *testing.T) {
452 }452 }
453 453
454 sv := layout.NewStackedView(mockFactory)454 sv := layout.NewStackedView(mockFactory)
455- sv.AddPane(ctx, "Page 1", "", containers[0])455+ sv.AddPane(ctx, "pane-1", "Page 1", "", containers[0])
456- sv.AddPane(ctx, "Page 2", "", containers[1])456+ sv.AddPane(ctx, "pane-2", "Page 2", "", containers[1])
457 457
458 // Currently at index 1 (last added)458 // Currently at index 1 (last added)
459 assert.Equal(t, 1, sv.ActiveIndex())459 assert.Equal(t, 1, sv.ActiveIndex())
@@ -490,8 +490,8 @@ func TestNavigatePrevious_WrapsAround(t *testing.T) {
490 }490 }
491 491
492 sv := layout.NewStackedView(mockFactory)492 sv := layout.NewStackedView(mockFactory)
493- sv.AddPane(ctx, "Page 1", "", containers[0])493+ sv.AddPane(ctx, "pane-1", "Page 1", "", containers[0])
494- sv.AddPane(ctx, "Page 2", "", containers[1])494+ sv.AddPane(ctx, "pane-2", "Page 2", "", containers[1])
495 495
496 // Set to first pane496 // Set to first pane
497 sv.SetActive(ctx, 0)497 sv.SetActive(ctx, 0)
@@ -617,7 +617,7 @@ func TestInsertPaneAfter_AtBeginning(t *testing.T) {
617 mockTitleBar1.EXPECT().AddCssClass("active").Once()617 mockTitleBar1.EXPECT().AddCssClass("active").Once()
618 618
619 sv := layout.NewStackedView(mockFactory)619 sv := layout.NewStackedView(mockFactory)
620- sv.AddPane(ctx, "Page 1", "", mockContainer1)620+ sv.AddPane(ctx, "pane-1", "Page 1", "", mockContainer1)
621 621
622 // Second pane - inserted at beginning (afterIndex=-1)622 // Second pane - inserted at beginning (afterIndex=-1)
623 mockTitleBar2, mockContainer2 := setupInsertPaneMocks(t, mockFactory, mockBox, nil)623 mockTitleBar2, mockContainer2 := setupInsertPaneMocks(t, mockFactory, mockBox, nil)
@@ -632,7 +632,7 @@ func TestInsertPaneAfter_AtBeginning(t *testing.T) {
632 mockTitleBar2.EXPECT().AddCssClass("active").Once()632 mockTitleBar2.EXPECT().AddCssClass("active").Once()
633 633
634 // Act634 // Act
635- index := sv.InsertPaneAfter(ctx, -1, "Page 0", "", mockContainer2)635+ index := sv.InsertPaneAfter(ctx, -1, "pane-0", "Page 0", "", mockContainer2)
636 636
637 // Assert637 // Assert
638 assert.Equal(t, 0, index)638 assert.Equal(t, 0, index)
@@ -658,8 +658,8 @@ func TestInsertPaneAfter_InMiddle(t *testing.T) {
658 }658 }
659 659
660 sv := layout.NewStackedView(mockFactory)660 sv := layout.NewStackedView(mockFactory)
661- sv.AddPane(ctx, "Page 1", "", containers[0])661+ sv.AddPane(ctx, "pane-1", "Page 1", "", containers[0])
662- sv.AddPane(ctx, "Page 2", "", containers[1])662+ sv.AddPane(ctx, "pane-2", "Page 2", "", containers[1])
663 663
664 // Set active to first pane664 // Set active to first pane
665 require.NoError(t, sv.SetActive(ctx, 0))665 require.NoError(t, sv.SetActive(ctx, 0))
@@ -672,7 +672,7 @@ func TestInsertPaneAfter_InMiddle(t *testing.T) {
672 mockTitleBar3.EXPECT().RemoveCssClass("active").Maybe()672 mockTitleBar3.EXPECT().RemoveCssClass("active").Maybe()
673 673
674 // Act674 // Act
675- index := sv.InsertPaneAfter(ctx, 0, "Page 1.5", "", mockContainer3)675+ index := sv.InsertPaneAfter(ctx, 0, "pane-1.5", "Page 1.5", "", mockContainer3)
676 676
677 // Assert677 // Assert
678 assert.Equal(t, 1, index)678 assert.Equal(t, 1, index)
@@ -695,7 +695,7 @@ func TestInsertPaneAfter_AtEnd(t *testing.T) {
695 mockTitleBar1.EXPECT().AddCssClass("active").Once()695 mockTitleBar1.EXPECT().AddCssClass("active").Once()
696 696
697 sv := layout.NewStackedView(mockFactory)697 sv := layout.NewStackedView(mockFactory)
698- sv.AddPane(ctx, "Page 1", "", mockContainer1)698+ sv.AddPane(ctx, "pane-1", "Page 1", "", mockContainer1)
699 699
700 // Insert after last pane (afterIndex=0, becomes index 1)700 // Insert after last pane (afterIndex=0, becomes index 1)
701 mockTitleBar2, mockContainer2 := setupInsertPaneMocks(t, mockFactory, mockBox, mockContainer1)701 mockTitleBar2, mockContainer2 := setupInsertPaneMocks(t, mockFactory, mockBox, mockContainer1)
@@ -709,7 +709,7 @@ func TestInsertPaneAfter_AtEnd(t *testing.T) {
709 mockTitleBar2.EXPECT().AddCssClass("active").Once()709 mockTitleBar2.EXPECT().AddCssClass("active").Once()
710 710
711 // Act711 // Act
712- index := sv.InsertPaneAfter(ctx, 0, "Page 2", "", mockContainer2)712+ index := sv.InsertPaneAfter(ctx, 0, "pane-2", "Page 2", "", mockContainer2)
713 713
714 // Assert714 // Assert
715 assert.Equal(t, 1, index)715 assert.Equal(t, 1, index)
@@ -742,9 +742,9 @@ func TestInsertPaneAfter_MaintainsOrder(t *testing.T) {
742 }742 }
743 743
744 sv := layout.NewStackedView(mockFactory)744 sv := layout.NewStackedView(mockFactory)
745- sv.AddPane(ctx, "A", "", containers[0]) // index 0745+ sv.AddPane(ctx, "pane-a", "A", "", containers[0]) // index 0
746- sv.AddPane(ctx, "B", "", containers[1]) // index 1746+ sv.AddPane(ctx, "pane-b", "B", "", containers[1]) // index 1
747- sv.AddPane(ctx, "C", "", containers[2]) // index 2747+ sv.AddPane(ctx, "pane-c", "C", "", containers[2]) // index 2
748 748
749 // Navigate to pane B (index 1)749 // Navigate to pane B (index 1)
750 require.NoError(t, sv.SetActive(ctx, 1))750 require.NoError(t, sv.SetActive(ctx, 1))
@@ -757,7 +757,7 @@ func TestInsertPaneAfter_MaintainsOrder(t *testing.T) {
757 mockTitleBar4.EXPECT().RemoveCssClass("active").Maybe()757 mockTitleBar4.EXPECT().RemoveCssClass("active").Maybe()
758 758
759 // Act759 // Act
760- index := sv.InsertPaneAfter(ctx, 1, "D", "", mockContainer4)760+ index := sv.InsertPaneAfter(ctx, 1, "pane-d", "D", "", mockContainer4)
761 761
762 // Assert: Order should be A(0), B(1), D(2), C(3)762 // Assert: Order should be A(0), B(1), D(2), C(3)
763 assert.Equal(t, 2, index)763 assert.Equal(t, 2, index)
@@ -791,7 +791,7 @@ func TestInsertPaneAfter_InvalidIndexClamped(t *testing.T) {
791 mockTitleBar1.EXPECT().AddCssClass("active").Once()791 mockTitleBar1.EXPECT().AddCssClass("active").Once()
792 792
793 sv := layout.NewStackedView(mockFactory)793 sv := layout.NewStackedView(mockFactory)
794- sv.AddPane(ctx, "Page 1", "", mockContainer1)794+ sv.AddPane(ctx, "pane-1", "Page 1", "", mockContainer1)
795 795
796 // Try to insert at invalid index (100) - should clamp to end796 // Try to insert at invalid index (100) - should clamp to end
797 mockTitleBar2, mockContainer2 := setupInsertPaneMocks(t, mockFactory, mockBox, mockContainer1)797 mockTitleBar2, mockContainer2 := setupInsertPaneMocks(t, mockFactory, mockBox, mockContainer1)
@@ -805,7 +805,7 @@ func TestInsertPaneAfter_InvalidIndexClamped(t *testing.T) {
805 mockTitleBar2.EXPECT().AddCssClass("active").Once()805 mockTitleBar2.EXPECT().AddCssClass("active").Once()
806 806
807 // Act - afterIndex=100 should be clamped to 0 (last valid index)807 // Act - afterIndex=100 should be clamped to 0 (last valid index)
808- index := sv.InsertPaneAfter(ctx, 100, "Page 2", "", mockContainer2)808+ index := sv.InsertPaneAfter(ctx, 100, "pane-2", "Page 2", "", mockContainer2)
809 809
810 // Assert810 // Assert
811 assert.Equal(t, 1, index)811 assert.Equal(t, 1, index)
M internal/ui/layout/tree.go +6 -4
@@ -135,7 +135,7 @@ func (tr *TreeRenderer) renderLeaf(ctx context.Context, node *entity.PaneNode) W
135135 Str("title", title).
136136 Msg("TreeRenderer.renderLeaf: wrapping pane in StackedView")
137137
138- stackedView.AddPane(ctx, title, "", paneWidget)
138+ stackedView.AddPane(ctx, paneID, title, "", paneWidget)
139139
140140 // Track this pane's StackedView for later stacking operations
141141 if node.Pane != nil {
@@ -185,22 +185,24 @@ func (tr *TreeRenderer) renderStacked(ctx context.Context, node *entity.PaneNode
185185 return nil, err
186186 }
187187
188- // Get title and favicon from pane if available
188+ // Get pane ID, title, and favicon from pane if available
189+ paneID := ""
189190 title := "Untitled"
190191 favicon := ""
191192 if child.Pane != nil {
193+ paneID = string(child.Pane.ID)
192194 if child.Pane.Title != "" {
193195 title = child.Pane.Title
194196 }
195197 favicon = child.Pane.FaviconURL
196198 }
197199
198- stackedView.AddPane(ctx, title, favicon, childWidget)
200+ stackedView.AddPane(ctx, paneID, title, favicon, childWidget)
199201
200202 // Override paneToStack to point to THIS stacked view, not the child's wrapper.
201203 // This ensures geometric navigation and stack sync work correctly.
202204 if child.Pane != nil {
203- tr.paneToStack[string(child.Pane.ID)] = stackedView
205+ tr.paneToStack[paneID] = stackedView
204206 }
205207 }
206208
@@ -135,7 +135,7 @@ func (tr *TreeRenderer) renderLeaf(ctx context.Context, node *entity.PaneNode) W
135 Str("title", title).135 Str("title", title).
136 Msg("TreeRenderer.renderLeaf: wrapping pane in StackedView")136 Msg("TreeRenderer.renderLeaf: wrapping pane in StackedView")
137 137
138- stackedView.AddPane(ctx, title, "", paneWidget)138+ stackedView.AddPane(ctx, paneID, title, "", paneWidget)
139 139
140 // Track this pane's StackedView for later stacking operations140 // Track this pane's StackedView for later stacking operations
141 if node.Pane != nil {141 if node.Pane != nil {
@@ -185,22 +185,24 @@ func (tr *TreeRenderer) renderStacked(ctx context.Context, node *entity.PaneNode
185 return nil, err185 return nil, err
186 }186 }
187 187
188- // Get title and favicon from pane if available188+ // Get pane ID, title, and favicon from pane if available
189+ paneID := ""
189 title := "Untitled"190 title := "Untitled"
190 favicon := ""191 favicon := ""
191 if child.Pane != nil {192 if child.Pane != nil {
193+ paneID = string(child.Pane.ID)
192 if child.Pane.Title != "" {194 if child.Pane.Title != "" {
193 title = child.Pane.Title195 title = child.Pane.Title
194 }196 }
195 favicon = child.Pane.FaviconURL197 favicon = child.Pane.FaviconURL
196 }198 }
197 199
198- stackedView.AddPane(ctx, title, favicon, childWidget)200+ stackedView.AddPane(ctx, paneID, title, favicon, childWidget)
199 201
200 // Override paneToStack to point to THIS stacked view, not the child's wrapper.202 // Override paneToStack to point to THIS stacked view, not the child's wrapper.
201 // This ensures geometric navigation and stack sync work correctly.203 // This ensures geometric navigation and stack sync work correctly.
202 if child.Pane != nil {204 if child.Pane != nil {
203- tr.paneToStack[string(child.Pane.ID)] = stackedView205+ tr.paneToStack[paneID] = stackedView
204 }206 }
205 }207 }
206 208

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