detached 2835e26

fix(ui): improve pane and tab mode border rendering

Enhances the visual border indicators for pane mode and tab mode by
applying margins to the correct container hierarchy, ensuring the
window background color shows through properly.

Changes:
- Apply borders to root container instead of ContentArea when available
- Add resolveWindow() helper for safe window reference resolution
- Track border target widget for proper cleanup on mode exit
- Add GetWindow() method to TabManager for workspace access
- Fallback to ContentArea for compatibility with older builds

This fixes cases where borders weren't rendering correctly due to
incorrect container targeting in the GTK widget hierarchy.
commit
2835e2605ce62ade5d8698f2678a18385e9fa38d
author
bnema · · 2025-11-18 06:42 UTC
parent
6ab096f
signature
unsigned
4 files · +104 -25
M internal/app/browser/tab_manager.go +9 -0
@@ -23,6 +23,7 @@ type TabManager struct {
2323 rootContainer gtk.Widgetter // Main vertical box containing tab bar + content
2424 tabBar gtk.Widgetter // Horizontal tab bar container
2525 ContentArea gtk.Widgetter // Container for active workspace (exported for border styling)
26+ tabModeTarget gtk.Widgetter // Widget currently holding tab mode border margins
2627
2728 // Modal state
2829 tabModeActive bool
@@ -472,6 +473,14 @@ func (tm *TabManager) GetRootContainer() gtk.Widgetter {
472473 return tm.rootContainer
473474 }
474475
476+// GetWindow returns the GTK window backing the tab manager.
477+func (tm *TabManager) GetWindow() *webkit.Window {
478+ tm.mu.RLock()
479+ defer tm.mu.RUnlock()
480+
481+ return tm.window
482+}
483+
475484 // Cleanup performs cleanup of all tabs and resources.
476485 func (tm *TabManager) Cleanup() {
477486 tm.mu.Lock()
@@ -23,6 +23,7 @@ type TabManager struct {
23 rootContainer gtk.Widgetter // Main vertical box containing tab bar + content23 rootContainer gtk.Widgetter // Main vertical box containing tab bar + content
24 tabBar gtk.Widgetter // Horizontal tab bar container24 tabBar gtk.Widgetter // Horizontal tab bar container
25 ContentArea gtk.Widgetter // Container for active workspace (exported for border styling)25 ContentArea gtk.Widgetter // Container for active workspace (exported for border styling)
26+ tabModeTarget gtk.Widgetter // Widget currently holding tab mode border margins
26 27
27 // Modal state28 // Modal state
28 tabModeActive bool29 tabModeActive bool
@@ -472,6 +473,14 @@ func (tm *TabManager) GetRootContainer() gtk.Widgetter {
472 return tm.rootContainer473 return tm.rootContainer
473 }474 }
474 475
476+// GetWindow returns the GTK window backing the tab manager.
477+func (tm *TabManager) GetWindow() *webkit.Window {
478+ tm.mu.RLock()
479+ defer tm.mu.RUnlock()
480+
481+ return tm.window
482+}
483+
475 // Cleanup performs cleanup of all tabs and resources.484 // Cleanup performs cleanup of all tabs and resources.
476 func (tm *TabManager) Cleanup() {485 func (tm *TabManager) Cleanup() {
477 tm.mu.Lock()486 tm.mu.Lock()
M internal/app/browser/tab_mode.go +46 -14
@@ -6,6 +6,7 @@ import (
66
77 "github.com/bnema/dumber/internal/logging"
88 "github.com/bnema/dumber/pkg/webkit"
9+ "github.com/diamondburned/gotk4/pkg/gtk/v4"
910 )
1011
1112 // EnterTabMode activates the modal tab management mode.
@@ -209,51 +210,82 @@ func (tm *TabManager) handleRenameTab() {
209210 func (tm *TabManager) applyTabModeBorder() {
210211 logging.Debug("[tab-mode] applyTabModeBorder: entry")
211212
212- if tm.window == nil || tm.ContentArea == nil {
213- logging.Debug("[tab-mode] applyTabModeBorder: window or ContentArea is nil, returning")
213+ if tm.window == nil {
214+ logging.Debug("[tab-mode] applyTabModeBorder: window is nil, returning")
214215 return
215216 }
216217
217- logging.Debug("[tab-mode] applyTabModeBorder: applying margins to content area")
218+ // Determine which container should receive the margins:
219+ // - Prefer the root container (direct child of the window) so the window background shows
220+ // - Fallback to ContentArea for older builds
221+ var target gtk.Widgetter
222+ if tm.rootContainer != nil {
223+ target = tm.rootContainer
224+ logging.Debug("[tab-mode] applyTabModeBorder: using root container for border")
225+ } else if tm.ContentArea != nil {
226+ target = tm.ContentArea
227+ logging.Debug("[tab-mode] applyTabModeBorder: using content area fallback for border")
228+ } else {
229+ logging.Debug("[tab-mode] applyTabModeBorder: no container available, returning")
230+ return
231+ }
232+
233+ // Remember the container for removal
234+ tm.tabModeTarget = target
218235
219- // Apply 4px margins to content area to create space for the border
220- webkit.WidgetSetMargin(tm.ContentArea, 4)
236+ // Apply 4px margins to create space for the border
237+ webkit.WidgetSetMargin(target, 4)
221238
222239 // Add CSS class to window for background color (the "border" color shows in the margin space)
223240 gtkWindow := tm.window.AsWindow()
224241 webkit.WidgetAddCSSClass(gtkWindow, "tab-mode-active")
225242
226243 // Force resize/allocation to apply margin changes immediately
227- webkit.WidgetQueueResize(tm.ContentArea)
228- webkit.WidgetQueueAllocate(tm.ContentArea)
244+ webkit.WidgetQueueResize(target)
245+ webkit.WidgetQueueAllocate(target)
229246
230247 // Queue redraw to show changes
231248 webkit.WidgetQueueDraw(gtkWindow)
232- webkit.WidgetQueueDraw(tm.ContentArea)
249+ webkit.WidgetQueueDraw(target)
233250
234251 logging.Debug("[tab-mode] applyTabModeBorder: border applied successfully")
235252 }
236253
237254 // removeTabModeBorder removes the visual indicator when exiting tab mode.
238255 func (tm *TabManager) removeTabModeBorder() {
239- if tm.window == nil || tm.ContentArea == nil {
256+ if tm.window == nil {
257+ return
258+ }
259+
260+ // Use whichever container received margins
261+ target := tm.tabModeTarget
262+ if target == nil {
263+ if tm.rootContainer != nil {
264+ target = tm.rootContainer
265+ } else {
266+ target = tm.ContentArea
267+ }
268+ }
269+ if target == nil {
240270 return
241271 }
242272
243- // Remove margins from content area
244- webkit.WidgetSetMargin(tm.ContentArea, 0)
273+ // Remove margins
274+ webkit.WidgetSetMargin(target, 0)
245275
246276 // Remove CSS class from window
247277 gtkWindow := tm.window.AsWindow()
248278 webkit.WidgetRemoveCSSClass(gtkWindow, "tab-mode-active")
249279
250280 // Force resize/allocation to apply margin changes immediately
251- webkit.WidgetQueueResize(tm.ContentArea)
252- webkit.WidgetQueueAllocate(tm.ContentArea)
281+ webkit.WidgetQueueResize(target)
282+ webkit.WidgetQueueAllocate(target)
253283
254284 // Queue redraw to show changes
255285 webkit.WidgetQueueDraw(gtkWindow)
256- webkit.WidgetQueueDraw(tm.ContentArea)
286+ webkit.WidgetQueueDraw(target)
287+
288+ tm.tabModeTarget = nil
257289
258290 logging.Debug("[tab-mode] Removed tab mode visual indicator")
259291 }
@@ -6,6 +6,7 @@ import (
6 6
7 "github.com/bnema/dumber/internal/logging"7 "github.com/bnema/dumber/internal/logging"
8 "github.com/bnema/dumber/pkg/webkit"8 "github.com/bnema/dumber/pkg/webkit"
9+ "github.com/diamondburned/gotk4/pkg/gtk/v4"
9 )10 )
10 11
11 // EnterTabMode activates the modal tab management mode.12 // EnterTabMode activates the modal tab management mode.
@@ -209,51 +210,82 @@ func (tm *TabManager) handleRenameTab() {
209 func (tm *TabManager) applyTabModeBorder() {210 func (tm *TabManager) applyTabModeBorder() {
210 logging.Debug("[tab-mode] applyTabModeBorder: entry")211 logging.Debug("[tab-mode] applyTabModeBorder: entry")
211 212
212- if tm.window == nil || tm.ContentArea == nil {213+ if tm.window == nil {
213- logging.Debug("[tab-mode] applyTabModeBorder: window or ContentArea is nil, returning")214+ logging.Debug("[tab-mode] applyTabModeBorder: window is nil, returning")
214 return215 return
215 }216 }
216 217
217- logging.Debug("[tab-mode] applyTabModeBorder: applying margins to content area")218+ // Determine which container should receive the margins:
219+ // - Prefer the root container (direct child of the window) so the window background shows
220+ // - Fallback to ContentArea for older builds
221+ var target gtk.Widgetter
222+ if tm.rootContainer != nil {
223+ target = tm.rootContainer
224+ logging.Debug("[tab-mode] applyTabModeBorder: using root container for border")
225+ } else if tm.ContentArea != nil {
226+ target = tm.ContentArea
227+ logging.Debug("[tab-mode] applyTabModeBorder: using content area fallback for border")
228+ } else {
229+ logging.Debug("[tab-mode] applyTabModeBorder: no container available, returning")
230+ return
231+ }
232+
233+ // Remember the container for removal
234+ tm.tabModeTarget = target
218 235
219- // Apply 4px margins to content area to create space for the border236+ // Apply 4px margins to create space for the border
220- webkit.WidgetSetMargin(tm.ContentArea, 4)237+ webkit.WidgetSetMargin(target, 4)
221 238
222 // Add CSS class to window for background color (the "border" color shows in the margin space)239 // Add CSS class to window for background color (the "border" color shows in the margin space)
223 gtkWindow := tm.window.AsWindow()240 gtkWindow := tm.window.AsWindow()
224 webkit.WidgetAddCSSClass(gtkWindow, "tab-mode-active")241 webkit.WidgetAddCSSClass(gtkWindow, "tab-mode-active")
225 242
226 // Force resize/allocation to apply margin changes immediately243 // Force resize/allocation to apply margin changes immediately
227- webkit.WidgetQueueResize(tm.ContentArea)244+ webkit.WidgetQueueResize(target)
228- webkit.WidgetQueueAllocate(tm.ContentArea)245+ webkit.WidgetQueueAllocate(target)
229 246
230 // Queue redraw to show changes247 // Queue redraw to show changes
231 webkit.WidgetQueueDraw(gtkWindow)248 webkit.WidgetQueueDraw(gtkWindow)
232- webkit.WidgetQueueDraw(tm.ContentArea)249+ webkit.WidgetQueueDraw(target)
233 250
234 logging.Debug("[tab-mode] applyTabModeBorder: border applied successfully")251 logging.Debug("[tab-mode] applyTabModeBorder: border applied successfully")
235 }252 }
236 253
237 // removeTabModeBorder removes the visual indicator when exiting tab mode.254 // removeTabModeBorder removes the visual indicator when exiting tab mode.
238 func (tm *TabManager) removeTabModeBorder() {255 func (tm *TabManager) removeTabModeBorder() {
239- if tm.window == nil || tm.ContentArea == nil {256+ if tm.window == nil {
257+ return
258+ }
259+
260+ // Use whichever container received margins
261+ target := tm.tabModeTarget
262+ if target == nil {
263+ if tm.rootContainer != nil {
264+ target = tm.rootContainer
265+ } else {
266+ target = tm.ContentArea
267+ }
268+ }
269+ if target == nil {
240 return270 return
241 }271 }
242 272
243- // Remove margins from content area273+ // Remove margins
244- webkit.WidgetSetMargin(tm.ContentArea, 0)274+ webkit.WidgetSetMargin(target, 0)
245 275
246 // Remove CSS class from window276 // Remove CSS class from window
247 gtkWindow := tm.window.AsWindow()277 gtkWindow := tm.window.AsWindow()
248 webkit.WidgetRemoveCSSClass(gtkWindow, "tab-mode-active")278 webkit.WidgetRemoveCSSClass(gtkWindow, "tab-mode-active")
249 279
250 // Force resize/allocation to apply margin changes immediately280 // Force resize/allocation to apply margin changes immediately
251- webkit.WidgetQueueResize(tm.ContentArea)281+ webkit.WidgetQueueResize(target)
252- webkit.WidgetQueueAllocate(tm.ContentArea)282+ webkit.WidgetQueueAllocate(target)
253 283
254 // Queue redraw to show changes284 // Queue redraw to show changes
255 webkit.WidgetQueueDraw(gtkWindow)285 webkit.WidgetQueueDraw(gtkWindow)
256- webkit.WidgetQueueDraw(tm.ContentArea)286+ webkit.WidgetQueueDraw(target)
287+
288+ tm.tabModeTarget = nil
257 289
258 logging.Debug("[tab-mode] Removed tab mode visual indicator")290 logging.Debug("[tab-mode] Removed tab mode visual indicator")
259 }291 }
M internal/app/browser/workspace_manager.go +28 -0
@@ -73,6 +73,34 @@ type WorkspaceManager struct {
7373 pendingIdle map[uintptr][]*paneNode
7474 }
7575
76+// resolveWindow ensures the workspace has a reference to the GTK window.
77+func (wm *WorkspaceManager) resolveWindow() *webkit.Window {
78+ if wm == nil {
79+ return nil
80+ }
81+
82+ if wm.window != nil {
83+ return wm.window
84+ }
85+
86+ if wm.app != nil {
87+ if wm.app.tabManager != nil {
88+ if win := wm.app.tabManager.GetWindow(); win != nil {
89+ wm.window = win
90+ return win
91+ }
92+ }
93+ if wm.app.webView != nil {
94+ if win := wm.app.webView.Window(); win != nil {
95+ wm.window = win
96+ return win
97+ }
98+ }
99+ }
100+
101+ return nil
102+}
103+
76104 // Workspace navigation shortcuts are now handled globally by WindowShortcutHandler
77105
78106 // NewWorkspaceManager builds a workspace manager rooted at the provided pane.
@@ -73,6 +73,34 @@ type WorkspaceManager struct {
73 pendingIdle map[uintptr][]*paneNode73 pendingIdle map[uintptr][]*paneNode
74 }74 }
75 75
76+// resolveWindow ensures the workspace has a reference to the GTK window.
77+func (wm *WorkspaceManager) resolveWindow() *webkit.Window {
78+ if wm == nil {
79+ return nil
80+ }
81+
82+ if wm.window != nil {
83+ return wm.window
84+ }
85+
86+ if wm.app != nil {
87+ if wm.app.tabManager != nil {
88+ if win := wm.app.tabManager.GetWindow(); win != nil {
89+ wm.window = win
90+ return win
91+ }
92+ }
93+ if wm.app.webView != nil {
94+ if win := wm.app.webView.Window(); win != nil {
95+ wm.window = win
96+ return win
97+ }
98+ }
99+ }
100+
101+ return nil
102+}
103+
76 // Workspace navigation shortcuts are now handled globally by WindowShortcutHandler104 // Workspace navigation shortcuts are now handled globally by WindowShortcutHandler
77 105
78 // NewWorkspaceManager builds a workspace manager rooted at the provided pane.106 // NewWorkspaceManager builds a workspace manager rooted at the provided pane.
M internal/app/browser/workspace_pane_mode.go +21 -11
@@ -207,18 +207,27 @@ func (wm *WorkspaceManager) dispatchPaneModeEvent(event string, detail string) {
207207
208208 // applyPaneModeBorder applies pane mode visual indicator using GTK margins
209209 func (wm *WorkspaceManager) applyPaneModeBorder() {
210- if wm.window == nil {
210+ window := wm.resolveWindow()
211+ if window == nil {
212+ log.Printf("[pane-mode] Cannot apply border: missing window reference")
211213 return
212214 }
213215
214216 // Determine which container to apply margins to:
215- // - If tab manager exists, apply to its ContentArea (so window background shows through)
217+ // - If tab manager exists, apply to its root container (direct child of window)
218+ // so margins reveal the window background color
216219 // - Otherwise, apply to workspace root (direct child of window)
217220 var targetContainer gtk.Widgetter
218- if wm.app != nil && wm.app.tabManager != nil && wm.app.tabManager.ContentArea != nil {
219- // Tab environment: apply margins to tab manager's content area
220- targetContainer = wm.app.tabManager.ContentArea
221- log.Printf("[pane-mode] Using tab manager's content area for border")
221+ if wm.app != nil && wm.app.tabManager != nil {
222+ if rootContainer := wm.app.tabManager.GetRootContainer(); rootContainer != nil {
223+ // Tab environment: apply margins to the root container that's attached to the window
224+ targetContainer = rootContainer
225+ log.Printf("[pane-mode] Using tab manager root container for border")
226+ } else if wm.app.tabManager.ContentArea != nil {
227+ // Fallback: older builds where root container isn't available yet
228+ targetContainer = wm.app.tabManager.ContentArea
229+ log.Printf("[pane-mode] Using tab manager content area fallback for border")
230+ }
222231 } else if wm.root != nil && wm.root.container != nil {
223232 // Non-tab environment: apply margins to workspace root
224233 targetContainer = wm.root.container
@@ -234,14 +243,14 @@ func (wm *WorkspaceManager) applyPaneModeBorder() {
234243 webkit.WidgetSetMargin(targetContainer, 4)
235244
236245 // Add CSS class to window for background color (the "border" color shows in the margin space)
237- webkit.WidgetAddCSSClass(wm.window.AsWindow(), "pane-mode-active")
246+ webkit.WidgetAddCSSClass(window.AsWindow(), "pane-mode-active")
238247
239248 // Force resize/allocation to apply margin changes immediately
240249 webkit.WidgetQueueResize(targetContainer)
241250 webkit.WidgetQueueAllocate(targetContainer)
242251
243252 // Queue redraw to show changes
244- webkit.WidgetQueueDraw(wm.window.AsWindow())
253+ webkit.WidgetQueueDraw(window.AsWindow())
245254 webkit.WidgetQueueDraw(targetContainer)
246255
247256 log.Printf("[pane-mode] Applied border using margins (container=%p)", targetContainer)
@@ -249,7 +258,8 @@ func (wm *WorkspaceManager) applyPaneModeBorder() {
249258
250259 // removePaneModeBorder removes the pane mode visual indicator
251260 func (wm *WorkspaceManager) removePaneModeBorder() {
252- if wm.window == nil {
261+ window := wm.resolveWindow()
262+ if window == nil {
253263 return
254264 }
255265
@@ -268,14 +278,14 @@ func (wm *WorkspaceManager) removePaneModeBorder() {
268278 webkit.WidgetSetMargin(container, 0)
269279
270280 // Remove CSS class from window
271- webkit.WidgetRemoveCSSClass(wm.window.AsWindow(), "pane-mode-active")
281+ webkit.WidgetRemoveCSSClass(window.AsWindow(), "pane-mode-active")
272282
273283 // Force resize/allocation to apply margin changes immediately
274284 webkit.WidgetQueueResize(container)
275285 webkit.WidgetQueueAllocate(container)
276286
277287 // Queue redraw to show changes
278- webkit.WidgetQueueDraw(wm.window.AsWindow())
288+ webkit.WidgetQueueDraw(window.AsWindow())
279289 webkit.WidgetQueueDraw(container)
280290
281291 // Clear the saved container reference
@@ -207,18 +207,27 @@ func (wm *WorkspaceManager) dispatchPaneModeEvent(event string, detail string) {
207 207
208 // applyPaneModeBorder applies pane mode visual indicator using GTK margins208 // applyPaneModeBorder applies pane mode visual indicator using GTK margins
209 func (wm *WorkspaceManager) applyPaneModeBorder() {209 func (wm *WorkspaceManager) applyPaneModeBorder() {
210- if wm.window == nil {210+ window := wm.resolveWindow()
211+ if window == nil {
212+ log.Printf("[pane-mode] Cannot apply border: missing window reference")
211 return213 return
212 }214 }
213 215
214 // Determine which container to apply margins to:216 // Determine which container to apply margins to:
215- // - If tab manager exists, apply to its ContentArea (so window background shows through)217+ // - If tab manager exists, apply to its root container (direct child of window)
218+ // so margins reveal the window background color
216 // - Otherwise, apply to workspace root (direct child of window)219 // - Otherwise, apply to workspace root (direct child of window)
217 var targetContainer gtk.Widgetter220 var targetContainer gtk.Widgetter
218- if wm.app != nil && wm.app.tabManager != nil && wm.app.tabManager.ContentArea != nil {221+ if wm.app != nil && wm.app.tabManager != nil {
219- // Tab environment: apply margins to tab manager's content area222+ if rootContainer := wm.app.tabManager.GetRootContainer(); rootContainer != nil {
220- targetContainer = wm.app.tabManager.ContentArea223+ // Tab environment: apply margins to the root container that's attached to the window
221- log.Printf("[pane-mode] Using tab manager's content area for border")224+ targetContainer = rootContainer
225+ log.Printf("[pane-mode] Using tab manager root container for border")
226+ } else if wm.app.tabManager.ContentArea != nil {
227+ // Fallback: older builds where root container isn't available yet
228+ targetContainer = wm.app.tabManager.ContentArea
229+ log.Printf("[pane-mode] Using tab manager content area fallback for border")
230+ }
222 } else if wm.root != nil && wm.root.container != nil {231 } else if wm.root != nil && wm.root.container != nil {
223 // Non-tab environment: apply margins to workspace root232 // Non-tab environment: apply margins to workspace root
224 targetContainer = wm.root.container233 targetContainer = wm.root.container
@@ -234,14 +243,14 @@ func (wm *WorkspaceManager) applyPaneModeBorder() {
234 webkit.WidgetSetMargin(targetContainer, 4)243 webkit.WidgetSetMargin(targetContainer, 4)
235 244
236 // Add CSS class to window for background color (the "border" color shows in the margin space)245 // Add CSS class to window for background color (the "border" color shows in the margin space)
237- webkit.WidgetAddCSSClass(wm.window.AsWindow(), "pane-mode-active")246+ webkit.WidgetAddCSSClass(window.AsWindow(), "pane-mode-active")
238 247
239 // Force resize/allocation to apply margin changes immediately248 // Force resize/allocation to apply margin changes immediately
240 webkit.WidgetQueueResize(targetContainer)249 webkit.WidgetQueueResize(targetContainer)
241 webkit.WidgetQueueAllocate(targetContainer)250 webkit.WidgetQueueAllocate(targetContainer)
242 251
243 // Queue redraw to show changes252 // Queue redraw to show changes
244- webkit.WidgetQueueDraw(wm.window.AsWindow())253+ webkit.WidgetQueueDraw(window.AsWindow())
245 webkit.WidgetQueueDraw(targetContainer)254 webkit.WidgetQueueDraw(targetContainer)
246 255
247 log.Printf("[pane-mode] Applied border using margins (container=%p)", targetContainer)256 log.Printf("[pane-mode] Applied border using margins (container=%p)", targetContainer)
@@ -249,7 +258,8 @@ func (wm *WorkspaceManager) applyPaneModeBorder() {
249 258
250 // removePaneModeBorder removes the pane mode visual indicator259 // removePaneModeBorder removes the pane mode visual indicator
251 func (wm *WorkspaceManager) removePaneModeBorder() {260 func (wm *WorkspaceManager) removePaneModeBorder() {
252- if wm.window == nil {261+ window := wm.resolveWindow()
262+ if window == nil {
253 return263 return
254 }264 }
255 265
@@ -268,14 +278,14 @@ func (wm *WorkspaceManager) removePaneModeBorder() {
268 webkit.WidgetSetMargin(container, 0)278 webkit.WidgetSetMargin(container, 0)
269 279
270 // Remove CSS class from window280 // Remove CSS class from window
271- webkit.WidgetRemoveCSSClass(wm.window.AsWindow(), "pane-mode-active")281+ webkit.WidgetRemoveCSSClass(window.AsWindow(), "pane-mode-active")
272 282
273 // Force resize/allocation to apply margin changes immediately283 // Force resize/allocation to apply margin changes immediately
274 webkit.WidgetQueueResize(container)284 webkit.WidgetQueueResize(container)
275 webkit.WidgetQueueAllocate(container)285 webkit.WidgetQueueAllocate(container)
276 286
277 // Queue redraw to show changes287 // Queue redraw to show changes
278- webkit.WidgetQueueDraw(wm.window.AsWindow())288+ webkit.WidgetQueueDraw(window.AsWindow())
279 webkit.WidgetQueueDraw(container)289 webkit.WidgetQueueDraw(container)
280 290
281 // Clear the saved container reference291 // Clear the saved container reference

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