detached c4aef32

fix(ui): wire split ratio persistence for incremental splits and suppress early notify

commit
c4aef32781653904d26ef5d7e4cdaff2614bfb8d
author
brice · · 2025-12-26 08:01 UTC
parent
67bb8aa
signature
unsigned
2 files · +18 -1
M internal/ui/coordinator/workspace.go +16 -0
@@ -323,6 +323,7 @@ func (c *WorkspaceCoordinator) doIncrementalStackSplit(
323323 splitView = layout.NewSplitView(ctx, factory, layout.OrientationHorizontal,
324324 existingStackWidget, newStackedView.Widget(), output.SplitRatio)
325325 }
326+ c.wireSplitRatioPersistence(ctx, splitView, output.ParentNode.ID)
326327
327328 wsView.SetRootWidgetDirect(splitView.Widget())
328329
@@ -370,6 +371,7 @@ func (c *WorkspaceCoordinator) doIncrementalStackSplit(
370371 splitView = layout.NewSplitView(ctx, factory, layout.OrientationHorizontal,
371372 existingStackWidget, newStackedView.Widget(), output.SplitRatio)
372373 }
374+ c.wireSplitRatioPersistence(ctx, splitView, output.ParentNode.ID)
373375
374376 // Put the new split view in the grandparent's slot
375377 if isStartChild {
@@ -562,6 +564,18 @@ func splitOrientation(direction usecase.SplitDirection) (layout.Orientation, boo
562564 }
563565 }
564566
567+func (c *WorkspaceCoordinator) wireSplitRatioPersistence(ctx context.Context, splitView *layout.SplitView, splitNodeID string) {
568+ if c == nil || splitView == nil || splitNodeID == "" {
569+ return
570+ }
571+
572+ splitView.SetOnRatioChanged(func(ratio float64) {
573+ if err := c.SetSplitRatio(ctx, splitNodeID, ratio); err != nil {
574+ logging.FromContext(ctx).Warn().Err(err).Str("split_node_id", splitNodeID).Msg("failed to persist split ratio")
575+ }
576+ })
577+}
578+
565579 func (c *WorkspaceCoordinator) replaceRootSplit(
566580 ctx context.Context,
567581 wsView *component.WorkspaceView,
@@ -584,6 +598,7 @@ func (c *WorkspaceCoordinator) replaceRootSplit(
584598 splitView = layout.NewSplitView(ctx, factory, orientation,
585599 newStackedView.Widget(), existingRootWidget, output.SplitRatio)
586600 }
601+ c.wireSplitRatioPersistence(ctx, splitView, output.ParentNode.ID)
587602
588603 wsView.SetRootWidgetDirect(splitView.Widget())
589604 tr.RegisterSplit(output.ParentNode.ID, splitView.Widget(), orientation)
@@ -643,6 +658,7 @@ func (c *WorkspaceCoordinator) replaceNonRootSplit(
643658 splitView = layout.NewSplitView(ctx, factory, orientation,
644659 newStackedView.Widget(), activePaneWidget, output.SplitRatio)
645660 }
661+ c.wireSplitRatioPersistence(ctx, splitView, output.ParentNode.ID)
646662
647663 if isStartChild {
648664 panedWidget.SetStartChild(splitView.Widget())
@@ -323,6 +323,7 @@ func (c *WorkspaceCoordinator) doIncrementalStackSplit(
323 splitView = layout.NewSplitView(ctx, factory, layout.OrientationHorizontal,323 splitView = layout.NewSplitView(ctx, factory, layout.OrientationHorizontal,
324 existingStackWidget, newStackedView.Widget(), output.SplitRatio)324 existingStackWidget, newStackedView.Widget(), output.SplitRatio)
325 }325 }
326+ c.wireSplitRatioPersistence(ctx, splitView, output.ParentNode.ID)
326 327
327 wsView.SetRootWidgetDirect(splitView.Widget())328 wsView.SetRootWidgetDirect(splitView.Widget())
328 329
@@ -370,6 +371,7 @@ func (c *WorkspaceCoordinator) doIncrementalStackSplit(
370 splitView = layout.NewSplitView(ctx, factory, layout.OrientationHorizontal,371 splitView = layout.NewSplitView(ctx, factory, layout.OrientationHorizontal,
371 existingStackWidget, newStackedView.Widget(), output.SplitRatio)372 existingStackWidget, newStackedView.Widget(), output.SplitRatio)
372 }373 }
374+ c.wireSplitRatioPersistence(ctx, splitView, output.ParentNode.ID)
373 375
374 // Put the new split view in the grandparent's slot376 // Put the new split view in the grandparent's slot
375 if isStartChild {377 if isStartChild {
@@ -562,6 +564,18 @@ func splitOrientation(direction usecase.SplitDirection) (layout.Orientation, boo
562 }564 }
563 }565 }
564 566
567+func (c *WorkspaceCoordinator) wireSplitRatioPersistence(ctx context.Context, splitView *layout.SplitView, splitNodeID string) {
568+ if c == nil || splitView == nil || splitNodeID == "" {
569+ return
570+ }
571+
572+ splitView.SetOnRatioChanged(func(ratio float64) {
573+ if err := c.SetSplitRatio(ctx, splitNodeID, ratio); err != nil {
574+ logging.FromContext(ctx).Warn().Err(err).Str("split_node_id", splitNodeID).Msg("failed to persist split ratio")
575+ }
576+ })
577+}
578+
565 func (c *WorkspaceCoordinator) replaceRootSplit(579 func (c *WorkspaceCoordinator) replaceRootSplit(
566 ctx context.Context,580 ctx context.Context,
567 wsView *component.WorkspaceView,581 wsView *component.WorkspaceView,
@@ -584,6 +598,7 @@ func (c *WorkspaceCoordinator) replaceRootSplit(
584 splitView = layout.NewSplitView(ctx, factory, orientation,598 splitView = layout.NewSplitView(ctx, factory, orientation,
585 newStackedView.Widget(), existingRootWidget, output.SplitRatio)599 newStackedView.Widget(), existingRootWidget, output.SplitRatio)
586 }600 }
601+ c.wireSplitRatioPersistence(ctx, splitView, output.ParentNode.ID)
587 602
588 wsView.SetRootWidgetDirect(splitView.Widget())603 wsView.SetRootWidgetDirect(splitView.Widget())
589 tr.RegisterSplit(output.ParentNode.ID, splitView.Widget(), orientation)604 tr.RegisterSplit(output.ParentNode.ID, splitView.Widget(), orientation)
@@ -643,6 +658,7 @@ func (c *WorkspaceCoordinator) replaceNonRootSplit(
643 splitView = layout.NewSplitView(ctx, factory, orientation,658 splitView = layout.NewSplitView(ctx, factory, orientation,
644 newStackedView.Widget(), activePaneWidget, output.SplitRatio)659 newStackedView.Widget(), activePaneWidget, output.SplitRatio)
645 }660 }
661+ c.wireSplitRatioPersistence(ctx, splitView, output.ParentNode.ID)
646 662
647 if isStartChild {663 if isStartChild {
648 panedWidget.SetStartChild(splitView.Widget())664 panedWidget.SetStartChild(splitView.Widget())
M internal/ui/layout/split.go +2 -1
@@ -35,6 +35,7 @@ type SplitView struct {
3535 const maxRetryFrames = 120
3636
3737 const notifyPositionDebounceDelay = 100 * time.Millisecond
38+const notifyPositionSuppressAfterApplyDelay = 50 * time.Millisecond
3839
3940 // NewSplitView creates a new split view with the given orientation and children.
4041 // The ratio determines the initial divider position (0.0-1.0).
@@ -226,7 +227,7 @@ func (sv *SplitView) ApplyRatio() bool {
226227 // Avoid treating programmatic SetPosition as a user resize.
227228 // We suppress notify::position briefly; GTK may emit during/after SetPosition.
228229 sv.mu.Lock()
229- sv.suppressNotifyUntil = time.Now().Add(50 * time.Millisecond)
230+ sv.suppressNotifyUntil = time.Now().Add(notifyPositionSuppressAfterApplyDelay)
230231 sv.mu.Unlock()
231232
232233 sv.paned.SetPosition(position)
@@ -35,6 +35,7 @@ type SplitView struct {
35 const maxRetryFrames = 12035 const maxRetryFrames = 120
36 36
37 const notifyPositionDebounceDelay = 100 * time.Millisecond37 const notifyPositionDebounceDelay = 100 * time.Millisecond
38+const notifyPositionSuppressAfterApplyDelay = 50 * time.Millisecond
38 39
39 // NewSplitView creates a new split view with the given orientation and children.40 // NewSplitView creates a new split view with the given orientation and children.
40 // The ratio determines the initial divider position (0.0-1.0).41 // The ratio determines the initial divider position (0.0-1.0).
@@ -226,7 +227,7 @@ func (sv *SplitView) ApplyRatio() bool {
226 // Avoid treating programmatic SetPosition as a user resize.227 // Avoid treating programmatic SetPosition as a user resize.
227 // We suppress notify::position briefly; GTK may emit during/after SetPosition.228 // We suppress notify::position briefly; GTK may emit during/after SetPosition.
228 sv.mu.Lock()229 sv.mu.Lock()
229- sv.suppressNotifyUntil = time.Now().Add(50 * time.Millisecond)230+ sv.suppressNotifyUntil = time.Now().Add(notifyPositionSuppressAfterApplyDelay)
230 sv.mu.Unlock()231 sv.mu.Unlock()
231 232
232 sv.paned.SetPosition(position)233 sv.paned.SetPosition(position)

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