detached ff7151a

Merge pull request #278 from bnema/chore/diagnostic-window-launch-logs

chore(logging): add window launch diagnostics
commit
ff7151a50eb3082f5786dacd76401820476d3ba3
author
bnema · · 2026-06-09 04:47 UTC
committer
GitHub · · 2026-06-09 04:47 UTC
merge of
9d58593 + 845a627 · diff against the first parent
signature
GPG UnverifiedB5690EEEBB952194 The key is unknown
5 files · +160 -10
M internal/infrastructure/desktop/browser_launch_relay.go +6 -3
@@ -315,21 +315,24 @@ func (*browserLaunchRelayListener) handleConnection(ctx context.Context, conn *n
315315 Msg("browser launch relay accepted request without opener")
316316 return
317317 }
318+ started := time.Now()
318319 log.Debug().
319320 Str("request_id", requestID).
320321 Str("url_host", safeURLHost(request.URL)).
321- Msg("browser launch relay opening fresh window")
322+ Msg("browser launch relay calling browser window opener")
322323 if err := opener.OpenFreshWindow(ctx, request.URL); err != nil {
323324 log.Warn().Err(err).
324325 Str("request_id", requestID).
325326 Str("url_host", safeURLHost(request.URL)).
326- Msg("failed to open fresh browser window")
327+ Dur("elapsed", time.Since(started)).
328+ Msg("browser launch relay opener failed")
327329 return
328330 }
329331 log.Debug().
330332 Str("request_id", requestID).
331333 Str("url_host", safeURLHost(request.URL)).
332- Msg("browser launch relay opened fresh window")
334+ Dur("elapsed", time.Since(started)).
335+ Msg("browser launch relay opener returned success")
333336 }()
334337 }
335338
@@ -315,21 +315,24 @@ func (*browserLaunchRelayListener) handleConnection(ctx context.Context, conn *n
315 Msg("browser launch relay accepted request without opener")315 Msg("browser launch relay accepted request without opener")
316 return316 return
317 }317 }
318+ started := time.Now()
318 log.Debug().319 log.Debug().
319 Str("request_id", requestID).320 Str("request_id", requestID).
320 Str("url_host", safeURLHost(request.URL)).321 Str("url_host", safeURLHost(request.URL)).
321- Msg("browser launch relay opening fresh window")322+ Msg("browser launch relay calling browser window opener")
322 if err := opener.OpenFreshWindow(ctx, request.URL); err != nil {323 if err := opener.OpenFreshWindow(ctx, request.URL); err != nil {
323 log.Warn().Err(err).324 log.Warn().Err(err).
324 Str("request_id", requestID).325 Str("request_id", requestID).
325 Str("url_host", safeURLHost(request.URL)).326 Str("url_host", safeURLHost(request.URL)).
326- Msg("failed to open fresh browser window")327+ Dur("elapsed", time.Since(started)).
328+ Msg("browser launch relay opener failed")
327 return329 return
328 }330 }
329 log.Debug().331 log.Debug().
330 Str("request_id", requestID).332 Str("request_id", requestID).
331 Str("url_host", safeURLHost(request.URL)).333 Str("url_host", safeURLHost(request.URL)).
332- Msg("browser launch relay opened fresh window")334+ Dur("elapsed", time.Since(started)).
335+ Msg("browser launch relay opener returned success")
333 }()336 }()
334 }337 }
335 338
M internal/logging/logger.go +10 -0
@@ -3,6 +3,7 @@ package logging
33 import (
44 "errors"
55 "io"
6+ neturl "net/url"
67 "os"
78 "path/filepath"
89 "sync"
@@ -248,3 +249,12 @@ func TruncateURL(url string, maxLen int) string {
248249 }
249250 return url[:maxLen] + "..."
250251 }
252+
253+// SafeURLHost returns only the host portion of a URL for privacy-preserving diagnostics.
254+func SafeURLHost(raw string) string {
255+ parsed, err := neturl.Parse(raw)
256+ if err != nil {
257+ return ""
258+ }
259+ return parsed.Host
260+}
@@ -3,6 +3,7 @@ package logging
3 import (3 import (
4 "errors"4 "errors"
5 "io"5 "io"
6+ neturl "net/url"
6 "os"7 "os"
7 "path/filepath"8 "path/filepath"
8 "sync"9 "sync"
@@ -248,3 +249,12 @@ func TruncateURL(url string, maxLen int) string {
248 }249 }
249 return url[:maxLen] + "..."250 return url[:maxLen] + "..."
250 }251 }
252+
253+// SafeURLHost returns only the host portion of a URL for privacy-preserving diagnostics.
254+func SafeURLHost(raw string) string {
255+ parsed, err := neturl.Parse(raw)
256+ if err != nil {
257+ return ""
258+ }
259+ return parsed.Host
260+}
M internal/logging/logger_test.go +19 -0
@@ -6,6 +6,25 @@ import (
66 "testing"
77 )
88
9+func TestSafeURLHostDropsPathQueryAndFragment(t *testing.T) {
10+ t.Parallel()
11+
12+ got := SafeURLHost("https://example.com/private/path?token=secret#frag")
13+ if got != "example.com" {
14+ t.Fatalf("SafeURLHost returned %q, want %q", got, "example.com")
15+ }
16+}
17+
18+func TestSafeURLHostReturnsEmptyForOpaqueOrInvalidInput(t *testing.T) {
19+ t.Parallel()
20+
21+ for _, raw := range []string{"about:blank", "not a url"} {
22+ if got := SafeURLHost(raw); got != "" {
23+ t.Fatalf("SafeURLHost(%q) returned %q, want empty host", raw, got)
24+ }
25+ }
26+}
27+
928 func TestSessionFileWriter_DropsWritesAfterClose(t *testing.T) {
1029 t.Parallel()
1130
@@ -6,6 +6,25 @@ import (
6 "testing"6 "testing"
7 )7 )
8 8
9+func TestSafeURLHostDropsPathQueryAndFragment(t *testing.T) {
10+ t.Parallel()
11+
12+ got := SafeURLHost("https://example.com/private/path?token=secret#frag")
13+ if got != "example.com" {
14+ t.Fatalf("SafeURLHost returned %q, want %q", got, "example.com")
15+ }
16+}
17+
18+func TestSafeURLHostReturnsEmptyForOpaqueOrInvalidInput(t *testing.T) {
19+ t.Parallel()
20+
21+ for _, raw := range []string{"about:blank", "not a url"} {
22+ if got := SafeURLHost(raw); got != "" {
23+ t.Fatalf("SafeURLHost(%q) returned %q, want empty host", raw, got)
24+ }
25+ }
26+}
27+
9 func TestSessionFileWriter_DropsWritesAfterClose(t *testing.T) {28 func TestSessionFileWriter_DropsWritesAfterClose(t *testing.T) {
10 t.Parallel()29 t.Parallel()
11 30
M internal/ui/app.go +87 -3
@@ -422,9 +422,19 @@ func (a *App) prewarmWebViewPoolAsync(ctx context.Context) {
422422 }
423423
424424 func (a *App) createBrowserWindow(ctx context.Context, initialURL string) (*browserWindow, error) {
425+ log := logging.FromContext(ctx)
426+ log.Debug().
427+ Str("url_host", logging.SafeURLHost(initialURL)).
428+ Bool("using_factory", a.browserWindowFactory != nil).
429+ Int("window_count_before", len(a.browserWindows)).
430+ Msg("ui: create browser window started")
431+
425432 if a.browserWindowFactory != nil {
426433 created, err := a.browserWindowFactory(ctx, initialURL)
427434 if err != nil {
435+ log.Warn().Err(err).
436+ Str("url_host", logging.SafeURLHost(initialURL)).
437+ Msg("ui: browser window factory failed")
428438 return nil, err
429439 }
430440 created.ensureTabs()
@@ -433,9 +443,11 @@ func (a *App) createBrowserWindow(ctx context.Context, initialURL string) (*brow
433443 return created, nil
434444 }
435445
436- log := logging.FromContext(ctx)
437446 mainWindow, err := window.New(ctx, a.gtkApp, a.deps.Config.Workspace.TabBarPosition)
438447 if err != nil {
448+ log.Warn().Err(err).
449+ Str("url_host", logging.SafeURLHost(initialURL)).
450+ Msg("ui: GTK browser window shell creation failed")
439451 return nil, err
440452 }
441453 browserWindow := &browserWindow{
@@ -496,6 +508,10 @@ func (a *App) createBrowserWindow(ctx context.Context, initialURL string) (*brow
496508 if display := mainWindow.Window().GetDisplay(); display != nil {
497509 a.deps.Theme.ApplyToDisplay(ctx, display)
498510 }
511+ log.Debug().
512+ Str("window_id", browserWindow.id).
513+ Str("url_host", logging.SafeURLHost(initialURL)).
514+ Msg("ui: create browser window completed")
499515 return browserWindow, nil
500516 }
501517
@@ -767,49 +783,101 @@ func (a *App) closeBrowserLaunchRelayListener() {
767783 }
768784
769785 func (a *App) openFreshWindow(ctx context.Context, url string) error {
786+ log := logging.FromContext(ctx)
787+ log.Debug().
788+ Str("url_host", logging.SafeURLHost(url)).
789+ Int("window_count_before", len(a.browserWindows)).
790+ Bool("has_tab_coord", a.tabCoord != nil).
791+ Bool("has_tabs_uc", a.tabsUC != nil).
792+ Msg("ui: open fresh window started")
793+
770794 created, err := a.createBrowserWindow(ctx, url)
771795 if err != nil {
796+ log.Warn().Err(err).
797+ Str("url_host", logging.SafeURLHost(url)).
798+ Int("window_count_after", len(a.browserWindows)).
799+ Msg("ui: open fresh window could not create browser window")
772800 return err
773801 }
802+ log.Debug().
803+ Str("window_id", created.id).
804+ Str("url_host", logging.SafeURLHost(url)).
805+ Msg("ui: open fresh window created browser window shell")
774806 a.registerBrowserWindow(created)
807+ log.Debug().
808+ Str("window_id", created.id).
809+ Int("window_count_after_register", len(a.browserWindows)).
810+ Msg("ui: open fresh window registered browser window")
775811
776812 if len(a.browserWindows) == 1 {
813+ log.Debug().Str("window_id", created.id).Msg("ui: activating first browser window")
777814 a.activateBrowserWindow(created)
778815 return nil
779816 }
780817
781818 var openErr error
819+ path := "tabs_uc"
782820 if a.tabCoord != nil {
821+ path = "tab_coord"
822+ log.Debug().Str("window_id", created.id).Msg("ui: creating initial tab for fresh window via tab coordinator")
783823 openErr = a.openFreshWindowWithTabCoord(ctx, url, created)
784824 } else if a.tabsUC == nil {
825+ log.Debug().Str("window_id", created.id).Msg("ui: activating fresh browser window without tab use case")
785826 a.activateBrowserWindow(created)
786827 return nil
787828 } else {
829+ log.Debug().Str("window_id", created.id).Msg("ui: creating initial tab for fresh window via tabs use case")
788830 openErr = a.openFreshWindowWithTabsUC(ctx, url, created)
789831 }
790832 if openErr != nil {
833+ log.Warn().Err(openErr).
834+ Str("window_id", created.id).
835+ Str("path", path).
836+ Int("window_count_after", len(a.browserWindows)).
837+ Msg("ui: open fresh window initial tab creation failed")
791838 return openErr
792839 }
840+ log.Debug().
841+ Str("window_id", created.id).
842+ Str("path", path).
843+ Msg("ui: activating fresh browser window after tab creation")
793844 a.activateBrowserWindow(created)
794845 return nil
795846 }
796847
797848 func (a *App) openFreshWindowWithTabCoord(ctx context.Context, url string, created *browserWindow) error {
849+ log := logging.FromContext(ctx)
798850 previousTarget := a.tabCoord.CurrentTarget()
799- a.tabCoord.SetCurrentTarget(a.ensureTabTargetForBrowserWindow(created))
851+ target := a.ensureTabTargetForBrowserWindow(created)
852+ a.tabCoord.SetCurrentTarget(target)
800853 defer func() {
801854 a.tabCoord.SetCurrentTarget(previousTarget)
802855 }()
803856
804- createdTab, createErr := a.tabCoord.Create(ctx, a.ensureTabTargetForBrowserWindow(created), url)
857+ log.Debug().
858+ Str("window_id", created.id).
859+ Str("url_host", logging.SafeURLHost(url)).
860+ Msg("ui: tab coordinator create started for fresh window")
861+ createdTab, createErr := a.tabCoord.Create(ctx, target, url)
805862 if createErr != nil {
863+ log.Warn().Err(createErr).
864+ Str("window_id", created.id).
865+ Msg("ui: tab coordinator create failed for fresh window; rolling back window")
806866 a.removeBrowserWindow(created.id)
807867 if created.mainWindow != nil {
808868 created.mainWindow.Destroy()
809869 }
810870 return createErr
811871 }
872+ log.Debug().
873+ Str("window_id", created.id).
874+ Str("tab_id", string(createdTab.ID)).
875+ Msg("ui: tab coordinator create completed for fresh window")
812876 if a.workspaceViews[createdTab.ID] == nil {
877+ log.Warn().
878+ Str("window_id", created.id).
879+ Str("tab_id", string(createdTab.ID)).
880+ Msg("ui: fresh window tab has no workspace view; rolling back window")
813881 if createdTabs := a.tabListForBrowserWindow(created); createdTabs != nil {
814882 createdTabs.Remove(createdTab.ID)
815883 }
@@ -837,17 +905,29 @@ func (a *App) openFreshWindowWithTabCoord(ctx context.Context, url string, creat
837905 }
838906
839907 func (a *App) openFreshWindowWithTabsUC(ctx context.Context, url string, created *browserWindow) error {
908+ log := logging.FromContext(ctx)
909+ log.Debug().
910+ Str("window_id", created.id).
911+ Str("url_host", logging.SafeURLHost(url)).
912+ Msg("ui: tabs use case create started for fresh window")
840913 output, err := a.tabsUC.Create(ctx, usecase.CreateTabInput{
841914 TabList: a.tabs,
842915 InitialURL: url,
843916 })
844917 if err != nil {
918+ log.Warn().Err(err).
919+ Str("window_id", created.id).
920+ Msg("ui: tabs use case create failed for fresh window; rolling back window")
845921 a.removeBrowserWindow(created.id)
846922 if created.mainWindow != nil {
847923 created.mainWindow.Destroy()
848924 }
849925 return err
850926 }
927+ log.Debug().
928+ Str("window_id", created.id).
929+ Str("tab_id", string(output.Tab.ID)).
930+ Msg("ui: tabs use case create completed for fresh window")
851931 a.setBrowserWindowForTab(output.Tab.ID, created)
852932 // Mirror the tabsUC-created tab into the new browser window's tab list.
853933 // This path uses the tabs usecase directly instead of tabCoord.Create,
@@ -858,6 +938,10 @@ func (a *App) openFreshWindowWithTabsUC(ctx context.Context, url string, created
858938 a.switchWorkspaceView(ctx, output.Tab.ID)
859939 }
860940 if a.workspaceViews[output.Tab.ID] == nil {
941+ log.Warn().
942+ Str("window_id", created.id).
943+ Str("tab_id", string(output.Tab.ID)).
944+ Msg("ui: fresh window tabs use case tab has no workspace view; rolling back window")
861945 if createdTabs := a.tabListForBrowserWindow(created); createdTabs != nil {
862946 createdTabs.Remove(output.Tab.ID)
863947 }
@@ -422,9 +422,19 @@ func (a *App) prewarmWebViewPoolAsync(ctx context.Context) {
422 }422 }
423 423
424 func (a *App) createBrowserWindow(ctx context.Context, initialURL string) (*browserWindow, error) {424 func (a *App) createBrowserWindow(ctx context.Context, initialURL string) (*browserWindow, error) {
425+ log := logging.FromContext(ctx)
426+ log.Debug().
427+ Str("url_host", logging.SafeURLHost(initialURL)).
428+ Bool("using_factory", a.browserWindowFactory != nil).
429+ Int("window_count_before", len(a.browserWindows)).
430+ Msg("ui: create browser window started")
431+
425 if a.browserWindowFactory != nil {432 if a.browserWindowFactory != nil {
426 created, err := a.browserWindowFactory(ctx, initialURL)433 created, err := a.browserWindowFactory(ctx, initialURL)
427 if err != nil {434 if err != nil {
435+ log.Warn().Err(err).
436+ Str("url_host", logging.SafeURLHost(initialURL)).
437+ Msg("ui: browser window factory failed")
428 return nil, err438 return nil, err
429 }439 }
430 created.ensureTabs()440 created.ensureTabs()
@@ -433,9 +443,11 @@ func (a *App) createBrowserWindow(ctx context.Context, initialURL string) (*brow
433 return created, nil443 return created, nil
434 }444 }
435 445
436- log := logging.FromContext(ctx)
437 mainWindow, err := window.New(ctx, a.gtkApp, a.deps.Config.Workspace.TabBarPosition)446 mainWindow, err := window.New(ctx, a.gtkApp, a.deps.Config.Workspace.TabBarPosition)
438 if err != nil {447 if err != nil {
448+ log.Warn().Err(err).
449+ Str("url_host", logging.SafeURLHost(initialURL)).
450+ Msg("ui: GTK browser window shell creation failed")
439 return nil, err451 return nil, err
440 }452 }
441 browserWindow := &browserWindow{453 browserWindow := &browserWindow{
@@ -496,6 +508,10 @@ func (a *App) createBrowserWindow(ctx context.Context, initialURL string) (*brow
496 if display := mainWindow.Window().GetDisplay(); display != nil {508 if display := mainWindow.Window().GetDisplay(); display != nil {
497 a.deps.Theme.ApplyToDisplay(ctx, display)509 a.deps.Theme.ApplyToDisplay(ctx, display)
498 }510 }
511+ log.Debug().
512+ Str("window_id", browserWindow.id).
513+ Str("url_host", logging.SafeURLHost(initialURL)).
514+ Msg("ui: create browser window completed")
499 return browserWindow, nil515 return browserWindow, nil
500 }516 }
501 517
@@ -767,49 +783,101 @@ func (a *App) closeBrowserLaunchRelayListener() {
767 }783 }
768 784
769 func (a *App) openFreshWindow(ctx context.Context, url string) error {785 func (a *App) openFreshWindow(ctx context.Context, url string) error {
786+ log := logging.FromContext(ctx)
787+ log.Debug().
788+ Str("url_host", logging.SafeURLHost(url)).
789+ Int("window_count_before", len(a.browserWindows)).
790+ Bool("has_tab_coord", a.tabCoord != nil).
791+ Bool("has_tabs_uc", a.tabsUC != nil).
792+ Msg("ui: open fresh window started")
793+
770 created, err := a.createBrowserWindow(ctx, url)794 created, err := a.createBrowserWindow(ctx, url)
771 if err != nil {795 if err != nil {
796+ log.Warn().Err(err).
797+ Str("url_host", logging.SafeURLHost(url)).
798+ Int("window_count_after", len(a.browserWindows)).
799+ Msg("ui: open fresh window could not create browser window")
772 return err800 return err
773 }801 }
802+ log.Debug().
803+ Str("window_id", created.id).
804+ Str("url_host", logging.SafeURLHost(url)).
805+ Msg("ui: open fresh window created browser window shell")
774 a.registerBrowserWindow(created)806 a.registerBrowserWindow(created)
807+ log.Debug().
808+ Str("window_id", created.id).
809+ Int("window_count_after_register", len(a.browserWindows)).
810+ Msg("ui: open fresh window registered browser window")
775 811
776 if len(a.browserWindows) == 1 {812 if len(a.browserWindows) == 1 {
813+ log.Debug().Str("window_id", created.id).Msg("ui: activating first browser window")
777 a.activateBrowserWindow(created)814 a.activateBrowserWindow(created)
778 return nil815 return nil
779 }816 }
780 817
781 var openErr error818 var openErr error
819+ path := "tabs_uc"
782 if a.tabCoord != nil {820 if a.tabCoord != nil {
821+ path = "tab_coord"
822+ log.Debug().Str("window_id", created.id).Msg("ui: creating initial tab for fresh window via tab coordinator")
783 openErr = a.openFreshWindowWithTabCoord(ctx, url, created)823 openErr = a.openFreshWindowWithTabCoord(ctx, url, created)
784 } else if a.tabsUC == nil {824 } else if a.tabsUC == nil {
825+ log.Debug().Str("window_id", created.id).Msg("ui: activating fresh browser window without tab use case")
785 a.activateBrowserWindow(created)826 a.activateBrowserWindow(created)
786 return nil827 return nil
787 } else {828 } else {
829+ log.Debug().Str("window_id", created.id).Msg("ui: creating initial tab for fresh window via tabs use case")
788 openErr = a.openFreshWindowWithTabsUC(ctx, url, created)830 openErr = a.openFreshWindowWithTabsUC(ctx, url, created)
789 }831 }
790 if openErr != nil {832 if openErr != nil {
833+ log.Warn().Err(openErr).
834+ Str("window_id", created.id).
835+ Str("path", path).
836+ Int("window_count_after", len(a.browserWindows)).
837+ Msg("ui: open fresh window initial tab creation failed")
791 return openErr838 return openErr
792 }839 }
840+ log.Debug().
841+ Str("window_id", created.id).
842+ Str("path", path).
843+ Msg("ui: activating fresh browser window after tab creation")
793 a.activateBrowserWindow(created)844 a.activateBrowserWindow(created)
794 return nil845 return nil
795 }846 }
796 847
797 func (a *App) openFreshWindowWithTabCoord(ctx context.Context, url string, created *browserWindow) error {848 func (a *App) openFreshWindowWithTabCoord(ctx context.Context, url string, created *browserWindow) error {
849+ log := logging.FromContext(ctx)
798 previousTarget := a.tabCoord.CurrentTarget()850 previousTarget := a.tabCoord.CurrentTarget()
799- a.tabCoord.SetCurrentTarget(a.ensureTabTargetForBrowserWindow(created))851+ target := a.ensureTabTargetForBrowserWindow(created)
852+ a.tabCoord.SetCurrentTarget(target)
800 defer func() {853 defer func() {
801 a.tabCoord.SetCurrentTarget(previousTarget)854 a.tabCoord.SetCurrentTarget(previousTarget)
802 }()855 }()
803 856
804- createdTab, createErr := a.tabCoord.Create(ctx, a.ensureTabTargetForBrowserWindow(created), url)857+ log.Debug().
858+ Str("window_id", created.id).
859+ Str("url_host", logging.SafeURLHost(url)).
860+ Msg("ui: tab coordinator create started for fresh window")
861+ createdTab, createErr := a.tabCoord.Create(ctx, target, url)
805 if createErr != nil {862 if createErr != nil {
863+ log.Warn().Err(createErr).
864+ Str("window_id", created.id).
865+ Msg("ui: tab coordinator create failed for fresh window; rolling back window")
806 a.removeBrowserWindow(created.id)866 a.removeBrowserWindow(created.id)
807 if created.mainWindow != nil {867 if created.mainWindow != nil {
808 created.mainWindow.Destroy()868 created.mainWindow.Destroy()
809 }869 }
810 return createErr870 return createErr
811 }871 }
872+ log.Debug().
873+ Str("window_id", created.id).
874+ Str("tab_id", string(createdTab.ID)).
875+ Msg("ui: tab coordinator create completed for fresh window")
812 if a.workspaceViews[createdTab.ID] == nil {876 if a.workspaceViews[createdTab.ID] == nil {
877+ log.Warn().
878+ Str("window_id", created.id).
879+ Str("tab_id", string(createdTab.ID)).
880+ Msg("ui: fresh window tab has no workspace view; rolling back window")
813 if createdTabs := a.tabListForBrowserWindow(created); createdTabs != nil {881 if createdTabs := a.tabListForBrowserWindow(created); createdTabs != nil {
814 createdTabs.Remove(createdTab.ID)882 createdTabs.Remove(createdTab.ID)
815 }883 }
@@ -837,17 +905,29 @@ func (a *App) openFreshWindowWithTabCoord(ctx context.Context, url string, creat
837 }905 }
838 906
839 func (a *App) openFreshWindowWithTabsUC(ctx context.Context, url string, created *browserWindow) error {907 func (a *App) openFreshWindowWithTabsUC(ctx context.Context, url string, created *browserWindow) error {
908+ log := logging.FromContext(ctx)
909+ log.Debug().
910+ Str("window_id", created.id).
911+ Str("url_host", logging.SafeURLHost(url)).
912+ Msg("ui: tabs use case create started for fresh window")
840 output, err := a.tabsUC.Create(ctx, usecase.CreateTabInput{913 output, err := a.tabsUC.Create(ctx, usecase.CreateTabInput{
841 TabList: a.tabs,914 TabList: a.tabs,
842 InitialURL: url,915 InitialURL: url,
843 })916 })
844 if err != nil {917 if err != nil {
918+ log.Warn().Err(err).
919+ Str("window_id", created.id).
920+ Msg("ui: tabs use case create failed for fresh window; rolling back window")
845 a.removeBrowserWindow(created.id)921 a.removeBrowserWindow(created.id)
846 if created.mainWindow != nil {922 if created.mainWindow != nil {
847 created.mainWindow.Destroy()923 created.mainWindow.Destroy()
848 }924 }
849 return err925 return err
850 }926 }
927+ log.Debug().
928+ Str("window_id", created.id).
929+ Str("tab_id", string(output.Tab.ID)).
930+ Msg("ui: tabs use case create completed for fresh window")
851 a.setBrowserWindowForTab(output.Tab.ID, created)931 a.setBrowserWindowForTab(output.Tab.ID, created)
852 // Mirror the tabsUC-created tab into the new browser window's tab list.932 // Mirror the tabsUC-created tab into the new browser window's tab list.
853 // This path uses the tabs usecase directly instead of tabCoord.Create,933 // This path uses the tabs usecase directly instead of tabCoord.Create,
@@ -858,6 +938,10 @@ func (a *App) openFreshWindowWithTabsUC(ctx context.Context, url string, created
858 a.switchWorkspaceView(ctx, output.Tab.ID)938 a.switchWorkspaceView(ctx, output.Tab.ID)
859 }939 }
860 if a.workspaceViews[output.Tab.ID] == nil {940 if a.workspaceViews[output.Tab.ID] == nil {
941+ log.Warn().
942+ Str("window_id", created.id).
943+ Str("tab_id", string(output.Tab.ID)).
944+ Msg("ui: fresh window tabs use case tab has no workspace view; rolling back window")
861 if createdTabs := a.tabListForBrowserWindow(created); createdTabs != nil {945 if createdTabs := a.tabListForBrowserWindow(created); createdTabs != nil {
862 createdTabs.Remove(output.Tab.ID)946 createdTabs.Remove(output.Tab.ID)
863 }947 }
M internal/ui/browser_window.go +38 -4
@@ -343,6 +343,11 @@ func (a *App) deterministicBrowserWindowFallback() *browserWindow {
343343 }
344344
345345 func (a *App) OpenFreshWindow(ctx context.Context, url string) error {
346+ log := logging.FromContext(ctx)
347+ log.Debug().
348+ Str("url_host", logging.SafeURLHost(url)).
349+ Msg("ui: open fresh window dispatch requested")
350+
346351 dispatch := a.dispatchOnMainThread
347352 if dispatch == nil {
348353 dispatch = func(label string, fn func()) syncdispatch.SyncDispatchResult {
@@ -354,17 +359,46 @@ func (a *App) OpenFreshWindow(ctx context.Context, url string) error {
354359 }
355360
356361 var openErr error
362+ var windowCountBefore int
363+ var windowCountAfter int
364+ var hasTabCoord bool
365+ var hasTabsUC bool
357366 result := dispatch("ui.open_fresh_window", func() {
367+ windowCountBefore = len(a.browserWindows)
368+ hasTabCoord = a.tabCoord != nil
369+ hasTabsUC = a.tabsUC != nil
370+ log.Debug().
371+ Str("url_host", logging.SafeURLHost(url)).
372+ Int("window_count_before", windowCountBefore).
373+ Bool("has_tab_coord", hasTabCoord).
374+ Bool("has_tabs_uc", hasTabsUC).
375+ Msg("ui: open fresh window main-thread work started")
358376 openErr = a.openFreshWindow(ctx, url)
377+ windowCountAfter = len(a.browserWindows)
359378 })
360379 if !result.Completed() {
361- logging.FromContext(ctx).Warn().
362- Str("url", logging.TruncateURL(url, logging.PermissionLogURLMaxLen)).
380+ log.Warn().
381+ Str("url_host", logging.SafeURLHost(url)).
363382 Dur("elapsed", result.Elapsed).
364383 Str("dispatch_status", string(result.Status)).
365384 Msg("ui: open fresh window skipped after main-thread dispatch did not complete")
366385 return fmt.Errorf("main thread dispatch did not complete: %s", result.Status)
367386 }
368-
369- return openErr
387+ if openErr != nil {
388+ log.Warn().Err(openErr).
389+ Str("url_host", logging.SafeURLHost(url)).
390+ Dur("elapsed", result.Elapsed).
391+ Str("dispatch_status", string(result.Status)).
392+ Int("window_count_after", windowCountAfter).
393+ Msg("ui: open fresh window failed")
394+ return openErr
395+ }
396+
397+ log.Debug().
398+ Str("url_host", logging.SafeURLHost(url)).
399+ Dur("elapsed", result.Elapsed).
400+ Str("dispatch_status", string(result.Status)).
401+ Int("window_count_after", windowCountAfter).
402+ Msg("ui: open fresh window completed")
403+ return nil
370404 }
@@ -343,6 +343,11 @@ func (a *App) deterministicBrowserWindowFallback() *browserWindow {
343 }343 }
344 344
345 func (a *App) OpenFreshWindow(ctx context.Context, url string) error {345 func (a *App) OpenFreshWindow(ctx context.Context, url string) error {
346+ log := logging.FromContext(ctx)
347+ log.Debug().
348+ Str("url_host", logging.SafeURLHost(url)).
349+ Msg("ui: open fresh window dispatch requested")
350+
346 dispatch := a.dispatchOnMainThread351 dispatch := a.dispatchOnMainThread
347 if dispatch == nil {352 if dispatch == nil {
348 dispatch = func(label string, fn func()) syncdispatch.SyncDispatchResult {353 dispatch = func(label string, fn func()) syncdispatch.SyncDispatchResult {
@@ -354,17 +359,46 @@ func (a *App) OpenFreshWindow(ctx context.Context, url string) error {
354 }359 }
355 360
356 var openErr error361 var openErr error
362+ var windowCountBefore int
363+ var windowCountAfter int
364+ var hasTabCoord bool
365+ var hasTabsUC bool
357 result := dispatch("ui.open_fresh_window", func() {366 result := dispatch("ui.open_fresh_window", func() {
367+ windowCountBefore = len(a.browserWindows)
368+ hasTabCoord = a.tabCoord != nil
369+ hasTabsUC = a.tabsUC != nil
370+ log.Debug().
371+ Str("url_host", logging.SafeURLHost(url)).
372+ Int("window_count_before", windowCountBefore).
373+ Bool("has_tab_coord", hasTabCoord).
374+ Bool("has_tabs_uc", hasTabsUC).
375+ Msg("ui: open fresh window main-thread work started")
358 openErr = a.openFreshWindow(ctx, url)376 openErr = a.openFreshWindow(ctx, url)
377+ windowCountAfter = len(a.browserWindows)
359 })378 })
360 if !result.Completed() {379 if !result.Completed() {
361- logging.FromContext(ctx).Warn().380+ log.Warn().
362- Str("url", logging.TruncateURL(url, logging.PermissionLogURLMaxLen)).381+ Str("url_host", logging.SafeURLHost(url)).
363 Dur("elapsed", result.Elapsed).382 Dur("elapsed", result.Elapsed).
364 Str("dispatch_status", string(result.Status)).383 Str("dispatch_status", string(result.Status)).
365 Msg("ui: open fresh window skipped after main-thread dispatch did not complete")384 Msg("ui: open fresh window skipped after main-thread dispatch did not complete")
366 return fmt.Errorf("main thread dispatch did not complete: %s", result.Status)385 return fmt.Errorf("main thread dispatch did not complete: %s", result.Status)
367 }386 }
368-387+ if openErr != nil {
369- return openErr388+ log.Warn().Err(openErr).
389+ Str("url_host", logging.SafeURLHost(url)).
390+ Dur("elapsed", result.Elapsed).
391+ Str("dispatch_status", string(result.Status)).
392+ Int("window_count_after", windowCountAfter).
393+ Msg("ui: open fresh window failed")
394+ return openErr
395+ }
396+
397+ log.Debug().
398+ Str("url_host", logging.SafeURLHost(url)).
399+ Dur("elapsed", result.Elapsed).
400+ Str("dispatch_status", string(result.Status)).
401+ Int("window_count_after", windowCountAfter).
402+ Msg("ui: open fresh window completed")
403+ return nil
370 }404 }

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