detached 2e39051

refactor(browser): use direction and UI string constants throughout workspace

Replaces hardcoded direction strings (left/right/up/down) with DirectionLeft/Right/Up/Down constants and \"New Tab\" with NewTabTitle constant for maintainability.
commit
2e390517bad9747afa50dd84fd0181623bd55fda
author
bnema · · 2025-10-23 19:33 UTC
committer
bnema · · 2025-10-24 17:33 UTC
parent
ca5e83c
signature
unsigned
12 files · +71 -65
M internal/app/browser/stacked_panes.go +5 -5
@@ -430,7 +430,7 @@ func (spm *StackedPaneManager) UpdateStackVisibility(stackNode *paneNode) {
430430 if pane.pane != nil && pane.pane.webView != nil {
431431 title := pane.pane.webView.GetTitle()
432432 if title == "" {
433- title = "New Tab"
433+ title = NewTabTitle
434434 }
435435 spm.updateTitleBarLabel(pane, title)
436436 }
@@ -475,12 +475,12 @@ func (spm *StackedPaneManager) NavigateStack(direction string) bool {
475475 // Calculate new index based on direction
476476 var newIndex int
477477 switch direction {
478- case "up":
478+ case DirectionUp:
479479 newIndex = currentIndex - 1
480480 if newIndex < 0 {
481481 newIndex = len(stackNode.stackedPanes) - 1 // Wrap to last
482482 }
483- case "down":
483+ case DirectionDown:
484484 newIndex = currentIndex + 1
485485 if newIndex >= len(stackNode.stackedPanes) {
486486 newIndex = 0 // Wrap to first
@@ -524,11 +524,11 @@ func (spm *StackedPaneManager) createTitleBar(pane *paneNode) gtk.Widgetter {
524524 if pane.pane != nil && pane.pane.webView != nil {
525525 titleText = pane.pane.webView.GetTitle()
526526 if titleText == "" {
527- titleText = "New Tab" // Fallback only when title is actually empty
527+ titleText = NewTabTitle // Fallback only when title is actually empty
528528 }
529529 pageURL = pane.pane.webView.GetCurrentURL()
530530 } else {
531- titleText = "New Tab"
531+ titleText = NewTabTitle
532532 }
533533
534534 titleBar := spm.createTitleBarWithTitle(titleText, pageURL)
@@ -430,7 +430,7 @@ func (spm *StackedPaneManager) UpdateStackVisibility(stackNode *paneNode) {
430 if pane.pane != nil && pane.pane.webView != nil {430 if pane.pane != nil && pane.pane.webView != nil {
431 title := pane.pane.webView.GetTitle()431 title := pane.pane.webView.GetTitle()
432 if title == "" {432 if title == "" {
433- title = "New Tab"433+ title = NewTabTitle
434 }434 }
435 spm.updateTitleBarLabel(pane, title)435 spm.updateTitleBarLabel(pane, title)
436 }436 }
@@ -475,12 +475,12 @@ func (spm *StackedPaneManager) NavigateStack(direction string) bool {
475 // Calculate new index based on direction475 // Calculate new index based on direction
476 var newIndex int476 var newIndex int
477 switch direction {477 switch direction {
478- case "up":478+ case DirectionUp:
479 newIndex = currentIndex - 1479 newIndex = currentIndex - 1
480 if newIndex < 0 {480 if newIndex < 0 {
481 newIndex = len(stackNode.stackedPanes) - 1 // Wrap to last481 newIndex = len(stackNode.stackedPanes) - 1 // Wrap to last
482 }482 }
483- case "down":483+ case DirectionDown:
484 newIndex = currentIndex + 1484 newIndex = currentIndex + 1
485 if newIndex >= len(stackNode.stackedPanes) {485 if newIndex >= len(stackNode.stackedPanes) {
486 newIndex = 0 // Wrap to first486 newIndex = 0 // Wrap to first
@@ -524,11 +524,11 @@ func (spm *StackedPaneManager) createTitleBar(pane *paneNode) gtk.Widgetter {
524 if pane.pane != nil && pane.pane.webView != nil {524 if pane.pane != nil && pane.pane.webView != nil {
525 titleText = pane.pane.webView.GetTitle()525 titleText = pane.pane.webView.GetTitle()
526 if titleText == "" {526 if titleText == "" {
527- titleText = "New Tab" // Fallback only when title is actually empty527+ titleText = NewTabTitle // Fallback only when title is actually empty
528 }528 }
529 pageURL = pane.pane.webView.GetCurrentURL()529 pageURL = pane.pane.webView.GetCurrentURL()
530 } else {530 } else {
531- titleText = "New Tab"531+ titleText = NewTabTitle
532 }532 }
533 533
534 titleBar := spm.createTitleBarWithTitle(titleText, pageURL)534 titleBar := spm.createTitleBarWithTitle(titleText, pageURL)
M internal/app/browser/window_shortcuts.go +4 -4
@@ -69,10 +69,10 @@ func (h *WindowShortcutHandler) registerGlobalShortcuts() error {
6969 {"ctrl+minus", h.handleZoomOut, "Zoom out"},
7070 {"ctrl+0", h.handleZoomReset, "Zoom reset"},
7171 // Workspace navigation shortcuts - global level for proper active pane targeting
72- {"alt+ArrowLeft", func() { h.handleWorkspaceNavigation("left") }, "Navigate left pane"},
73- {"alt+ArrowRight", func() { h.handleWorkspaceNavigation("right") }, "Navigate right pane"},
74- {"alt+ArrowUp", func() { h.handleWorkspaceNavigation("up") }, "Navigate up pane"},
75- {"alt+ArrowDown", func() { h.handleWorkspaceNavigation("down") }, "Navigate down pane"},
72+ {"alt+ArrowLeft", func() { h.handleWorkspaceNavigation(DirectionLeft) }, "Navigate left pane"},
73+ {"alt+ArrowRight", func() { h.handleWorkspaceNavigation(DirectionRight) }, "Navigate right pane"},
74+ {"alt+ArrowUp", func() { h.handleWorkspaceNavigation(DirectionUp) }, "Navigate up pane"},
75+ {"alt+ArrowDown", func() { h.handleWorkspaceNavigation(DirectionDown) }, "Navigate down pane"},
7676 }
7777
7878 for _, shortcut := range shortcuts {
@@ -69,10 +69,10 @@ func (h *WindowShortcutHandler) registerGlobalShortcuts() error {
69 {"ctrl+minus", h.handleZoomOut, "Zoom out"},69 {"ctrl+minus", h.handleZoomOut, "Zoom out"},
70 {"ctrl+0", h.handleZoomReset, "Zoom reset"},70 {"ctrl+0", h.handleZoomReset, "Zoom reset"},
71 // Workspace navigation shortcuts - global level for proper active pane targeting71 // Workspace navigation shortcuts - global level for proper active pane targeting
72- {"alt+ArrowLeft", func() { h.handleWorkspaceNavigation("left") }, "Navigate left pane"},72+ {"alt+ArrowLeft", func() { h.handleWorkspaceNavigation(DirectionLeft) }, "Navigate left pane"},
73- {"alt+ArrowRight", func() { h.handleWorkspaceNavigation("right") }, "Navigate right pane"},73+ {"alt+ArrowRight", func() { h.handleWorkspaceNavigation(DirectionRight) }, "Navigate right pane"},
74- {"alt+ArrowUp", func() { h.handleWorkspaceNavigation("up") }, "Navigate up pane"},74+ {"alt+ArrowUp", func() { h.handleWorkspaceNavigation(DirectionUp) }, "Navigate up pane"},
75- {"alt+ArrowDown", func() { h.handleWorkspaceNavigation("down") }, "Navigate down pane"},75+ {"alt+ArrowDown", func() { h.handleWorkspaceNavigation(DirectionDown) }, "Navigate down pane"},
76 }76 }
77 77
78 for _, shortcut := range shortcuts {78 for _, shortcut := range shortcuts {
M internal/app/browser/workspace_css.go +3 -1
@@ -185,7 +185,9 @@ func (wm *WorkspaceManager) ensureWorkspaceStyles() {
185185
186186 // Only apply CSS if it hasn't been applied globally or if content changed
187187 if !globalCSSInitialized || globalCSSContent != workspaceCSS {
188- webkit.AddCSSProvider(workspaceCSS)
188+ if err := webkit.AddCSSProvider(workspaceCSS); err != nil {
189+ log.Printf("[workspace] WARNING: Failed to add CSS provider: %v", err)
190+ }
189191 globalCSSInitialized = true
190192 globalCSSContent = workspaceCSS
191193 log.Printf("[workspace] Applied workspace CSS styles (%d bytes)", len(workspaceCSS))
@@ -185,7 +185,9 @@ func (wm *WorkspaceManager) ensureWorkspaceStyles() {
185 185
186 // Only apply CSS if it hasn't been applied globally or if content changed186 // Only apply CSS if it hasn't been applied globally or if content changed
187 if !globalCSSInitialized || globalCSSContent != workspaceCSS {187 if !globalCSSInitialized || globalCSSContent != workspaceCSS {
188- webkit.AddCSSProvider(workspaceCSS)188+ if err := webkit.AddCSSProvider(workspaceCSS); err != nil {
189+ log.Printf("[workspace] WARNING: Failed to add CSS provider: %v", err)
190+ }
189 globalCSSInitialized = true191 globalCSSInitialized = true
190 globalCSSContent = workspaceCSS192 globalCSSContent = workspaceCSS
191 log.Printf("[workspace] Applied workspace CSS styles (%d bytes)", len(workspaceCSS))193 log.Printf("[workspace] Applied workspace CSS styles (%d bytes)", len(workspaceCSS))
M internal/app/browser/workspace_geometry_validator.go +2 -2
@@ -166,9 +166,9 @@ func (gv *GeometryValidator) ValidateSplit(node *paneNode, direction string) Val
166166 }
167167
168168 switch direction {
169- case "left", "right":
169+ case DirectionLeft, DirectionRight:
170170 return gv.ValidateHorizontalSplit(node)
171- case "up", "down":
171+ case DirectionUp, DirectionDown:
172172 return gv.ValidateVerticalSplit(node)
173173 default:
174174 return ValidationResult{IsValid: false, Reason: fmt.Sprintf("unknown split direction: %s", direction)}
@@ -166,9 +166,9 @@ func (gv *GeometryValidator) ValidateSplit(node *paneNode, direction string) Val
166 }166 }
167 167
168 switch direction {168 switch direction {
169- case "left", "right":169+ case DirectionLeft, DirectionRight:
170 return gv.ValidateHorizontalSplit(node)170 return gv.ValidateHorizontalSplit(node)
171- case "up", "down":171+ case DirectionUp, DirectionDown:
172 return gv.ValidateVerticalSplit(node)172 return gv.ValidateVerticalSplit(node)
173 default:173 default:
174 return ValidationResult{IsValid: false, Reason: fmt.Sprintf("unknown split direction: %s", direction)}174 return ValidationResult{IsValid: false, Reason: fmt.Sprintf("unknown split direction: %s", direction)}
M internal/app/browser/workspace_manager.go +1 -1
@@ -259,7 +259,7 @@ func (wm *WorkspaceManager) OnWorkspaceMessage(source *webkit.WebView, msg messa
259259 wm.SetActivePane(node, SourceProgrammatic)
260260 direction := strings.ToLower(msg.Direction)
261261 if direction == "" {
262- direction = "right"
262+ direction = DirectionRight
263263 }
264264
265265 if wm.shouldDebounce(source, 200*time.Millisecond) {
@@ -259,7 +259,7 @@ func (wm *WorkspaceManager) OnWorkspaceMessage(source *webkit.WebView, msg messa
259 wm.SetActivePane(node, SourceProgrammatic)259 wm.SetActivePane(node, SourceProgrammatic)
260 direction := strings.ToLower(msg.Direction)260 direction := strings.ToLower(msg.Direction)
261 if direction == "" {261 if direction == "" {
262- direction = "right"262+ direction = DirectionRight
263 }263 }
264 264
265 if wm.shouldDebounce(source, 200*time.Millisecond) {265 if wm.shouldDebounce(source, 200*time.Millisecond) {
M internal/app/browser/workspace_navigation.go +23 -23
@@ -105,21 +105,21 @@ func (wm *WorkspaceManager) detachFocus(node *paneNode) {
105105 }
106106
107107 // FocusNeighbor moves focus to the nearest pane in the requested direction using the
108-// actual widget geometry to determine adjacency. For stacked panes, "up" and "down"
108+// actual widget geometry to determine adjacency. For stacked panes, DirectionUp and DirectionDown
109109 // navigate within the stack.
110110 func (wm *WorkspaceManager) FocusNeighbor(direction string) bool {
111111 if wm == nil {
112112 return false
113113 }
114114 switch strings.ToLower(direction) {
115- case "up", "down":
115+ case DirectionUp, DirectionDown:
116116 // Check if current pane is part of a stack and handle stack navigation
117117 if wm.stackedPaneManager.NavigateStack(strings.ToLower(direction)) {
118118 return true
119119 }
120120 // Fall back to regular adjacency navigation
121121 return wm.focusAdjacent(strings.ToLower(direction))
122- case "left", "right":
122+ case DirectionLeft, DirectionRight:
123123 return wm.focusAdjacent(strings.ToLower(direction))
124124 default:
125125 log.Printf("[workspace] invalid focus direction: %s (expected: up, down, left, right)", direction)
@@ -169,12 +169,12 @@ func (wm *WorkspaceManager) navigateStack(direction string) bool {
169169 // Calculate new index based on direction
170170 var newIndex int
171171 switch direction {
172- case "up":
172+ case DirectionUp:
173173 newIndex = currentIndex - 1
174174 if newIndex < 0 {
175175 newIndex = len(stackNode.stackedPanes) - 1 // Wrap to last
176176 }
177- case "down":
177+ case DirectionDown:
178178 newIndex = currentIndex + 1
179179 if newIndex >= len(stackNode.stackedPanes) {
180180 newIndex = 0 // Wrap to first
@@ -235,29 +235,29 @@ func (wm *WorkspaceManager) focusAdjacent(direction string) bool {
235235
236236 var score float64
237237 switch direction {
238- case "left":
238+ case DirectionLeft:
239239 if dx >= -focusEpsilon {
240240 continue
241241 }
242242 score = math.Abs(dx)*1000 + math.Abs(dy)
243- case "right":
243+ case DirectionRight:
244244 if dx <= focusEpsilon {
245245 continue
246246 }
247247 score = math.Abs(dx)*1000 + math.Abs(dy)
248- case "up":
248+ case DirectionUp:
249249 if dy >= -focusEpsilon {
250250 continue
251251 }
252252 score = math.Abs(dy)*1000 + math.Abs(dx)
253- case "down":
253+ case DirectionDown:
254254 if dy <= focusEpsilon {
255255 continue
256256 }
257257 score = math.Abs(dy)*1000 + math.Abs(dx)
258258 }
259259
260- if direction == "up" || direction == "down" {
260+ if direction == DirectionUp || direction == DirectionDown {
261261 debugCandidates = append(debugCandidates, fmt.Sprintf("cand=%p dx=%.2f dy=%.2f score=%.2f", candidate.container, dx, dy, score))
262262 }
263263
@@ -287,29 +287,29 @@ func (wm *WorkspaceManager) structuralNeighbor(node *paneNode, direction string)
287287 refX, refY, refWidth, refHeight := webkit.WidgetGetAllocation(node.container)
288288 cx := float64(refX) + float64(refWidth)/2.0
289289 cy := float64(refY) + float64(refHeight)/2.0
290- axisVertical := direction == "up" || direction == "down"
290+ axisVertical := direction == DirectionUp || direction == DirectionDown
291291
292292 for parent := node.parent; parent != nil; parent = parent.parent {
293293 switch direction {
294- case "up":
294+ case DirectionUp:
295295 if axisVertical && parent.orientation == gtk.OrientationVertical && parent.right == node {
296296 if leaf := wm.closestLeafFromSubtree(parent.left, cx, cy, direction); leaf != nil {
297297 return leaf
298298 }
299299 }
300- case "down":
300+ case DirectionDown:
301301 if axisVertical && parent.orientation == gtk.OrientationVertical && parent.left == node {
302302 if leaf := wm.closestLeafFromSubtree(parent.right, cx, cy, direction); leaf != nil {
303303 return leaf
304304 }
305305 }
306- case "left":
306+ case DirectionLeft:
307307 if !axisVertical && parent.orientation == gtk.OrientationHorizontal && parent.right == node {
308308 if leaf := wm.closestLeafFromSubtree(parent.left, cx, cy, direction); leaf != nil {
309309 return leaf
310310 }
311311 }
312- case "right":
312+ case DirectionRight:
313313 if !axisVertical && parent.orientation == gtk.OrientationHorizontal && parent.left == node {
314314 if leaf := wm.closestLeafFromSubtree(parent.right, cx, cy, direction); leaf != nil {
315315 return leaf
@@ -338,22 +338,22 @@ func (wm *WorkspaceManager) closestLeafFromSubtree(node *paneNode, cx, cy float6
338338 dy := ty - cy
339339 var score float64
340340 switch direction {
341- case "left":
341+ case DirectionLeft:
342342 if dx >= -focusEpsilon {
343343 continue
344344 }
345345 score = math.Abs(dx)*1000 + math.Abs(dy)
346- case "right":
346+ case DirectionRight:
347347 if dx <= focusEpsilon {
348348 continue
349349 }
350350 score = math.Abs(dx)*1000 + math.Abs(dy)
351- case "up":
351+ case DirectionUp:
352352 if dy >= -focusEpsilon {
353353 continue
354354 }
355355 score = math.Abs(dy)*1000 + math.Abs(dx)
356- case "down":
356+ case DirectionDown:
357357 if dy <= focusEpsilon {
358358 continue
359359 }
@@ -393,22 +393,22 @@ func (wm *WorkspaceManager) boundaryFallbackWithDepth(node *paneNode, direction
393393 return node
394394 }
395395 switch direction {
396- case "up":
396+ case DirectionUp:
397397 if leaf := wm.boundaryFallbackWithDepth(node.right, direction, depth+1); leaf != nil {
398398 return leaf
399399 }
400400 return wm.boundaryFallbackWithDepth(node.left, direction, depth+1)
401- case "down":
401+ case DirectionDown:
402402 if leaf := wm.boundaryFallbackWithDepth(node.left, direction, depth+1); leaf != nil {
403403 return leaf
404404 }
405405 return wm.boundaryFallbackWithDepth(node.right, direction, depth+1)
406- case "left":
406+ case DirectionLeft:
407407 if leaf := wm.boundaryFallbackWithDepth(node.right, direction, depth+1); leaf != nil {
408408 return leaf
409409 }
410410 return wm.boundaryFallbackWithDepth(node.left, direction, depth+1)
411- case "right":
411+ case DirectionRight:
412412 if leaf := wm.boundaryFallbackWithDepth(node.left, direction, depth+1); leaf != nil {
413413 return leaf
414414 }
@@ -105,21 +105,21 @@ func (wm *WorkspaceManager) detachFocus(node *paneNode) {
105 }105 }
106 106
107 // FocusNeighbor moves focus to the nearest pane in the requested direction using the107 // FocusNeighbor moves focus to the nearest pane in the requested direction using the
108-// actual widget geometry to determine adjacency. For stacked panes, "up" and "down"108+// actual widget geometry to determine adjacency. For stacked panes, DirectionUp and DirectionDown
109 // navigate within the stack.109 // navigate within the stack.
110 func (wm *WorkspaceManager) FocusNeighbor(direction string) bool {110 func (wm *WorkspaceManager) FocusNeighbor(direction string) bool {
111 if wm == nil {111 if wm == nil {
112 return false112 return false
113 }113 }
114 switch strings.ToLower(direction) {114 switch strings.ToLower(direction) {
115- case "up", "down":115+ case DirectionUp, DirectionDown:
116 // Check if current pane is part of a stack and handle stack navigation116 // Check if current pane is part of a stack and handle stack navigation
117 if wm.stackedPaneManager.NavigateStack(strings.ToLower(direction)) {117 if wm.stackedPaneManager.NavigateStack(strings.ToLower(direction)) {
118 return true118 return true
119 }119 }
120 // Fall back to regular adjacency navigation120 // Fall back to regular adjacency navigation
121 return wm.focusAdjacent(strings.ToLower(direction))121 return wm.focusAdjacent(strings.ToLower(direction))
122- case "left", "right":122+ case DirectionLeft, DirectionRight:
123 return wm.focusAdjacent(strings.ToLower(direction))123 return wm.focusAdjacent(strings.ToLower(direction))
124 default:124 default:
125 log.Printf("[workspace] invalid focus direction: %s (expected: up, down, left, right)", direction)125 log.Printf("[workspace] invalid focus direction: %s (expected: up, down, left, right)", direction)
@@ -169,12 +169,12 @@ func (wm *WorkspaceManager) navigateStack(direction string) bool {
169 // Calculate new index based on direction169 // Calculate new index based on direction
170 var newIndex int170 var newIndex int
171 switch direction {171 switch direction {
172- case "up":172+ case DirectionUp:
173 newIndex = currentIndex - 1173 newIndex = currentIndex - 1
174 if newIndex < 0 {174 if newIndex < 0 {
175 newIndex = len(stackNode.stackedPanes) - 1 // Wrap to last175 newIndex = len(stackNode.stackedPanes) - 1 // Wrap to last
176 }176 }
177- case "down":177+ case DirectionDown:
178 newIndex = currentIndex + 1178 newIndex = currentIndex + 1
179 if newIndex >= len(stackNode.stackedPanes) {179 if newIndex >= len(stackNode.stackedPanes) {
180 newIndex = 0 // Wrap to first180 newIndex = 0 // Wrap to first
@@ -235,29 +235,29 @@ func (wm *WorkspaceManager) focusAdjacent(direction string) bool {
235 235
236 var score float64236 var score float64
237 switch direction {237 switch direction {
238- case "left":238+ case DirectionLeft:
239 if dx >= -focusEpsilon {239 if dx >= -focusEpsilon {
240 continue240 continue
241 }241 }
242 score = math.Abs(dx)*1000 + math.Abs(dy)242 score = math.Abs(dx)*1000 + math.Abs(dy)
243- case "right":243+ case DirectionRight:
244 if dx <= focusEpsilon {244 if dx <= focusEpsilon {
245 continue245 continue
246 }246 }
247 score = math.Abs(dx)*1000 + math.Abs(dy)247 score = math.Abs(dx)*1000 + math.Abs(dy)
248- case "up":248+ case DirectionUp:
249 if dy >= -focusEpsilon {249 if dy >= -focusEpsilon {
250 continue250 continue
251 }251 }
252 score = math.Abs(dy)*1000 + math.Abs(dx)252 score = math.Abs(dy)*1000 + math.Abs(dx)
253- case "down":253+ case DirectionDown:
254 if dy <= focusEpsilon {254 if dy <= focusEpsilon {
255 continue255 continue
256 }256 }
257 score = math.Abs(dy)*1000 + math.Abs(dx)257 score = math.Abs(dy)*1000 + math.Abs(dx)
258 }258 }
259 259
260- if direction == "up" || direction == "down" {260+ if direction == DirectionUp || direction == DirectionDown {
261 debugCandidates = append(debugCandidates, fmt.Sprintf("cand=%p dx=%.2f dy=%.2f score=%.2f", candidate.container, dx, dy, score))261 debugCandidates = append(debugCandidates, fmt.Sprintf("cand=%p dx=%.2f dy=%.2f score=%.2f", candidate.container, dx, dy, score))
262 }262 }
263 263
@@ -287,29 +287,29 @@ func (wm *WorkspaceManager) structuralNeighbor(node *paneNode, direction string)
287 refX, refY, refWidth, refHeight := webkit.WidgetGetAllocation(node.container)287 refX, refY, refWidth, refHeight := webkit.WidgetGetAllocation(node.container)
288 cx := float64(refX) + float64(refWidth)/2.0288 cx := float64(refX) + float64(refWidth)/2.0
289 cy := float64(refY) + float64(refHeight)/2.0289 cy := float64(refY) + float64(refHeight)/2.0
290- axisVertical := direction == "up" || direction == "down"290+ axisVertical := direction == DirectionUp || direction == DirectionDown
291 291
292 for parent := node.parent; parent != nil; parent = parent.parent {292 for parent := node.parent; parent != nil; parent = parent.parent {
293 switch direction {293 switch direction {
294- case "up":294+ case DirectionUp:
295 if axisVertical && parent.orientation == gtk.OrientationVertical && parent.right == node {295 if axisVertical && parent.orientation == gtk.OrientationVertical && parent.right == node {
296 if leaf := wm.closestLeafFromSubtree(parent.left, cx, cy, direction); leaf != nil {296 if leaf := wm.closestLeafFromSubtree(parent.left, cx, cy, direction); leaf != nil {
297 return leaf297 return leaf
298 }298 }
299 }299 }
300- case "down":300+ case DirectionDown:
301 if axisVertical && parent.orientation == gtk.OrientationVertical && parent.left == node {301 if axisVertical && parent.orientation == gtk.OrientationVertical && parent.left == node {
302 if leaf := wm.closestLeafFromSubtree(parent.right, cx, cy, direction); leaf != nil {302 if leaf := wm.closestLeafFromSubtree(parent.right, cx, cy, direction); leaf != nil {
303 return leaf303 return leaf
304 }304 }
305 }305 }
306- case "left":306+ case DirectionLeft:
307 if !axisVertical && parent.orientation == gtk.OrientationHorizontal && parent.right == node {307 if !axisVertical && parent.orientation == gtk.OrientationHorizontal && parent.right == node {
308 if leaf := wm.closestLeafFromSubtree(parent.left, cx, cy, direction); leaf != nil {308 if leaf := wm.closestLeafFromSubtree(parent.left, cx, cy, direction); leaf != nil {
309 return leaf309 return leaf
310 }310 }
311 }311 }
312- case "right":312+ case DirectionRight:
313 if !axisVertical && parent.orientation == gtk.OrientationHorizontal && parent.left == node {313 if !axisVertical && parent.orientation == gtk.OrientationHorizontal && parent.left == node {
314 if leaf := wm.closestLeafFromSubtree(parent.right, cx, cy, direction); leaf != nil {314 if leaf := wm.closestLeafFromSubtree(parent.right, cx, cy, direction); leaf != nil {
315 return leaf315 return leaf
@@ -338,22 +338,22 @@ func (wm *WorkspaceManager) closestLeafFromSubtree(node *paneNode, cx, cy float6
338 dy := ty - cy338 dy := ty - cy
339 var score float64339 var score float64
340 switch direction {340 switch direction {
341- case "left":341+ case DirectionLeft:
342 if dx >= -focusEpsilon {342 if dx >= -focusEpsilon {
343 continue343 continue
344 }344 }
345 score = math.Abs(dx)*1000 + math.Abs(dy)345 score = math.Abs(dx)*1000 + math.Abs(dy)
346- case "right":346+ case DirectionRight:
347 if dx <= focusEpsilon {347 if dx <= focusEpsilon {
348 continue348 continue
349 }349 }
350 score = math.Abs(dx)*1000 + math.Abs(dy)350 score = math.Abs(dx)*1000 + math.Abs(dy)
351- case "up":351+ case DirectionUp:
352 if dy >= -focusEpsilon {352 if dy >= -focusEpsilon {
353 continue353 continue
354 }354 }
355 score = math.Abs(dy)*1000 + math.Abs(dx)355 score = math.Abs(dy)*1000 + math.Abs(dx)
356- case "down":356+ case DirectionDown:
357 if dy <= focusEpsilon {357 if dy <= focusEpsilon {
358 continue358 continue
359 }359 }
@@ -393,22 +393,22 @@ func (wm *WorkspaceManager) boundaryFallbackWithDepth(node *paneNode, direction
393 return node393 return node
394 }394 }
395 switch direction {395 switch direction {
396- case "up":396+ case DirectionUp:
397 if leaf := wm.boundaryFallbackWithDepth(node.right, direction, depth+1); leaf != nil {397 if leaf := wm.boundaryFallbackWithDepth(node.right, direction, depth+1); leaf != nil {
398 return leaf398 return leaf
399 }399 }
400 return wm.boundaryFallbackWithDepth(node.left, direction, depth+1)400 return wm.boundaryFallbackWithDepth(node.left, direction, depth+1)
401- case "down":401+ case DirectionDown:
402 if leaf := wm.boundaryFallbackWithDepth(node.left, direction, depth+1); leaf != nil {402 if leaf := wm.boundaryFallbackWithDepth(node.left, direction, depth+1); leaf != nil {
403 return leaf403 return leaf
404 }404 }
405 return wm.boundaryFallbackWithDepth(node.right, direction, depth+1)405 return wm.boundaryFallbackWithDepth(node.right, direction, depth+1)
406- case "left":406+ case DirectionLeft:
407 if leaf := wm.boundaryFallbackWithDepth(node.right, direction, depth+1); leaf != nil {407 if leaf := wm.boundaryFallbackWithDepth(node.right, direction, depth+1); leaf != nil {
408 return leaf408 return leaf
409 }409 }
410 return wm.boundaryFallbackWithDepth(node.left, direction, depth+1)410 return wm.boundaryFallbackWithDepth(node.left, direction, depth+1)
411- case "right":411+ case DirectionRight:
412 if leaf := wm.boundaryFallbackWithDepth(node.left, direction, depth+1); leaf != nil {412 if leaf := wm.boundaryFallbackWithDepth(node.left, direction, depth+1); leaf != nil {
413 return leaf413 return leaf
414 }414 }
M internal/app/browser/workspace_navigation_test.go +8 -8
@@ -77,10 +77,10 @@ func TestMapDirection(t *testing.T) {
7777 orientation gtk.Orientation
7878 existingFirst bool
7979 }{
80- {"down", gtk.OrientationVertical, true},
81- {"up", gtk.OrientationVertical, false},
82- {"right", gtk.OrientationHorizontal, true},
83- {"left", gtk.OrientationHorizontal, false},
80+ {DirectionDown, gtk.OrientationVertical, true},
81+ {DirectionUp, gtk.OrientationVertical, false},
82+ {DirectionRight, gtk.OrientationHorizontal, true},
83+ {DirectionLeft, gtk.OrientationHorizontal, false},
8484 }
8585
8686 for _, tt := range tests {
@@ -131,10 +131,10 @@ func TestCaseSensitiveDirections(t *testing.T) {
131131 "Down",
132132 "LEFT",
133133 "Right",
134- "up",
135- "down",
136- "left",
137- "right",
134+ DirectionUp,
135+ DirectionDown,
136+ DirectionLeft,
137+ DirectionRight,
138138 }
139139
140140 for _, direction := range validDirections {
@@ -77,10 +77,10 @@ func TestMapDirection(t *testing.T) {
77 orientation gtk.Orientation77 orientation gtk.Orientation
78 existingFirst bool78 existingFirst bool
79 }{79 }{
80- {"down", gtk.OrientationVertical, true},80+ {DirectionDown, gtk.OrientationVertical, true},
81- {"up", gtk.OrientationVertical, false},81+ {DirectionUp, gtk.OrientationVertical, false},
82- {"right", gtk.OrientationHorizontal, true},82+ {DirectionRight, gtk.OrientationHorizontal, true},
83- {"left", gtk.OrientationHorizontal, false},83+ {DirectionLeft, gtk.OrientationHorizontal, false},
84 }84 }
85 85
86 for _, tt := range tests {86 for _, tt := range tests {
@@ -131,10 +131,10 @@ func TestCaseSensitiveDirections(t *testing.T) {
131 "Down",131 "Down",
132 "LEFT",132 "LEFT",
133 "Right",133 "Right",
134- "up",134+ DirectionUp,
135- "down",135+ DirectionDown,
136- "left",136+ DirectionLeft,
137- "right",137+ DirectionRight,
138 }138 }
139 139
140 for _, direction := range validDirections {140 for _, direction := range validDirections {
M internal/app/browser/workspace_operations.go +4 -4
@@ -16,10 +16,10 @@ func (wm *WorkspaceManager) SplitPane(target *paneNode, direction string) (*pane
1616
1717 // Validate direction parameter
1818 validDirections := map[string]bool{
19- "left": true,
20- "right": true,
21- "up": true,
22- "down": true,
19+ DirectionLeft: true,
20+ DirectionRight: true,
21+ DirectionUp: true,
22+ DirectionDown: true,
2323 }
2424 if !validDirections[direction] {
2525 return nil, fmt.Errorf("invalid split direction '%s', expected one of: left, right, up, down", direction)
@@ -16,10 +16,10 @@ func (wm *WorkspaceManager) SplitPane(target *paneNode, direction string) (*pane
16 16
17 // Validate direction parameter17 // Validate direction parameter
18 validDirections := map[string]bool{18 validDirections := map[string]bool{
19- "left": true,19+ DirectionLeft: true,
20- "right": true,20+ DirectionRight: true,
21- "up": true,21+ DirectionUp: true,
22- "down": true,22+ DirectionDown: true,
23 }23 }
24 if !validDirections[direction] {24 if !validDirections[direction] {
25 return nil, fmt.Errorf("invalid split direction '%s', expected one of: left, right, up, down", direction)25 return nil, fmt.Errorf("invalid split direction '%s', expected one of: left, right, up, down", direction)
M internal/app/browser/workspace_pane_mode.go +5 -5
@@ -124,17 +124,17 @@ func (wm *WorkspaceManager) handlePaneSplit(node *paneNode, action string) {
124124 return
125125 }
126126
127- // Extract direction from action (e.g., "split-right" -> "right")
127+ // Extract direction from action (e.g., "split-right" -> DirectionRight)
128128 var direction string
129129 switch action {
130130 case "split-right":
131- direction = "right"
131+ direction = DirectionRight
132132 case "split-left":
133- direction = "left"
133+ direction = DirectionLeft
134134 case "split-up":
135- direction = "up"
135+ direction = DirectionUp
136136 case "split-down":
137- direction = "down"
137+ direction = DirectionDown
138138 default:
139139 log.Printf("[pane-mode] Invalid split action: %s", action)
140140 return
@@ -124,17 +124,17 @@ func (wm *WorkspaceManager) handlePaneSplit(node *paneNode, action string) {
124 return124 return
125 }125 }
126 126
127- // Extract direction from action (e.g., "split-right" -> "right")127+ // Extract direction from action (e.g., "split-right" -> DirectionRight)
128 var direction string128 var direction string
129 switch action {129 switch action {
130 case "split-right":130 case "split-right":
131- direction = "right"131+ direction = DirectionRight
132 case "split-left":132 case "split-left":
133- direction = "left"133+ direction = DirectionLeft
134 case "split-up":134 case "split-up":
135- direction = "up"135+ direction = DirectionUp
136 case "split-down":136 case "split-down":
137- direction = "down"137+ direction = DirectionDown
138 default:138 default:
139 log.Printf("[pane-mode] Invalid split action: %s", action)139 log.Printf("[pane-mode] Invalid split action: %s", action)
140 return140 return
M internal/app/browser/workspace_popup.go +7 -7
@@ -37,7 +37,7 @@ func (wm *WorkspaceManager) insertIndependentPane(sourceNode *paneNode, webView
3737 }
3838 direction := strings.ToLower(wm.app.config.Workspace.Popups.Placement)
3939 if direction == "" {
40- direction = "right"
40+ direction = DirectionRight
4141 }
4242 // Use splitNode with existing pane instead of insertPopupPane
4343 if _, err := wm.splitNode(sourceNode, direction, newPane); err != nil {
@@ -65,7 +65,7 @@ func (wm *WorkspaceManager) configureRelatedPopup(sourceNode *paneNode, webView
6565 }
6666 direction := strings.ToLower(wm.app.config.Workspace.Popups.Placement)
6767 if direction == "" {
68- direction = "right"
68+ direction = DirectionRight
6969 }
7070 // Use splitNode with existing pane instead of insertPopupPane
7171 if _, err := wm.splitNode(sourceNode, direction, newPane); err != nil {
@@ -427,7 +427,7 @@ func (wm *WorkspaceManager) handlePopupReadyToShow(popupID uint64) {
427427 // Split pane behavior (default) - use splitNode with existing popup pane
428428 direction := strings.ToLower(wm.app.config.Workspace.Popups.Placement)
429429 if direction == "" {
430- direction = "right"
430+ direction = DirectionRight
431431 }
432432 if _, err := wm.splitNode(info.parentNode, direction, popupPane); err != nil {
433433 log.Printf("[workspace] Failed to insert split popup pane: %v", err)
@@ -444,7 +444,7 @@ func (wm *WorkspaceManager) handlePopupReadyToShow(popupID uint64) {
444444 // Insert the popup pane in the same container - use splitNode with existing popup pane
445445 direction := strings.ToLower(wm.app.config.Workspace.Popups.Placement)
446446 if direction == "" {
447- direction = "right"
447+ direction = DirectionRight
448448 }
449449 if _, err := wm.splitNode(info.parentNode, direction, popupPane); err != nil {
450450 log.Printf("[workspace] Failed to insert stacked popup pane: %v", err)
@@ -456,7 +456,7 @@ func (wm *WorkspaceManager) handlePopupReadyToShow(popupID uint64) {
456456 log.Printf("[workspace] WARNING: Tabbed popup behavior not yet implemented, falling back to split")
457457 direction := strings.ToLower(wm.app.config.Workspace.Popups.Placement)
458458 if direction == "" {
459- direction = "right"
459+ direction = DirectionRight
460460 }
461461 if _, err := wm.splitNode(info.parentNode, direction, popupPane); err != nil {
462462 log.Printf("[workspace] Failed to insert tabbed popup pane: %v", err)
@@ -469,7 +469,7 @@ func (wm *WorkspaceManager) handlePopupReadyToShow(popupID uint64) {
469469 log.Printf("[workspace] WARNING: Windowed popup behavior not yet implemented, falling back to split")
470470 direction := strings.ToLower(wm.app.config.Workspace.Popups.Placement)
471471 if direction == "" {
472- direction = "right"
472+ direction = DirectionRight
473473 }
474474 if _, err := wm.splitNode(info.parentNode, direction, popupPane); err != nil {
475475 log.Printf("[workspace] Failed to insert windowed popup pane: %v", err)
@@ -481,7 +481,7 @@ func (wm *WorkspaceManager) handlePopupReadyToShow(popupID uint64) {
481481 log.Printf("[workspace] Unknown popup behavior '%s', falling back to split", behavior)
482482 direction := strings.ToLower(wm.app.config.Workspace.Popups.Placement)
483483 if direction == "" {
484- direction = "right"
484+ direction = DirectionRight
485485 }
486486 if _, err := wm.splitNode(info.parentNode, direction, popupPane); err != nil {
487487 log.Printf("[workspace] Failed to insert popup pane: %v", err)
@@ -37,7 +37,7 @@ func (wm *WorkspaceManager) insertIndependentPane(sourceNode *paneNode, webView
37 }37 }
38 direction := strings.ToLower(wm.app.config.Workspace.Popups.Placement)38 direction := strings.ToLower(wm.app.config.Workspace.Popups.Placement)
39 if direction == "" {39 if direction == "" {
40- direction = "right"40+ direction = DirectionRight
41 }41 }
42 // Use splitNode with existing pane instead of insertPopupPane42 // Use splitNode with existing pane instead of insertPopupPane
43 if _, err := wm.splitNode(sourceNode, direction, newPane); err != nil {43 if _, err := wm.splitNode(sourceNode, direction, newPane); err != nil {
@@ -65,7 +65,7 @@ func (wm *WorkspaceManager) configureRelatedPopup(sourceNode *paneNode, webView
65 }65 }
66 direction := strings.ToLower(wm.app.config.Workspace.Popups.Placement)66 direction := strings.ToLower(wm.app.config.Workspace.Popups.Placement)
67 if direction == "" {67 if direction == "" {
68- direction = "right"68+ direction = DirectionRight
69 }69 }
70 // Use splitNode with existing pane instead of insertPopupPane70 // Use splitNode with existing pane instead of insertPopupPane
71 if _, err := wm.splitNode(sourceNode, direction, newPane); err != nil {71 if _, err := wm.splitNode(sourceNode, direction, newPane); err != nil {
@@ -427,7 +427,7 @@ func (wm *WorkspaceManager) handlePopupReadyToShow(popupID uint64) {
427 // Split pane behavior (default) - use splitNode with existing popup pane427 // Split pane behavior (default) - use splitNode with existing popup pane
428 direction := strings.ToLower(wm.app.config.Workspace.Popups.Placement)428 direction := strings.ToLower(wm.app.config.Workspace.Popups.Placement)
429 if direction == "" {429 if direction == "" {
430- direction = "right"430+ direction = DirectionRight
431 }431 }
432 if _, err := wm.splitNode(info.parentNode, direction, popupPane); err != nil {432 if _, err := wm.splitNode(info.parentNode, direction, popupPane); err != nil {
433 log.Printf("[workspace] Failed to insert split popup pane: %v", err)433 log.Printf("[workspace] Failed to insert split popup pane: %v", err)
@@ -444,7 +444,7 @@ func (wm *WorkspaceManager) handlePopupReadyToShow(popupID uint64) {
444 // Insert the popup pane in the same container - use splitNode with existing popup pane444 // Insert the popup pane in the same container - use splitNode with existing popup pane
445 direction := strings.ToLower(wm.app.config.Workspace.Popups.Placement)445 direction := strings.ToLower(wm.app.config.Workspace.Popups.Placement)
446 if direction == "" {446 if direction == "" {
447- direction = "right"447+ direction = DirectionRight
448 }448 }
449 if _, err := wm.splitNode(info.parentNode, direction, popupPane); err != nil {449 if _, err := wm.splitNode(info.parentNode, direction, popupPane); err != nil {
450 log.Printf("[workspace] Failed to insert stacked popup pane: %v", err)450 log.Printf("[workspace] Failed to insert stacked popup pane: %v", err)
@@ -456,7 +456,7 @@ func (wm *WorkspaceManager) handlePopupReadyToShow(popupID uint64) {
456 log.Printf("[workspace] WARNING: Tabbed popup behavior not yet implemented, falling back to split")456 log.Printf("[workspace] WARNING: Tabbed popup behavior not yet implemented, falling back to split")
457 direction := strings.ToLower(wm.app.config.Workspace.Popups.Placement)457 direction := strings.ToLower(wm.app.config.Workspace.Popups.Placement)
458 if direction == "" {458 if direction == "" {
459- direction = "right"459+ direction = DirectionRight
460 }460 }
461 if _, err := wm.splitNode(info.parentNode, direction, popupPane); err != nil {461 if _, err := wm.splitNode(info.parentNode, direction, popupPane); err != nil {
462 log.Printf("[workspace] Failed to insert tabbed popup pane: %v", err)462 log.Printf("[workspace] Failed to insert tabbed popup pane: %v", err)
@@ -469,7 +469,7 @@ func (wm *WorkspaceManager) handlePopupReadyToShow(popupID uint64) {
469 log.Printf("[workspace] WARNING: Windowed popup behavior not yet implemented, falling back to split")469 log.Printf("[workspace] WARNING: Windowed popup behavior not yet implemented, falling back to split")
470 direction := strings.ToLower(wm.app.config.Workspace.Popups.Placement)470 direction := strings.ToLower(wm.app.config.Workspace.Popups.Placement)
471 if direction == "" {471 if direction == "" {
472- direction = "right"472+ direction = DirectionRight
473 }473 }
474 if _, err := wm.splitNode(info.parentNode, direction, popupPane); err != nil {474 if _, err := wm.splitNode(info.parentNode, direction, popupPane); err != nil {
475 log.Printf("[workspace] Failed to insert windowed popup pane: %v", err)475 log.Printf("[workspace] Failed to insert windowed popup pane: %v", err)
@@ -481,7 +481,7 @@ func (wm *WorkspaceManager) handlePopupReadyToShow(popupID uint64) {
481 log.Printf("[workspace] Unknown popup behavior '%s', falling back to split", behavior)481 log.Printf("[workspace] Unknown popup behavior '%s', falling back to split", behavior)
482 direction := strings.ToLower(wm.app.config.Workspace.Popups.Placement)482 direction := strings.ToLower(wm.app.config.Workspace.Popups.Placement)
483 if direction == "" {483 if direction == "" {
484- direction = "right"484+ direction = DirectionRight
485 }485 }
486 if _, err := wm.splitNode(info.parentNode, direction, popupPane); err != nil {486 if _, err := wm.splitNode(info.parentNode, direction, popupPane); err != nil {
487 log.Printf("[workspace] Failed to insert popup pane: %v", err)487 log.Printf("[workspace] Failed to insert popup pane: %v", err)
M internal/app/browser/workspace_utils.go +3 -3
@@ -35,11 +35,11 @@ func (wm *WorkspaceManager) ensureGUIInPane(pane *BrowserPane) {
3535 // mapDirection maps a direction string to GTK orientation and positioning
3636 func mapDirection(direction string) (gtk.Orientation, bool) {
3737 switch direction {
38- case "left":
38+ case DirectionLeft:
3939 return gtk.OrientationHorizontal, false
40- case "up":
40+ case DirectionUp:
4141 return gtk.OrientationVertical, false
42- case "down":
42+ case DirectionDown:
4343 return gtk.OrientationVertical, true
4444 default:
4545 return gtk.OrientationHorizontal, true
@@ -35,11 +35,11 @@ func (wm *WorkspaceManager) ensureGUIInPane(pane *BrowserPane) {
35 // mapDirection maps a direction string to GTK orientation and positioning35 // mapDirection maps a direction string to GTK orientation and positioning
36 func mapDirection(direction string) (gtk.Orientation, bool) {36 func mapDirection(direction string) (gtk.Orientation, bool) {
37 switch direction {37 switch direction {
38- case "left":38+ case DirectionLeft:
39 return gtk.OrientationHorizontal, false39 return gtk.OrientationHorizontal, false
40- case "up":40+ case DirectionUp:
41 return gtk.OrientationVertical, false41 return gtk.OrientationVertical, false
42- case "down":42+ case DirectionDown:
43 return gtk.OrientationVertical, true43 return gtk.OrientationVertical, true
44 default:44 default:
45 return gtk.OrientationHorizontal, true45 return gtk.OrientationHorizontal, true
M internal/app/control/navigation.go +6 -2
@@ -55,7 +55,9 @@ func (n *NavigationController) NavigateToURL(input string) error {
5555 haveZoom = true
5656 if n.webView != nil {
5757 if n.webView.UsesDomZoom() {
58- n.webView.SeedDomZoom(zoomLevel)
58+ if err := n.webView.SeedDomZoom(zoomLevel); err != nil {
59+ log.Printf("Warning: failed to seed DOM zoom for %s: %v", result.URL, err)
60+ }
5961 } else {
6062 n.webView.RunOnMainThread(func() {
6163 if err := n.webView.SetZoom(zoomLevel); err != nil {
@@ -108,7 +110,9 @@ func (n *NavigationController) HandleBrowseCommand() {
108110 zoomLevel = level
109111 haveZoom = true
110112 if n.webView.UsesDomZoom() {
111- n.webView.SeedDomZoom(zoomLevel)
113+ if err := n.webView.SeedDomZoom(zoomLevel); err != nil {
114+ log.Printf("Warning: failed to seed DOM zoom for %s: %v", result.URL, err)
115+ }
112116 } else {
113117 n.webView.RunOnMainThread(func() {
114118 if err := n.webView.SetZoom(zoomLevel); err != nil {
@@ -55,7 +55,9 @@ func (n *NavigationController) NavigateToURL(input string) error {
55 haveZoom = true55 haveZoom = true
56 if n.webView != nil {56 if n.webView != nil {
57 if n.webView.UsesDomZoom() {57 if n.webView.UsesDomZoom() {
58- n.webView.SeedDomZoom(zoomLevel)58+ if err := n.webView.SeedDomZoom(zoomLevel); err != nil {
59+ log.Printf("Warning: failed to seed DOM zoom for %s: %v", result.URL, err)
60+ }
59 } else {61 } else {
60 n.webView.RunOnMainThread(func() {62 n.webView.RunOnMainThread(func() {
61 if err := n.webView.SetZoom(zoomLevel); err != nil {63 if err := n.webView.SetZoom(zoomLevel); err != nil {
@@ -108,7 +110,9 @@ func (n *NavigationController) HandleBrowseCommand() {
108 zoomLevel = level110 zoomLevel = level
109 haveZoom = true111 haveZoom = true
110 if n.webView.UsesDomZoom() {112 if n.webView.UsesDomZoom() {
111- n.webView.SeedDomZoom(zoomLevel)113+ if err := n.webView.SeedDomZoom(zoomLevel); err != nil {
114+ log.Printf("Warning: failed to seed DOM zoom for %s: %v", result.URL, err)
115+ }
112 } else {116 } else {
113 n.webView.RunOnMainThread(func() {117 n.webView.RunOnMainThread(func() {
114 if err := n.webView.SetZoom(zoomLevel); err != nil {118 if err := n.webView.SetZoom(zoomLevel); err != nil {

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