Merge pull request #278 from bnema/chore/diagnostic-window-launch-logs
chore(logging): add window launch diagnostics
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 | |||
| 315 | 315 | Msg("browser launch relay accepted request without opener") | |
| 316 | 316 | return | |
| 317 | 317 | } | |
| 318 | + | started := time.Now() | |
| 318 | 319 | log.Debug(). | |
| 319 | 320 | Str("request_id", requestID). | |
| 320 | 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 | 323 | if err := opener.OpenFreshWindow(ctx, request.URL); err != nil { | |
| 323 | 324 | log.Warn().Err(err). | |
| 324 | 325 | Str("request_id", requestID). | |
| 325 | 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 | 329 | return | |
| 328 | 330 | } | |
| 329 | 331 | log.Debug(). | |
| 330 | 332 | Str("request_id", requestID). | |
| 331 | 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 | ||
| @@ -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 | return | 316 | 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 | return | 329 | 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 | |||
| 3 | 3 | import ( | |
| 4 | 4 | "errors" | |
| 5 | 5 | "io" | |
| 6 | + | neturl "net/url" | |
| 6 | 7 | "os" | |
| 7 | 8 | "path/filepath" | |
| 8 | 9 | "sync" | |
| @@ -248,3 +249,12 @@ func TruncateURL(url string, maxLen int) string { | |||
| 248 | 249 | } | |
| 249 | 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 | + | } | |
| @@ -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 ( | |||
| 6 | 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 | 28 | func TestSessionFileWriter_DropsWritesAfterClose(t *testing.T) { | |
| 10 | 29 | t.Parallel() | |
| 11 | 30 | ||
| @@ -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) { | |||
| 422 | 422 | } | |
| 423 | 423 | ||
| 424 | 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 | 432 | if a.browserWindowFactory != nil { | |
| 426 | 433 | created, err := a.browserWindowFactory(ctx, initialURL) | |
| 427 | 434 | if err != nil { | |
| 435 | + | log.Warn().Err(err). | |
| 436 | + | Str("url_host", logging.SafeURLHost(initialURL)). | |
| 437 | + | Msg("ui: browser window factory failed") | |
| 428 | 438 | return nil, err | |
| 429 | 439 | } | |
| 430 | 440 | created.ensureTabs() | |
| @@ -433,9 +443,11 @@ func (a *App) createBrowserWindow(ctx context.Context, initialURL string) (*brow | |||
| 433 | 443 | return created, nil | |
| 434 | 444 | } | |
| 435 | 445 | ||
| 436 | - | log := logging.FromContext(ctx) | |
| 437 | 446 | mainWindow, err := window.New(ctx, a.gtkApp, a.deps.Config.Workspace.TabBarPosition) | |
| 438 | 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 | 451 | return nil, err | |
| 440 | 452 | } | |
| 441 | 453 | browserWindow := &browserWindow{ | |
| @@ -496,6 +508,10 @@ func (a *App) createBrowserWindow(ctx context.Context, initialURL string) (*brow | |||
| 496 | 508 | if display := mainWindow.Window().GetDisplay(); display != nil { | |
| 497 | 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 | 515 | return browserWindow, nil | |
| 500 | 516 | } | |
| 501 | 517 | ||
| @@ -767,49 +783,101 @@ func (a *App) closeBrowserLaunchRelayListener() { | |||
| 767 | 783 | } | |
| 768 | 784 | ||
| 769 | 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 | 794 | created, err := a.createBrowserWindow(ctx, url) | |
| 771 | 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 | 800 | 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 | 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 | 812 | if len(a.browserWindows) == 1 { | |
| 813 | + | log.Debug().Str("window_id", created.id).Msg("ui: activating first browser window") | |
| 777 | 814 | a.activateBrowserWindow(created) | |
| 778 | 815 | return nil | |
| 779 | 816 | } | |
| 780 | 817 | ||
| 781 | 818 | var openErr error | |
| 819 | + | path := "tabs_uc" | |
| 782 | 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 | 823 | openErr = a.openFreshWindowWithTabCoord(ctx, url, created) | |
| 784 | 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 | 826 | a.activateBrowserWindow(created) | |
| 786 | 827 | return nil | |
| 787 | 828 | } else { | |
| 829 | + | log.Debug().Str("window_id", created.id).Msg("ui: creating initial tab for fresh window via tabs use case") | |
| 788 | 830 | openErr = a.openFreshWindowWithTabsUC(ctx, url, created) | |
| 789 | 831 | } | |
| 790 | 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 | 838 | 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 | 844 | a.activateBrowserWindow(created) | |
| 794 | 845 | return nil | |
| 795 | 846 | } | |
| 796 | 847 | ||
| 797 | 848 | func (a *App) openFreshWindowWithTabCoord(ctx context.Context, url string, created *browserWindow) error { | |
| 849 | + | log := logging.FromContext(ctx) | |
| 798 | 850 | previousTarget := a.tabCoord.CurrentTarget() | |
| 799 | - | a.tabCoord.SetCurrentTarget(a.ensureTabTargetForBrowserWindow(created)) | |
| 851 | + | target := a.ensureTabTargetForBrowserWindow(created) | |
| 852 | + | a.tabCoord.SetCurrentTarget(target) | |
| 800 | 853 | defer func() { | |
| 801 | 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 | 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 | 866 | a.removeBrowserWindow(created.id) | |
| 807 | 867 | if created.mainWindow != nil { | |
| 808 | 868 | created.mainWindow.Destroy() | |
| 809 | 869 | } | |
| 810 | 870 | 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 | 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 | 881 | if createdTabs := a.tabListForBrowserWindow(created); createdTabs != nil { | |
| 814 | 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 | 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 | 913 | output, err := a.tabsUC.Create(ctx, usecase.CreateTabInput{ | |
| 841 | 914 | TabList: a.tabs, | |
| 842 | 915 | InitialURL: url, | |
| 843 | 916 | }) | |
| 844 | 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 | 921 | a.removeBrowserWindow(created.id) | |
| 846 | 922 | if created.mainWindow != nil { | |
| 847 | 923 | created.mainWindow.Destroy() | |
| 848 | 924 | } | |
| 849 | 925 | 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 | 931 | a.setBrowserWindowForTab(output.Tab.ID, created) | |
| 852 | 932 | // Mirror the tabsUC-created tab into the new browser window's tab list. | |
| 853 | 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 | 938 | a.switchWorkspaceView(ctx, output.Tab.ID) | |
| 859 | 939 | } | |
| 860 | 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 | 945 | if createdTabs := a.tabListForBrowserWindow(created); createdTabs != nil { | |
| 862 | 946 | createdTabs.Remove(output.Tab.ID) | |
| 863 | 947 | } | |
| @@ -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, err | 438 | 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, nil | 443 | 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, err | 451 | 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, nil | 515 | 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 err | 800 | 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 nil | 815 | return nil | ||
| 779 | } | 816 | } | ||
| 780 | 817 | ||||
| 781 | var openErr error | 818 | 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 nil | 827 | 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 openErr | 838 | 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 nil | 845 | 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 createErr | 870 | 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 err | 925 | 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 { | |||
| 343 | 343 | } | |
| 344 | 344 | ||
| 345 | 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 | 351 | dispatch := a.dispatchOnMainThread | |
| 347 | 352 | if dispatch == nil { | |
| 348 | 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 | 361 | var openErr error | |
| 362 | + | var windowCountBefore int | |
| 363 | + | var windowCountAfter int | |
| 364 | + | var hasTabCoord bool | |
| 365 | + | var hasTabsUC bool | |
| 357 | 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 | 376 | openErr = a.openFreshWindow(ctx, url) | |
| 377 | + | windowCountAfter = len(a.browserWindows) | |
| 359 | 378 | }) | |
| 360 | 379 | 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)). | |
| 363 | 382 | Dur("elapsed", result.Elapsed). | |
| 364 | 383 | Str("dispatch_status", string(result.Status)). | |
| 365 | 384 | Msg("ui: open fresh window skipped after main-thread dispatch did not complete") | |
| 366 | 385 | return fmt.Errorf("main thread dispatch did not complete: %s", result.Status) | |
| 367 | 386 | } | |
| 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 | |
| 370 | 404 | } | |
| @@ -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.dispatchOnMainThread | 351 | 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 error | 361 | 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 openErr | 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 | |||
| 370 | } | 404 | } | ||