detached 68837f5

fix: promote remaining browser window on close

commit
68837f5f00be6598114310febc874a074fe5a04e
author
brice · · 2026-04-14 15:37 UTC
parent
7109c5e
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +51 -0
M internal/ui/browser_window.go +8 -0
@@ -27,7 +27,15 @@ func (a *App) removeBrowserWindow(id string) {
2727 return
2828 }
2929 if bw := a.browserWindows[id]; bw != nil && bw.mainWindow == a.mainWindow {
30+ delete(a.browserWindows, id)
31+ for _, remaining := range a.browserWindows {
32+ if remaining != nil && remaining.mainWindow != nil {
33+ a.mainWindow = remaining.mainWindow
34+ return
35+ }
36+ }
3037 a.mainWindow = nil
38+ return
3139 }
3240 delete(a.browserWindows, id)
3341 }
@@ -27,7 +27,15 @@ func (a *App) removeBrowserWindow(id string) {
27 return27 return
28 }28 }
29 if bw := a.browserWindows[id]; bw != nil && bw.mainWindow == a.mainWindow {29 if bw := a.browserWindows[id]; bw != nil && bw.mainWindow == a.mainWindow {
30+ delete(a.browserWindows, id)
31+ for _, remaining := range a.browserWindows {
32+ if remaining != nil && remaining.mainWindow != nil {
33+ a.mainWindow = remaining.mainWindow
34+ return
35+ }
36+ }
30 a.mainWindow = nil37 a.mainWindow = nil
38+ return
31 }39 }
32 delete(a.browserWindows, id)40 delete(a.browserWindows, id)
33 }41 }
M internal/ui/browser_window_test.go +43 -0
@@ -3,6 +3,8 @@ package ui
33 import (
44 "context"
55 "testing"
6+
7+ "github.com/bnema/dumber/internal/ui/window"
68 )
79
810 func TestBrowserWindow_RegisterAndRemoveTrackCollection(t *testing.T) {
@@ -33,6 +35,47 @@ func TestBrowserWindow_RegisterAndRemoveTrackCollection(t *testing.T) {
3335 }
3436 }
3537
38+func TestBrowserWindow_RemovePromotesRemainingMainWindow(t *testing.T) {
39+ mainWindow := &window.MainWindow{}
40+ otherWindow := &window.MainWindow{}
41+ first := &browserWindow{id: "window-1", mainWindow: mainWindow}
42+ second := &browserWindow{id: "window-2", mainWindow: otherWindow}
43+ app := &App{
44+ mainWindow: mainWindow,
45+ browserWindows: map[string]*browserWindow{first.id: first, second.id: second},
46+ }
47+
48+ app.removeBrowserWindow(first.id)
49+
50+ if app.mainWindow != otherWindow {
51+ t.Fatalf("mainWindow = %p, want promoted window %p", app.mainWindow, otherWindow)
52+ }
53+ if len(app.browserWindows) != 1 {
54+ t.Fatalf("browserWindows length = %d, want 1", len(app.browserWindows))
55+ }
56+ if _, ok := app.browserWindows[second.id]; !ok {
57+ t.Fatalf("remaining browser window was not kept")
58+ }
59+}
60+
61+func TestBrowserWindow_RemoveLastClearsMainWindow(t *testing.T) {
62+ mainWindow := &window.MainWindow{}
63+ first := &browserWindow{id: "window-1", mainWindow: mainWindow}
64+ app := &App{
65+ mainWindow: mainWindow,
66+ browserWindows: map[string]*browserWindow{first.id: first},
67+ }
68+
69+ app.removeBrowserWindow(first.id)
70+
71+ if app.mainWindow != nil {
72+ t.Fatalf("mainWindow = %p, want nil", app.mainWindow)
73+ }
74+ if len(app.browserWindows) != 0 {
75+ t.Fatalf("browserWindows length = %d, want 0", len(app.browserWindows))
76+ }
77+}
78+
3679 func TestOpenFreshWindow_DispatchesAndTracksBrowserWindow(t *testing.T) {
3780 app := &App{browserWindows: make(map[string]*browserWindow)}
3881
@@ -3,6 +3,8 @@ package ui
3 import (3 import (
4 "context"4 "context"
5 "testing"5 "testing"
6+
7+ "github.com/bnema/dumber/internal/ui/window"
6 )8 )
7 9
8 func TestBrowserWindow_RegisterAndRemoveTrackCollection(t *testing.T) {10 func TestBrowserWindow_RegisterAndRemoveTrackCollection(t *testing.T) {
@@ -33,6 +35,47 @@ func TestBrowserWindow_RegisterAndRemoveTrackCollection(t *testing.T) {
33 }35 }
34 }36 }
35 37
38+func TestBrowserWindow_RemovePromotesRemainingMainWindow(t *testing.T) {
39+ mainWindow := &window.MainWindow{}
40+ otherWindow := &window.MainWindow{}
41+ first := &browserWindow{id: "window-1", mainWindow: mainWindow}
42+ second := &browserWindow{id: "window-2", mainWindow: otherWindow}
43+ app := &App{
44+ mainWindow: mainWindow,
45+ browserWindows: map[string]*browserWindow{first.id: first, second.id: second},
46+ }
47+
48+ app.removeBrowserWindow(first.id)
49+
50+ if app.mainWindow != otherWindow {
51+ t.Fatalf("mainWindow = %p, want promoted window %p", app.mainWindow, otherWindow)
52+ }
53+ if len(app.browserWindows) != 1 {
54+ t.Fatalf("browserWindows length = %d, want 1", len(app.browserWindows))
55+ }
56+ if _, ok := app.browserWindows[second.id]; !ok {
57+ t.Fatalf("remaining browser window was not kept")
58+ }
59+}
60+
61+func TestBrowserWindow_RemoveLastClearsMainWindow(t *testing.T) {
62+ mainWindow := &window.MainWindow{}
63+ first := &browserWindow{id: "window-1", mainWindow: mainWindow}
64+ app := &App{
65+ mainWindow: mainWindow,
66+ browserWindows: map[string]*browserWindow{first.id: first},
67+ }
68+
69+ app.removeBrowserWindow(first.id)
70+
71+ if app.mainWindow != nil {
72+ t.Fatalf("mainWindow = %p, want nil", app.mainWindow)
73+ }
74+ if len(app.browserWindows) != 0 {
75+ t.Fatalf("browserWindows length = %d, want 0", len(app.browserWindows))
76+ }
77+}
78+
36 func TestOpenFreshWindow_DispatchesAndTracksBrowserWindow(t *testing.T) {79 func TestOpenFreshWindow_DispatchesAndTracksBrowserWindow(t *testing.T) {
37 app := &App{browserWindows: make(map[string]*browserWindow)}80 app := &App{browserWindows: make(map[string]*browserWindow)}
38 81

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