fix(history): clear PR blocker findings
- commit
- 1ee4e97d496147797e0a36aede1f99d2da12e28e
- author
- brice · · 2026-06-13 09:17 UTC
- parent
- 933fcaf
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
11 files · +90 -79
M internal/ui/browser_window_history_sidebar.go +7 -12
| @@ -19,7 +19,7 @@ func (bw *browserWindow) initHistorySidebar(ctx context.Context, a *App) { | |||
| 19 | 19 | } | |
| 20 | 20 | log := logging.FromContext(ctx) | |
| 21 | 21 | ||
| 22 | - | cfg := a.buildHistorySidebarConfig(ctx, bw) | |
| 22 | + | cfg := a.buildHistorySidebarConfig(bw) | |
| 23 | 23 | ||
| 24 | 24 | sidebar := component.NewHistorySidebar(ctx, cfg) | |
| 25 | 25 | if sidebar == nil { | |
| @@ -42,7 +42,7 @@ func (bw *browserWindow) initHistorySidebar(ctx context.Context, a *App) { | |||
| 42 | 42 | ||
| 43 | 43 | // buildHistorySidebarConfig constructs the HistorySidebarConfig for the given | |
| 44 | 44 | // browser window. Extracted from initHistorySidebar for testability. | |
| 45 | - | func (a *App) buildHistorySidebarConfig(ctx context.Context, bw *browserWindow) component.HistorySidebarConfig { | |
| 45 | + | func (a *App) buildHistorySidebarConfig(bw *browserWindow) component.HistorySidebarConfig { | |
| 46 | 46 | var historyUC port.HistorySidebarHistory | |
| 47 | 47 | if a.deps != nil { | |
| 48 | 48 | historyUC = a.deps.HistoryUC | |
| @@ -76,9 +76,8 @@ func (a *App) navigateHistorySidebarSelection(ctx context.Context, bw *browserWi | |||
| 76 | 76 | return a.navigateFromBrowserWindow(ctx, bw, url) | |
| 77 | 77 | } | |
| 78 | 78 | ||
| 79 | - | // toggleHistorySidebar toggles sidebar visibility. An optional width config | |
| 80 | - | // can be provided and is applied when showing the sidebar. | |
| 81 | - | func (bw *browserWindow) toggleHistorySidebar(widthCfg ...window.SidebarWidthConfig) { | |
| 79 | + | // toggleHistorySidebar toggles sidebar visibility. | |
| 80 | + | func (bw *browserWindow) toggleHistorySidebar() { | |
| 82 | 81 | if bw == nil || bw.historySidebar == nil { | |
| 83 | 82 | return | |
| 84 | 83 | } | |
| @@ -86,20 +85,16 @@ func (bw *browserWindow) toggleHistorySidebar(widthCfg ...window.SidebarWidthCon | |||
| 86 | 85 | if bw.sidebarVisible { | |
| 87 | 86 | bw.hideHistorySidebar() | |
| 88 | 87 | } else { | |
| 89 | - | bw.showHistorySidebar(widthCfg...) | |
| 88 | + | bw.showHistorySidebar() | |
| 90 | 89 | } | |
| 91 | 90 | } | |
| 92 | 91 | ||
| 93 | 92 | // showHistorySidebar makes the sidebar visible and grabs focus for the search | |
| 94 | - | // entry. An optional width config can be provided to override the default width. | |
| 95 | - | func (bw *browserWindow) showHistorySidebar(widthCfg ...window.SidebarWidthConfig) { | |
| 93 | + | // entry. | |
| 94 | + | func (bw *browserWindow) showHistorySidebar() { | |
| 96 | 95 | if bw == nil || bw.historySidebar == nil || bw.mainWindow == nil { | |
| 97 | 96 | return | |
| 98 | 97 | } | |
| 99 | - | // Apply width config if provided | |
| 100 | - | if len(widthCfg) > 0 { | |
| 101 | - | bw.mainWindow.SetSidebarWidth(widthCfg[0]) | |
| 102 | - | } | |
| 103 | 98 | bw.historySidebar.Show() | |
| 104 | 99 | bw.mainWindow.SetSidebarVisible(true) | |
| 105 | 100 | bw.sidebarVisible = true | |
| @@ -19,7 +19,7 @@ func (bw *browserWindow) initHistorySidebar(ctx context.Context, a *App) { | |||||
| 19 | } | 19 | } | ||
| 20 | log := logging.FromContext(ctx) | 20 | log := logging.FromContext(ctx) | ||
| 21 | 21 | ||||
| 22 | - | cfg := a.buildHistorySidebarConfig(ctx, bw) | 22 | + | cfg := a.buildHistorySidebarConfig(bw) |
| 23 | 23 | ||||
| 24 | sidebar := component.NewHistorySidebar(ctx, cfg) | 24 | sidebar := component.NewHistorySidebar(ctx, cfg) | ||
| 25 | if sidebar == nil { | 25 | if sidebar == nil { | ||
| @@ -42,7 +42,7 @@ func (bw *browserWindow) initHistorySidebar(ctx context.Context, a *App) { | |||||
| 42 | 42 | ||||
| 43 | // buildHistorySidebarConfig constructs the HistorySidebarConfig for the given | 43 | // buildHistorySidebarConfig constructs the HistorySidebarConfig for the given | ||
| 44 | // browser window. Extracted from initHistorySidebar for testability. | 44 | // browser window. Extracted from initHistorySidebar for testability. | ||
| 45 | - | func (a *App) buildHistorySidebarConfig(ctx context.Context, bw *browserWindow) component.HistorySidebarConfig { | 45 | + | func (a *App) buildHistorySidebarConfig(bw *browserWindow) component.HistorySidebarConfig { |
| 46 | var historyUC port.HistorySidebarHistory | 46 | var historyUC port.HistorySidebarHistory | ||
| 47 | if a.deps != nil { | 47 | if a.deps != nil { | ||
| 48 | historyUC = a.deps.HistoryUC | 48 | historyUC = a.deps.HistoryUC | ||
| @@ -76,9 +76,8 @@ func (a *App) navigateHistorySidebarSelection(ctx context.Context, bw *browserWi | |||||
| 76 | return a.navigateFromBrowserWindow(ctx, bw, url) | 76 | return a.navigateFromBrowserWindow(ctx, bw, url) | ||
| 77 | } | 77 | } | ||
| 78 | 78 | ||||
| 79 | - | // toggleHistorySidebar toggles sidebar visibility. An optional width config | 79 | + | // toggleHistorySidebar toggles sidebar visibility. |
| 80 | - | // can be provided and is applied when showing the sidebar. | 80 | + | func (bw *browserWindow) toggleHistorySidebar() { |
| 81 | - | func (bw *browserWindow) toggleHistorySidebar(widthCfg ...window.SidebarWidthConfig) { | |||
| 82 | if bw == nil || bw.historySidebar == nil { | 81 | if bw == nil || bw.historySidebar == nil { | ||
| 83 | return | 82 | return | ||
| 84 | } | 83 | } | ||
| @@ -86,20 +85,16 @@ func (bw *browserWindow) toggleHistorySidebar(widthCfg ...window.SidebarWidthCon | |||||
| 86 | if bw.sidebarVisible { | 85 | if bw.sidebarVisible { | ||
| 87 | bw.hideHistorySidebar() | 86 | bw.hideHistorySidebar() | ||
| 88 | } else { | 87 | } else { | ||
| 89 | - | bw.showHistorySidebar(widthCfg...) | 88 | + | bw.showHistorySidebar() |
| 90 | } | 89 | } | ||
| 91 | } | 90 | } | ||
| 92 | 91 | ||||
| 93 | // showHistorySidebar makes the sidebar visible and grabs focus for the search | 92 | // showHistorySidebar makes the sidebar visible and grabs focus for the search | ||
| 94 | - | // entry. An optional width config can be provided to override the default width. | 93 | + | // entry. |
| 95 | - | func (bw *browserWindow) showHistorySidebar(widthCfg ...window.SidebarWidthConfig) { | 94 | + | func (bw *browserWindow) showHistorySidebar() { |
| 96 | if bw == nil || bw.historySidebar == nil || bw.mainWindow == nil { | 95 | if bw == nil || bw.historySidebar == nil || bw.mainWindow == nil { | ||
| 97 | return | 96 | return | ||
| 98 | } | 97 | } | ||
| 99 | - | // Apply width config if provided | |||
| 100 | - | if len(widthCfg) > 0 { | |||
| 101 | - | bw.mainWindow.SetSidebarWidth(widthCfg[0]) | |||
| 102 | - | } | |||
| 103 | bw.historySidebar.Show() | 98 | bw.historySidebar.Show() | ||
| 104 | bw.mainWindow.SetSidebarVisible(true) | 99 | bw.mainWindow.SetSidebarVisible(true) | ||
| 105 | bw.sidebarVisible = true | 100 | bw.sidebarVisible = true | ||
M internal/ui/browser_window_history_sidebar_test.go +11 -16
| @@ -69,7 +69,7 @@ func TestHistorySidebarConfig_OnNavigateNavigatesActivePaneAndKeepsSidebar(t *te | |||
| 69 | 69 | second.historySidebar = &component.HistorySidebar{} | |
| 70 | 70 | second.sidebarVisible = true | |
| 71 | 71 | ||
| 72 | - | cfg := app.buildHistorySidebarConfig(ctx, second) | |
| 72 | + | cfg := app.buildHistorySidebarConfig(second) | |
| 73 | 73 | navigateURL := "https://example.com" | |
| 74 | 74 | err := cfg.OnNavigate(ctx, navigateURL) | |
| 75 | 75 | require.NoError(t, err, "OnNavigate should succeed") | |
| @@ -118,7 +118,7 @@ func TestHistorySidebarConfig_OnNavigateKeepOpenNavigatesWithoutClosing(t *testi | |||
| 118 | 118 | } | |
| 119 | 119 | app.tabs.Add(tab) | |
| 120 | 120 | ||
| 121 | - | cfg := app.buildHistorySidebarConfig(ctx, bw) | |
| 121 | + | cfg := app.buildHistorySidebarConfig(bw) | |
| 122 | 122 | navigateURL := "https://keep-open.com" | |
| 123 | 123 | err := cfg.OnNavigateKeepOpen(ctx, navigateURL) | |
| 124 | 124 | require.NoError(t, err) | |
| @@ -346,7 +346,6 @@ func TestApp_HistorySidebarToggleHandler_NilFocusedWindowIsNoOp(t *testing.T) { | |||
| 346 | 346 | // TestHistorySidebarConfig_OnCloseHidesSidebar verifies that the OnClose | |
| 347 | 347 | // callback hides the sidebar for the owning browser window. | |
| 348 | 348 | func TestHistorySidebarConfig_OnCloseHidesSidebar(t *testing.T) { | |
| 349 | - | ctx := t.Context() | |
| 350 | 349 | tab := entity.NewTab(entity.TabID("tab-1"), entity.WorkspaceID("ws-1"), entity.NewPane(entity.PaneID("pane-1"))) | |
| 351 | 350 | bwTabs := entity.NewTabList() | |
| 352 | 351 | bwTabs.Add(tab) | |
| @@ -367,7 +366,7 @@ func TestHistorySidebarConfig_OnCloseHidesSidebar(t *testing.T) { | |||
| 367 | 366 | } | |
| 368 | 367 | app.tabs.Add(tab) | |
| 369 | 368 | ||
| 370 | - | cfg := app.buildHistorySidebarConfig(ctx, bw) | |
| 369 | + | cfg := app.buildHistorySidebarConfig(bw) | |
| 371 | 370 | cfg.OnClose() | |
| 372 | 371 | ||
| 373 | 372 | assert.False(t, bw.sidebarVisible, "sidebar must be hidden by OnClose") | |
| @@ -456,7 +455,7 @@ func TestApp_HistorySidebar_ToggleThroughDispatcher_UnavailableReturnsError(t *t | |||
| 456 | 455 | ||
| 457 | 456 | err := kbDispatcher.Dispatch(ctx, input.ActionToggleHistorySystemView) | |
| 458 | 457 | require.Error(t, err) | |
| 459 | - | assert.ErrorContains(t, err, "history sidebar unavailable") | |
| 458 | + | require.ErrorContains(t, err, "history sidebar unavailable") | |
| 460 | 459 | assert.False(t, bw.sidebarVisible, "sidebar must remain invisible when not wired") | |
| 461 | 460 | } | |
| 462 | 461 | ||
| @@ -483,7 +482,7 @@ func TestApp_HistorySidebar_ToggleThroughDispatcher_NilFocusedReturnsError(t *te | |||
| 483 | 482 | ||
| 484 | 483 | err := kbDispatcher.Dispatch(ctx, input.ActionToggleHistorySystemView) | |
| 485 | 484 | require.Error(t, err) | |
| 486 | - | assert.ErrorContains(t, err, "history sidebar unavailable") | |
| 485 | + | require.ErrorContains(t, err, "history sidebar unavailable") | |
| 487 | 486 | } | |
| 488 | 487 | ||
| 489 | 488 | // ============================================================================= | |
| @@ -527,7 +526,7 @@ func TestApp_HistorySidebarConfig_NavigateCallbackNavigates(t *testing.T) { | |||
| 527 | 526 | app.tabs.Add(tab) | |
| 528 | 527 | ||
| 529 | 528 | // Build the config using the extracted seam. | |
| 530 | - | cfg := app.buildHistorySidebarConfig(ctx, bw) | |
| 529 | + | cfg := app.buildHistorySidebarConfig(bw) | |
| 531 | 530 | require.NotNil(t, cfg.OnNavigate, "OnNavigate callback must be non-nil") | |
| 532 | 531 | ||
| 533 | 532 | // Invoke the OnNavigate callback. | |
| @@ -623,7 +622,7 @@ func TestApp_HistorySidebarConfig_NavigateCallbackOwnership(t *testing.T) { | |||
| 623 | 622 | app.tabs.Add(tab2) | |
| 624 | 623 | ||
| 625 | 624 | // Build config for the SECOND window, even though first is globally focused. | |
| 626 | - | cfg := app.buildHistorySidebarConfig(ctx, second) | |
| 625 | + | cfg := app.buildHistorySidebarConfig(second) | |
| 627 | 626 | ||
| 628 | 627 | // Invoke OnNavigate — should navigate through second window's pane-2. | |
| 629 | 628 | err := cfg.OnNavigate(ctx, "https://ownership.com") | |
| @@ -672,7 +671,7 @@ func TestApp_HistorySidebarConfig_KeepOpenCallback(t *testing.T) { | |||
| 672 | 671 | } | |
| 673 | 672 | app.tabs.Add(tab) | |
| 674 | 673 | ||
| 675 | - | cfg := app.buildHistorySidebarConfig(ctx, bw) | |
| 674 | + | cfg := app.buildHistorySidebarConfig(bw) | |
| 676 | 675 | ||
| 677 | 676 | // OnNavigateKeepOpen navigates but does NOT close the sidebar. | |
| 678 | 677 | navigateURL := "https://keep-open.com" | |
| @@ -722,7 +721,7 @@ func TestApp_HistorySidebarConfig_OpenInNewPaneCallback(t *testing.T) { | |||
| 722 | 721 | } | |
| 723 | 722 | app.tabs.Add(tab) | |
| 724 | 723 | ||
| 725 | - | cfg := app.buildHistorySidebarConfig(ctx, bw) | |
| 724 | + | cfg := app.buildHistorySidebarConfig(bw) | |
| 726 | 725 | ||
| 727 | 726 | // OnOpenInNewPane should activate the owning window and split with URL. | |
| 728 | 727 | splitURL := "https://shift-enter.com" | |
| @@ -748,8 +747,6 @@ func TestApp_HistorySidebarConfig_OpenInNewPaneCallback(t *testing.T) { | |||
| 748 | 747 | // TestApp_HistorySidebarConfig_CloseCallback verifies that OnClose hides the | |
| 749 | 748 | // sidebar for the owning browser window and restores focus to the active pane. | |
| 750 | 749 | func TestApp_HistorySidebarConfig_CloseCallback(t *testing.T) { | |
| 751 | - | ctx := t.Context() | |
| 752 | - | ||
| 753 | 750 | paneID := entity.PaneID("pane-1") | |
| 754 | 751 | tab := entity.NewTab(entity.TabID("tab-1"), entity.WorkspaceID("ws-1"), entity.NewPane(paneID)) | |
| 755 | 752 | bwTabs := entity.NewTabList() | |
| @@ -783,7 +780,7 @@ func TestApp_HistorySidebarConfig_CloseCallback(t *testing.T) { | |||
| 783 | 780 | tab.ID: wsView, | |
| 784 | 781 | }, | |
| 785 | 782 | } | |
| 786 | - | cfg := app.buildHistorySidebarConfig(ctx, bw) | |
| 783 | + | cfg := app.buildHistorySidebarConfig(bw) | |
| 787 | 784 | ||
| 788 | 785 | // OnClose hides the sidebar. | |
| 789 | 786 | cfg.OnClose() | |
| @@ -795,8 +792,6 @@ func TestApp_HistorySidebarConfig_CloseCallback(t *testing.T) { | |||
| 795 | 792 | // OnClose (hideAndRestoreFocusForBrowserWindow) is safe when the browser | |
| 796 | 793 | // window has no sidebar or is nil. | |
| 797 | 794 | func TestApp_HistorySidebarConfig_CloseWithNoSidebarIsSafe(t *testing.T) { | |
| 798 | - | ctx := t.Context() | |
| 799 | - | ||
| 800 | 795 | bw := &browserWindow{ | |
| 801 | 796 | id: "no-sidebar", | |
| 802 | 797 | mainWindow: &window.MainWindow{}, | |
| @@ -808,7 +803,7 @@ func TestApp_HistorySidebarConfig_CloseWithNoSidebarIsSafe(t *testing.T) { | |||
| 808 | 803 | browserWindows: map[string]*browserWindow{bw.id: bw}, | |
| 809 | 804 | } | |
| 810 | 805 | ||
| 811 | - | cfg := app.buildHistorySidebarConfig(ctx, bw) | |
| 806 | + | cfg := app.buildHistorySidebarConfig(bw) | |
| 812 | 807 | ||
| 813 | 808 | // Should not panic even with nil sidebar. | |
| 814 | 809 | require.NotPanics(t, func() { cfg.OnClose() }) | |
| @@ -69,7 +69,7 @@ func TestHistorySidebarConfig_OnNavigateNavigatesActivePaneAndKeepsSidebar(t *te | |||||
| 69 | second.historySidebar = &component.HistorySidebar{} | 69 | second.historySidebar = &component.HistorySidebar{} | ||
| 70 | second.sidebarVisible = true | 70 | second.sidebarVisible = true | ||
| 71 | 71 | ||||
| 72 | - | cfg := app.buildHistorySidebarConfig(ctx, second) | 72 | + | cfg := app.buildHistorySidebarConfig(second) |
| 73 | navigateURL := "https://example.com" | 73 | navigateURL := "https://example.com" | ||
| 74 | err := cfg.OnNavigate(ctx, navigateURL) | 74 | err := cfg.OnNavigate(ctx, navigateURL) | ||
| 75 | require.NoError(t, err, "OnNavigate should succeed") | 75 | require.NoError(t, err, "OnNavigate should succeed") | ||
| @@ -118,7 +118,7 @@ func TestHistorySidebarConfig_OnNavigateKeepOpenNavigatesWithoutClosing(t *testi | |||||
| 118 | } | 118 | } | ||
| 119 | app.tabs.Add(tab) | 119 | app.tabs.Add(tab) | ||
| 120 | 120 | ||||
| 121 | - | cfg := app.buildHistorySidebarConfig(ctx, bw) | 121 | + | cfg := app.buildHistorySidebarConfig(bw) |
| 122 | navigateURL := "https://keep-open.com" | 122 | navigateURL := "https://keep-open.com" | ||
| 123 | err := cfg.OnNavigateKeepOpen(ctx, navigateURL) | 123 | err := cfg.OnNavigateKeepOpen(ctx, navigateURL) | ||
| 124 | require.NoError(t, err) | 124 | require.NoError(t, err) | ||
| @@ -346,7 +346,6 @@ func TestApp_HistorySidebarToggleHandler_NilFocusedWindowIsNoOp(t *testing.T) { | |||||
| 346 | // TestHistorySidebarConfig_OnCloseHidesSidebar verifies that the OnClose | 346 | // TestHistorySidebarConfig_OnCloseHidesSidebar verifies that the OnClose | ||
| 347 | // callback hides the sidebar for the owning browser window. | 347 | // callback hides the sidebar for the owning browser window. | ||
| 348 | func TestHistorySidebarConfig_OnCloseHidesSidebar(t *testing.T) { | 348 | func TestHistorySidebarConfig_OnCloseHidesSidebar(t *testing.T) { | ||
| 349 | - | ctx := t.Context() | |||
| 350 | tab := entity.NewTab(entity.TabID("tab-1"), entity.WorkspaceID("ws-1"), entity.NewPane(entity.PaneID("pane-1"))) | 349 | tab := entity.NewTab(entity.TabID("tab-1"), entity.WorkspaceID("ws-1"), entity.NewPane(entity.PaneID("pane-1"))) | ||
| 351 | bwTabs := entity.NewTabList() | 350 | bwTabs := entity.NewTabList() | ||
| 352 | bwTabs.Add(tab) | 351 | bwTabs.Add(tab) | ||
| @@ -367,7 +366,7 @@ func TestHistorySidebarConfig_OnCloseHidesSidebar(t *testing.T) { | |||||
| 367 | } | 366 | } | ||
| 368 | app.tabs.Add(tab) | 367 | app.tabs.Add(tab) | ||
| 369 | 368 | ||||
| 370 | - | cfg := app.buildHistorySidebarConfig(ctx, bw) | 369 | + | cfg := app.buildHistorySidebarConfig(bw) |
| 371 | cfg.OnClose() | 370 | cfg.OnClose() | ||
| 372 | 371 | ||||
| 373 | assert.False(t, bw.sidebarVisible, "sidebar must be hidden by OnClose") | 372 | assert.False(t, bw.sidebarVisible, "sidebar must be hidden by OnClose") | ||
| @@ -456,7 +455,7 @@ func TestApp_HistorySidebar_ToggleThroughDispatcher_UnavailableReturnsError(t *t | |||||
| 456 | 455 | ||||
| 457 | err := kbDispatcher.Dispatch(ctx, input.ActionToggleHistorySystemView) | 456 | err := kbDispatcher.Dispatch(ctx, input.ActionToggleHistorySystemView) | ||
| 458 | require.Error(t, err) | 457 | require.Error(t, err) | ||
| 459 | - | assert.ErrorContains(t, err, "history sidebar unavailable") | 458 | + | require.ErrorContains(t, err, "history sidebar unavailable") |
| 460 | assert.False(t, bw.sidebarVisible, "sidebar must remain invisible when not wired") | 459 | assert.False(t, bw.sidebarVisible, "sidebar must remain invisible when not wired") | ||
| 461 | } | 460 | } | ||
| 462 | 461 | ||||
| @@ -483,7 +482,7 @@ func TestApp_HistorySidebar_ToggleThroughDispatcher_NilFocusedReturnsError(t *te | |||||
| 483 | 482 | ||||
| 484 | err := kbDispatcher.Dispatch(ctx, input.ActionToggleHistorySystemView) | 483 | err := kbDispatcher.Dispatch(ctx, input.ActionToggleHistorySystemView) | ||
| 485 | require.Error(t, err) | 484 | require.Error(t, err) | ||
| 486 | - | assert.ErrorContains(t, err, "history sidebar unavailable") | 485 | + | require.ErrorContains(t, err, "history sidebar unavailable") |
| 487 | } | 486 | } | ||
| 488 | 487 | ||||
| 489 | // ============================================================================= | 488 | // ============================================================================= | ||
| @@ -527,7 +526,7 @@ func TestApp_HistorySidebarConfig_NavigateCallbackNavigates(t *testing.T) { | |||||
| 527 | app.tabs.Add(tab) | 526 | app.tabs.Add(tab) | ||
| 528 | 527 | ||||
| 529 | // Build the config using the extracted seam. | 528 | // Build the config using the extracted seam. | ||
| 530 | - | cfg := app.buildHistorySidebarConfig(ctx, bw) | 529 | + | cfg := app.buildHistorySidebarConfig(bw) |
| 531 | require.NotNil(t, cfg.OnNavigate, "OnNavigate callback must be non-nil") | 530 | require.NotNil(t, cfg.OnNavigate, "OnNavigate callback must be non-nil") | ||
| 532 | 531 | ||||
| 533 | // Invoke the OnNavigate callback. | 532 | // Invoke the OnNavigate callback. | ||
| @@ -623,7 +622,7 @@ func TestApp_HistorySidebarConfig_NavigateCallbackOwnership(t *testing.T) { | |||||
| 623 | app.tabs.Add(tab2) | 622 | app.tabs.Add(tab2) | ||
| 624 | 623 | ||||
| 625 | // Build config for the SECOND window, even though first is globally focused. | 624 | // Build config for the SECOND window, even though first is globally focused. | ||
| 626 | - | cfg := app.buildHistorySidebarConfig(ctx, second) | 625 | + | cfg := app.buildHistorySidebarConfig(second) |
| 627 | 626 | ||||
| 628 | // Invoke OnNavigate — should navigate through second window's pane-2. | 627 | // Invoke OnNavigate — should navigate through second window's pane-2. | ||
| 629 | err := cfg.OnNavigate(ctx, "https://ownership.com") | 628 | err := cfg.OnNavigate(ctx, "https://ownership.com") | ||
| @@ -672,7 +671,7 @@ func TestApp_HistorySidebarConfig_KeepOpenCallback(t *testing.T) { | |||||
| 672 | } | 671 | } | ||
| 673 | app.tabs.Add(tab) | 672 | app.tabs.Add(tab) | ||
| 674 | 673 | ||||
| 675 | - | cfg := app.buildHistorySidebarConfig(ctx, bw) | 674 | + | cfg := app.buildHistorySidebarConfig(bw) |
| 676 | 675 | ||||
| 677 | // OnNavigateKeepOpen navigates but does NOT close the sidebar. | 676 | // OnNavigateKeepOpen navigates but does NOT close the sidebar. | ||
| 678 | navigateURL := "https://keep-open.com" | 677 | navigateURL := "https://keep-open.com" | ||
| @@ -722,7 +721,7 @@ func TestApp_HistorySidebarConfig_OpenInNewPaneCallback(t *testing.T) { | |||||
| 722 | } | 721 | } | ||
| 723 | app.tabs.Add(tab) | 722 | app.tabs.Add(tab) | ||
| 724 | 723 | ||||
| 725 | - | cfg := app.buildHistorySidebarConfig(ctx, bw) | 724 | + | cfg := app.buildHistorySidebarConfig(bw) |
| 726 | 725 | ||||
| 727 | // OnOpenInNewPane should activate the owning window and split with URL. | 726 | // OnOpenInNewPane should activate the owning window and split with URL. | ||
| 728 | splitURL := "https://shift-enter.com" | 727 | splitURL := "https://shift-enter.com" | ||
| @@ -748,8 +747,6 @@ func TestApp_HistorySidebarConfig_OpenInNewPaneCallback(t *testing.T) { | |||||
| 748 | // TestApp_HistorySidebarConfig_CloseCallback verifies that OnClose hides the | 747 | // TestApp_HistorySidebarConfig_CloseCallback verifies that OnClose hides the | ||
| 749 | // sidebar for the owning browser window and restores focus to the active pane. | 748 | // sidebar for the owning browser window and restores focus to the active pane. | ||
| 750 | func TestApp_HistorySidebarConfig_CloseCallback(t *testing.T) { | 749 | func TestApp_HistorySidebarConfig_CloseCallback(t *testing.T) { | ||
| 751 | - | ctx := t.Context() | |||
| 752 | - | ||||
| 753 | paneID := entity.PaneID("pane-1") | 750 | paneID := entity.PaneID("pane-1") | ||
| 754 | tab := entity.NewTab(entity.TabID("tab-1"), entity.WorkspaceID("ws-1"), entity.NewPane(paneID)) | 751 | tab := entity.NewTab(entity.TabID("tab-1"), entity.WorkspaceID("ws-1"), entity.NewPane(paneID)) | ||
| 755 | bwTabs := entity.NewTabList() | 752 | bwTabs := entity.NewTabList() | ||
| @@ -783,7 +780,7 @@ func TestApp_HistorySidebarConfig_CloseCallback(t *testing.T) { | |||||
| 783 | tab.ID: wsView, | 780 | tab.ID: wsView, | ||
| 784 | }, | 781 | }, | ||
| 785 | } | 782 | } | ||
| 786 | - | cfg := app.buildHistorySidebarConfig(ctx, bw) | 783 | + | cfg := app.buildHistorySidebarConfig(bw) |
| 787 | 784 | ||||
| 788 | // OnClose hides the sidebar. | 785 | // OnClose hides the sidebar. | ||
| 789 | cfg.OnClose() | 786 | cfg.OnClose() | ||
| @@ -795,8 +792,6 @@ func TestApp_HistorySidebarConfig_CloseCallback(t *testing.T) { | |||||
| 795 | // OnClose (hideAndRestoreFocusForBrowserWindow) is safe when the browser | 792 | // OnClose (hideAndRestoreFocusForBrowserWindow) is safe when the browser | ||
| 796 | // window has no sidebar or is nil. | 793 | // window has no sidebar or is nil. | ||
| 797 | func TestApp_HistorySidebarConfig_CloseWithNoSidebarIsSafe(t *testing.T) { | 794 | func TestApp_HistorySidebarConfig_CloseWithNoSidebarIsSafe(t *testing.T) { | ||
| 798 | - | ctx := t.Context() | |||
| 799 | - | ||||
| 800 | bw := &browserWindow{ | 795 | bw := &browserWindow{ | ||
| 801 | id: "no-sidebar", | 796 | id: "no-sidebar", | ||
| 802 | mainWindow: &window.MainWindow{}, | 797 | mainWindow: &window.MainWindow{}, | ||
| @@ -808,7 +803,7 @@ func TestApp_HistorySidebarConfig_CloseWithNoSidebarIsSafe(t *testing.T) { | |||||
| 808 | browserWindows: map[string]*browserWindow{bw.id: bw}, | 803 | browserWindows: map[string]*browserWindow{bw.id: bw}, | ||
| 809 | } | 804 | } | ||
| 810 | 805 | ||||
| 811 | - | cfg := app.buildHistorySidebarConfig(ctx, bw) | 806 | + | cfg := app.buildHistorySidebarConfig(bw) |
| 812 | 807 | ||||
| 813 | // Should not panic even with nil sidebar. | 808 | // Should not panic even with nil sidebar. | ||
| 814 | require.NotPanics(t, func() { cfg.OnClose() }) | 809 | require.NotPanics(t, func() { cfg.OnClose() }) | ||
M internal/ui/component/history_model.go +9 -8
| @@ -28,6 +28,7 @@ const ( | |||
| 28 | 28 | dayLabelToday = "Today" | |
| 29 | 29 | dayLabelYesterday = "Yesterday" | |
| 30 | 30 | dayLabelOtherYearFormat = "January 2, 2006" | |
| 31 | + | hoursPerDay = 24 | |
| 31 | 32 | ) | |
| 32 | 33 | ||
| 33 | 34 | // groupHistoryByDay groups entries by calendar day, newest-first. | |
| @@ -73,7 +74,7 @@ func groupHistoryByDay(entries []*entity.HistoryEntry) []historyGroup { | |||
| 73 | 74 | return groups | |
| 74 | 75 | } | |
| 75 | 76 | ||
| 76 | - | func dayLabelForKey(key dayKey, todayStart time.Time, now time.Time) string { | |
| 77 | + | func dayLabelForKey(key dayKey, todayStart, now time.Time) string { | |
| 77 | 78 | dayStart := time.Date(key.year, key.month, key.day, 0, 0, 0, 0, now.Location()) | |
| 78 | 79 | switch { | |
| 79 | 80 | case dayStart.Equal(todayStart): | |
| @@ -119,14 +120,14 @@ func relativeTime(t time.Time) string { | |||
| 119 | 120 | return "1m ago" | |
| 120 | 121 | } | |
| 121 | 122 | return strconv.Itoa(m) + "m ago" | |
| 122 | - | case d < 24*time.Hour: | |
| 123 | + | case d < hoursPerDay*time.Hour: | |
| 123 | 124 | h := int(d.Hours()) | |
| 124 | 125 | if h < 2 { | |
| 125 | 126 | return "1h ago" | |
| 126 | 127 | } | |
| 127 | 128 | return strconv.Itoa(h) + "h ago" | |
| 128 | - | case d < 7*24*time.Hour: | |
| 129 | - | days := int(d.Hours() / 24) | |
| 129 | + | case d < 7*hoursPerDay*time.Hour: | |
| 130 | + | days := int(d.Hours() / hoursPerDay) | |
| 130 | 131 | if days < 2 { | |
| 131 | 132 | return "1d ago" | |
| 132 | 133 | } | |
| @@ -231,13 +232,13 @@ func (m keyboardNavModel) groupIndexAt(index int) int { | |||
| 231 | 232 | } | |
| 232 | 233 | ||
| 233 | 234 | func (m keyboardNavModel) maxGroupIndex() int { | |
| 234 | - | max := -1 | |
| 235 | + | maxIndex := -1 | |
| 235 | 236 | for _, row := range m.rows { | |
| 236 | - | if row.GroupIndex > max { | |
| 237 | - | max = row.GroupIndex | |
| 237 | + | if row.GroupIndex > maxIndex { | |
| 238 | + | maxIndex = row.GroupIndex | |
| 238 | 239 | } | |
| 239 | 240 | } | |
| 240 | - | return max | |
| 241 | + | return maxIndex | |
| 241 | 242 | } | |
| 242 | 243 | ||
| 243 | 244 | func (m keyboardNavModel) cumulativeOffsetAtGroup(gi int) int { | |
| @@ -28,6 +28,7 @@ const ( | |||||
| 28 | dayLabelToday = "Today" | 28 | dayLabelToday = "Today" | ||
| 29 | dayLabelYesterday = "Yesterday" | 29 | dayLabelYesterday = "Yesterday" | ||
| 30 | dayLabelOtherYearFormat = "January 2, 2006" | 30 | dayLabelOtherYearFormat = "January 2, 2006" | ||
| 31 | + | hoursPerDay = 24 | |||
| 31 | ) | 32 | ) | ||
| 32 | 33 | ||||
| 33 | // groupHistoryByDay groups entries by calendar day, newest-first. | 34 | // groupHistoryByDay groups entries by calendar day, newest-first. | ||
| @@ -73,7 +74,7 @@ func groupHistoryByDay(entries []*entity.HistoryEntry) []historyGroup { | |||||
| 73 | return groups | 74 | return groups | ||
| 74 | } | 75 | } | ||
| 75 | 76 | ||||
| 76 | - | func dayLabelForKey(key dayKey, todayStart time.Time, now time.Time) string { | 77 | + | func dayLabelForKey(key dayKey, todayStart, now time.Time) string { |
| 77 | dayStart := time.Date(key.year, key.month, key.day, 0, 0, 0, 0, now.Location()) | 78 | dayStart := time.Date(key.year, key.month, key.day, 0, 0, 0, 0, now.Location()) | ||
| 78 | switch { | 79 | switch { | ||
| 79 | case dayStart.Equal(todayStart): | 80 | case dayStart.Equal(todayStart): | ||
| @@ -119,14 +120,14 @@ func relativeTime(t time.Time) string { | |||||
| 119 | return "1m ago" | 120 | return "1m ago" | ||
| 120 | } | 121 | } | ||
| 121 | return strconv.Itoa(m) + "m ago" | 122 | return strconv.Itoa(m) + "m ago" | ||
| 122 | - | case d < 24*time.Hour: | 123 | + | case d < hoursPerDay*time.Hour: |
| 123 | h := int(d.Hours()) | 124 | h := int(d.Hours()) | ||
| 124 | if h < 2 { | 125 | if h < 2 { | ||
| 125 | return "1h ago" | 126 | return "1h ago" | ||
| 126 | } | 127 | } | ||
| 127 | return strconv.Itoa(h) + "h ago" | 128 | return strconv.Itoa(h) + "h ago" | ||
| 128 | - | case d < 7*24*time.Hour: | 129 | + | case d < 7*hoursPerDay*time.Hour: |
| 129 | - | days := int(d.Hours() / 24) | 130 | + | days := int(d.Hours() / hoursPerDay) |
| 130 | if days < 2 { | 131 | if days < 2 { | ||
| 131 | return "1d ago" | 132 | return "1d ago" | ||
| 132 | } | 133 | } | ||
| @@ -231,13 +232,13 @@ func (m keyboardNavModel) groupIndexAt(index int) int { | |||||
| 231 | } | 232 | } | ||
| 232 | 233 | ||||
| 233 | func (m keyboardNavModel) maxGroupIndex() int { | 234 | func (m keyboardNavModel) maxGroupIndex() int { | ||
| 234 | - | max := -1 | 235 | + | maxIndex := -1 |
| 235 | for _, row := range m.rows { | 236 | for _, row := range m.rows { | ||
| 236 | - | if row.GroupIndex > max { | 237 | + | if row.GroupIndex > maxIndex { |
| 237 | - | max = row.GroupIndex | 238 | + | maxIndex = row.GroupIndex |
| 238 | } | 239 | } | ||
| 239 | } | 240 | } | ||
| 240 | - | return max | 241 | + | return maxIndex |
| 241 | } | 242 | } | ||
| 242 | 243 | ||||
| 243 | func (m keyboardNavModel) cumulativeOffsetAtGroup(gi int) int { | 244 | func (m keyboardNavModel) cumulativeOffsetAtGroup(gi int) int { | ||
M internal/ui/component/history_model_test.go +10 -12
| @@ -270,7 +270,7 @@ func TestKeyboardNavModel_EmptyGroups(t *testing.T) { | |||
| 270 | 270 | assert.Equal(t, -1, m.firstSelectableIndex()) | |
| 271 | 271 | assert.Equal(t, -1, m.lastSelectableIndex()) | |
| 272 | 272 | assert.Nil(t, m.entryAt(0)) | |
| 273 | - | assert.Equal(t, "", m.entryURLAt(0)) | |
| 273 | + | assert.Empty(t, m.entryURLAt(0)) | |
| 274 | 274 | ||
| 275 | 275 | m = newKeyboardNavModel([]historyGroup{}) | |
| 276 | 276 | assert.Equal(t, 0, m.totalRows()) | |
| @@ -285,7 +285,7 @@ func TestKeyboardNavModel_SingleGroup(t *testing.T) { | |||
| 285 | 285 | // Row 0: header (not selectable) | |
| 286 | 286 | assert.False(t, m.isSelectable(0)) | |
| 287 | 287 | assert.Nil(t, m.entryAt(0)) | |
| 288 | - | assert.Equal(t, "", m.entryURLAt(0)) | |
| 288 | + | assert.Empty(t, m.entryURLAt(0)) | |
| 289 | 289 | ||
| 290 | 290 | // Rows 1-3: entries (selectable) | |
| 291 | 291 | for i := 1; i <= 3; i++ { | |
| @@ -395,13 +395,13 @@ func TestKeyboardNavModel_EntryAtURL(t *testing.T) { | |||
| 395 | 395 | ||
| 396 | 396 | // Header rows return nil / "" | |
| 397 | 397 | assert.Nil(t, m.entryAt(0)) // H0 | |
| 398 | - | assert.Equal(t, "", m.entryURLAt(0)) | |
| 398 | + | assert.Empty(t, m.entryURLAt(0)) | |
| 399 | 399 | assert.Nil(t, m.entryAt(2)) // H1 | |
| 400 | - | assert.Equal(t, "", m.entryURLAt(2)) | |
| 400 | + | assert.Empty(t, m.entryURLAt(2)) | |
| 401 | 401 | ||
| 402 | 402 | // Out of range | |
| 403 | 403 | assert.Nil(t, m.entryAt(99)) | |
| 404 | - | assert.Equal(t, "", m.entryURLAt(99)) | |
| 404 | + | assert.Empty(t, m.entryURLAt(99)) | |
| 405 | 405 | assert.Nil(t, m.entryAt(-1)) | |
| 406 | 406 | } | |
| 407 | 407 | ||
| @@ -463,7 +463,7 @@ func TestKeyboardNavModelFromRows_UsesExplicitDisplayRows(t *testing.T) { | |||
| 463 | 463 | ||
| 464 | 464 | func TestTransitionSearchState_EmptyQuery(t *testing.T) { | |
| 465 | 465 | next := transitionSearchState("", 0) | |
| 466 | - | assert.Equal(t, "", next.Query) | |
| 466 | + | assert.Empty(t, next.Query) | |
| 467 | 467 | assert.False(t, next.HasSearchDone) | |
| 468 | 468 | assert.False(t, next.HasResults) | |
| 469 | 469 | assert.Equal(t, 0, next.ResultCount) | |
| @@ -495,7 +495,7 @@ func TestTransitionSearchState_QueryToQuery(t *testing.T) { | |||
| 495 | 495 | ||
| 496 | 496 | func TestTransitionSearchState_QueryToEmpty(t *testing.T) { | |
| 497 | 497 | next := transitionSearchState("", 0) | |
| 498 | - | assert.Equal(t, "", next.Query) | |
| 498 | + | assert.Empty(t, next.Query) | |
| 499 | 499 | assert.False(t, next.HasSearchDone) | |
| 500 | 500 | assert.False(t, next.HasResults) | |
| 501 | 501 | assert.Equal(t, 0, next.ResultCount) | |
| @@ -507,7 +507,7 @@ func TestTransitionSearchState_QueryToEmpty(t *testing.T) { | |||
| 507 | 507 | ||
| 508 | 508 | func TestApplyReloadState_WithoutQuery(t *testing.T) { | |
| 509 | 509 | s := applyReloadState("") | |
| 510 | - | assert.Equal(t, "", s.PreservedQuery) | |
| 510 | + | assert.Empty(t, s.PreservedQuery) | |
| 511 | 511 | assert.True(t, s.ResetBrowse) | |
| 512 | 512 | assert.False(t, s.ClearSearch) | |
| 513 | 513 | } | |
| @@ -667,10 +667,8 @@ func TestTransitionSearchState_SequentialSearches(t *testing.T) { | |||
| 667 | 667 | // TestTransitionSearchState_SearchThenClearThenReSearch verifies the | |
| 668 | 668 | // transition from search -> empty -> new search produces correct state. | |
| 669 | 669 | func TestTransitionSearchState_SearchThenClearThenReSearch(t *testing.T) { | |
| 670 | - | s := searchStateSnapshot{} | |
| 671 | - | ||
| 672 | 670 | // Search "term" -> 2 results | |
| 673 | - | s = transitionSearchState("term", 2) | |
| 671 | + | s := transitionSearchState("term", 2) | |
| 674 | 672 | assert.Equal(t, "term", s.Query) | |
| 675 | 673 | assert.True(t, s.HasSearchDone) | |
| 676 | 674 | assert.True(t, s.HasResults) | |
| @@ -678,7 +676,7 @@ func TestTransitionSearchState_SearchThenClearThenReSearch(t *testing.T) { | |||
| 678 | 676 | ||
| 679 | 677 | // Clear: query becomes "" | |
| 680 | 678 | s = transitionSearchState("", 0) | |
| 681 | - | assert.Equal(t, "", s.Query) | |
| 679 | + | assert.Empty(t, s.Query) | |
| 682 | 680 | assert.False(t, s.HasSearchDone) | |
| 683 | 681 | assert.False(t, s.HasResults) | |
| 684 | 682 | assert.Equal(t, 0, s.ResultCount) | |
| @@ -270,7 +270,7 @@ func TestKeyboardNavModel_EmptyGroups(t *testing.T) { | |||||
| 270 | assert.Equal(t, -1, m.firstSelectableIndex()) | 270 | assert.Equal(t, -1, m.firstSelectableIndex()) | ||
| 271 | assert.Equal(t, -1, m.lastSelectableIndex()) | 271 | assert.Equal(t, -1, m.lastSelectableIndex()) | ||
| 272 | assert.Nil(t, m.entryAt(0)) | 272 | assert.Nil(t, m.entryAt(0)) | ||
| 273 | - | assert.Equal(t, "", m.entryURLAt(0)) | 273 | + | assert.Empty(t, m.entryURLAt(0)) |
| 274 | 274 | ||||
| 275 | m = newKeyboardNavModel([]historyGroup{}) | 275 | m = newKeyboardNavModel([]historyGroup{}) | ||
| 276 | assert.Equal(t, 0, m.totalRows()) | 276 | assert.Equal(t, 0, m.totalRows()) | ||
| @@ -285,7 +285,7 @@ func TestKeyboardNavModel_SingleGroup(t *testing.T) { | |||||
| 285 | // Row 0: header (not selectable) | 285 | // Row 0: header (not selectable) | ||
| 286 | assert.False(t, m.isSelectable(0)) | 286 | assert.False(t, m.isSelectable(0)) | ||
| 287 | assert.Nil(t, m.entryAt(0)) | 287 | assert.Nil(t, m.entryAt(0)) | ||
| 288 | - | assert.Equal(t, "", m.entryURLAt(0)) | 288 | + | assert.Empty(t, m.entryURLAt(0)) |
| 289 | 289 | ||||
| 290 | // Rows 1-3: entries (selectable) | 290 | // Rows 1-3: entries (selectable) | ||
| 291 | for i := 1; i <= 3; i++ { | 291 | for i := 1; i <= 3; i++ { | ||
| @@ -395,13 +395,13 @@ func TestKeyboardNavModel_EntryAtURL(t *testing.T) { | |||||
| 395 | 395 | ||||
| 396 | // Header rows return nil / "" | 396 | // Header rows return nil / "" | ||
| 397 | assert.Nil(t, m.entryAt(0)) // H0 | 397 | assert.Nil(t, m.entryAt(0)) // H0 | ||
| 398 | - | assert.Equal(t, "", m.entryURLAt(0)) | 398 | + | assert.Empty(t, m.entryURLAt(0)) |
| 399 | assert.Nil(t, m.entryAt(2)) // H1 | 399 | assert.Nil(t, m.entryAt(2)) // H1 | ||
| 400 | - | assert.Equal(t, "", m.entryURLAt(2)) | 400 | + | assert.Empty(t, m.entryURLAt(2)) |
| 401 | 401 | ||||
| 402 | // Out of range | 402 | // Out of range | ||
| 403 | assert.Nil(t, m.entryAt(99)) | 403 | assert.Nil(t, m.entryAt(99)) | ||
| 404 | - | assert.Equal(t, "", m.entryURLAt(99)) | 404 | + | assert.Empty(t, m.entryURLAt(99)) |
| 405 | assert.Nil(t, m.entryAt(-1)) | 405 | assert.Nil(t, m.entryAt(-1)) | ||
| 406 | } | 406 | } | ||
| 407 | 407 | ||||
| @@ -463,7 +463,7 @@ func TestKeyboardNavModelFromRows_UsesExplicitDisplayRows(t *testing.T) { | |||||
| 463 | 463 | ||||
| 464 | func TestTransitionSearchState_EmptyQuery(t *testing.T) { | 464 | func TestTransitionSearchState_EmptyQuery(t *testing.T) { | ||
| 465 | next := transitionSearchState("", 0) | 465 | next := transitionSearchState("", 0) | ||
| 466 | - | assert.Equal(t, "", next.Query) | 466 | + | assert.Empty(t, next.Query) |
| 467 | assert.False(t, next.HasSearchDone) | 467 | assert.False(t, next.HasSearchDone) | ||
| 468 | assert.False(t, next.HasResults) | 468 | assert.False(t, next.HasResults) | ||
| 469 | assert.Equal(t, 0, next.ResultCount) | 469 | assert.Equal(t, 0, next.ResultCount) | ||
| @@ -495,7 +495,7 @@ func TestTransitionSearchState_QueryToQuery(t *testing.T) { | |||||
| 495 | 495 | ||||
| 496 | func TestTransitionSearchState_QueryToEmpty(t *testing.T) { | 496 | func TestTransitionSearchState_QueryToEmpty(t *testing.T) { | ||
| 497 | next := transitionSearchState("", 0) | 497 | next := transitionSearchState("", 0) | ||
| 498 | - | assert.Equal(t, "", next.Query) | 498 | + | assert.Empty(t, next.Query) |
| 499 | assert.False(t, next.HasSearchDone) | 499 | assert.False(t, next.HasSearchDone) | ||
| 500 | assert.False(t, next.HasResults) | 500 | assert.False(t, next.HasResults) | ||
| 501 | assert.Equal(t, 0, next.ResultCount) | 501 | assert.Equal(t, 0, next.ResultCount) | ||
| @@ -507,7 +507,7 @@ func TestTransitionSearchState_QueryToEmpty(t *testing.T) { | |||||
| 507 | 507 | ||||
| 508 | func TestApplyReloadState_WithoutQuery(t *testing.T) { | 508 | func TestApplyReloadState_WithoutQuery(t *testing.T) { | ||
| 509 | s := applyReloadState("") | 509 | s := applyReloadState("") | ||
| 510 | - | assert.Equal(t, "", s.PreservedQuery) | 510 | + | assert.Empty(t, s.PreservedQuery) |
| 511 | assert.True(t, s.ResetBrowse) | 511 | assert.True(t, s.ResetBrowse) | ||
| 512 | assert.False(t, s.ClearSearch) | 512 | assert.False(t, s.ClearSearch) | ||
| 513 | } | 513 | } | ||
| @@ -667,10 +667,8 @@ func TestTransitionSearchState_SequentialSearches(t *testing.T) { | |||||
| 667 | // TestTransitionSearchState_SearchThenClearThenReSearch verifies the | 667 | // TestTransitionSearchState_SearchThenClearThenReSearch verifies the | ||
| 668 | // transition from search -> empty -> new search produces correct state. | 668 | // transition from search -> empty -> new search produces correct state. | ||
| 669 | func TestTransitionSearchState_SearchThenClearThenReSearch(t *testing.T) { | 669 | func TestTransitionSearchState_SearchThenClearThenReSearch(t *testing.T) { | ||
| 670 | - | s := searchStateSnapshot{} | |||
| 671 | - | ||||
| 672 | // Search "term" -> 2 results | 670 | // Search "term" -> 2 results | ||
| 673 | - | s = transitionSearchState("term", 2) | 671 | + | s := transitionSearchState("term", 2) |
| 674 | assert.Equal(t, "term", s.Query) | 672 | assert.Equal(t, "term", s.Query) | ||
| 675 | assert.True(t, s.HasSearchDone) | 673 | assert.True(t, s.HasSearchDone) | ||
| 676 | assert.True(t, s.HasResults) | 674 | assert.True(t, s.HasResults) | ||
| @@ -678,7 +676,7 @@ func TestTransitionSearchState_SearchThenClearThenReSearch(t *testing.T) { | |||||
| 678 | 676 | ||||
| 679 | // Clear: query becomes "" | 677 | // Clear: query becomes "" | ||
| 680 | s = transitionSearchState("", 0) | 678 | s = transitionSearchState("", 0) | ||
| 681 | - | assert.Equal(t, "", s.Query) | 679 | + | assert.Empty(t, s.Query) |
| 682 | assert.False(t, s.HasSearchDone) | 680 | assert.False(t, s.HasSearchDone) | ||
| 683 | assert.False(t, s.HasResults) | 681 | assert.False(t, s.HasResults) | ||
| 684 | assert.Equal(t, 0, s.ResultCount) | 682 | assert.Equal(t, 0, s.ResultCount) | ||
M internal/ui/component/history_sidebar.go +2 -2
| @@ -234,14 +234,14 @@ func (hs *HistorySidebar) Widget() *gtk.Widget { | |||
| 234 | 234 | // current recent visits, not stale data from initialization. | |
| 235 | 235 | func (hs *HistorySidebar) Show() { | |
| 236 | 236 | hs.mu.Lock() | |
| 237 | - | defer hs.mu.Unlock() | |
| 238 | - | ||
| 239 | 237 | if hs.outerBox == nil || hs.destroyed { | |
| 238 | + | hs.mu.Unlock() | |
| 240 | 239 | return | |
| 241 | 240 | } | |
| 242 | 241 | ||
| 243 | 242 | hs.outerBox.SetVisible(true) | |
| 244 | 243 | hs.visible = true | |
| 244 | + | hs.mu.Unlock() | |
| 245 | 245 | ||
| 246 | 246 | // Schedule a background reload so the sidebar shows fresh data | |
| 247 | 247 | // when it becomes visible, not stale data captured at init time. | |
| @@ -234,14 +234,14 @@ func (hs *HistorySidebar) Widget() *gtk.Widget { | |||||
| 234 | // current recent visits, not stale data from initialization. | 234 | // current recent visits, not stale data from initialization. | ||
| 235 | func (hs *HistorySidebar) Show() { | 235 | func (hs *HistorySidebar) Show() { | ||
| 236 | hs.mu.Lock() | 236 | hs.mu.Lock() | ||
| 237 | - | defer hs.mu.Unlock() | |||
| 238 | - | ||||
| 239 | if hs.outerBox == nil || hs.destroyed { | 237 | if hs.outerBox == nil || hs.destroyed { | ||
| 238 | + | hs.mu.Unlock() | |||
| 240 | return | 239 | return | ||
| 241 | } | 240 | } | ||
| 242 | 241 | ||||
| 243 | hs.outerBox.SetVisible(true) | 242 | hs.outerBox.SetVisible(true) | ||
| 244 | hs.visible = true | 243 | hs.visible = true | ||
| 244 | + | hs.mu.Unlock() | |||
| 245 | 245 | ||||
| 246 | // Schedule a background reload so the sidebar shows fresh data | 246 | // Schedule a background reload so the sidebar shows fresh data | ||
| 247 | // when it becomes visible, not stale data captured at init time. | 247 | // when it becomes visible, not stale data captured at init time. | ||
M internal/ui/component/history_sidebar_keyboard.go +5 -5
| @@ -40,7 +40,7 @@ func (hs *HistorySidebar) setupKeyboardNavigation() { | |||
| 40 | 40 | ||
| 41 | 41 | // --- Enter variants --- | |
| 42 | 42 | case uint(gdk.KEY_Return), uint(gdk.KEY_KP_Enter): | |
| 43 | - | return hs.handleEnterKey(keyval, state) | |
| 43 | + | return hs.handleEnterKey(state) | |
| 44 | 44 | ||
| 45 | 45 | // --- Delete: remove selected entry --- | |
| 46 | 46 | case uint(gdk.KEY_Delete), uint(gdk.KEY_KP_Delete): | |
| @@ -94,7 +94,7 @@ func (hs *HistorySidebar) setupKeyboardNavigation() { | |||
| 94 | 94 | ||
| 95 | 95 | // handleEnterKey processes Enter, Ctrl+Enter, and Shift+Enter on a selected row. | |
| 96 | 96 | // Returns true if the key was consumed. | |
| 97 | - | func (hs *HistorySidebar) handleEnterKey(keyval uint, state gdk.ModifierType) bool { | |
| 97 | + | func (hs *HistorySidebar) handleEnterKey(state gdk.ModifierType) bool { | |
| 98 | 98 | // Determine activation mode from modifiers | |
| 99 | 99 | var action HistorySidebarKeyboardAction | |
| 100 | 100 | ||
| @@ -175,7 +175,7 @@ func (hs *HistorySidebar) handleDeleteKey() bool { | |||
| 175 | 175 | hs.mu.Unlock() | |
| 176 | 176 | return false | |
| 177 | 177 | } | |
| 178 | - | hs.applyDeletedEntryLocked(url, entryID, nextSelectedURL) | |
| 178 | + | hs.applyDeletedEntryLocked(entryID, nextSelectedURL) | |
| 179 | 179 | hs.mu.Unlock() | |
| 180 | 180 | ||
| 181 | 181 | hs.rebuildList() | |
| @@ -189,7 +189,7 @@ func (hs *HistorySidebar) handleDeleteKey() bool { | |||
| 189 | 189 | ||
| 190 | 190 | // applyDeletedEntryLocked updates local sidebar state after a successful | |
| 191 | 191 | // history delete. Must be called with hs.mu write lock held. | |
| 192 | - | func (hs *HistorySidebar) applyDeletedEntryLocked(url string, entryID int64, nextSelectedURL string) { | |
| 192 | + | func (hs *HistorySidebar) applyDeletedEntryLocked(entryID int64, nextSelectedURL string) { | |
| 193 | 193 | hs.preserveScrollAndSelection() | |
| 194 | 194 | hs.prevSelectedURL = nextSelectedURL | |
| 195 | 195 | hs.searchGen++ | |
| @@ -428,7 +428,7 @@ func (hs *HistorySidebar) selectAdjacentRow(direction int) { | |||
| 428 | 428 | ||
| 429 | 429 | hs.mu.RLock() | |
| 430 | 430 | model := newKeyboardNavModelFromRows(hs.displayRows) | |
| 431 | - | target := -1 | |
| 431 | + | var target int | |
| 432 | 432 | if current < 0 { | |
| 433 | 433 | if direction > 0 { | |
| 434 | 434 | target = model.firstSelectableIndex() | |
| @@ -40,7 +40,7 @@ func (hs *HistorySidebar) setupKeyboardNavigation() { | |||||
| 40 | 40 | ||||
| 41 | // --- Enter variants --- | 41 | // --- Enter variants --- | ||
| 42 | case uint(gdk.KEY_Return), uint(gdk.KEY_KP_Enter): | 42 | case uint(gdk.KEY_Return), uint(gdk.KEY_KP_Enter): | ||
| 43 | - | return hs.handleEnterKey(keyval, state) | 43 | + | return hs.handleEnterKey(state) |
| 44 | 44 | ||||
| 45 | // --- Delete: remove selected entry --- | 45 | // --- Delete: remove selected entry --- | ||
| 46 | case uint(gdk.KEY_Delete), uint(gdk.KEY_KP_Delete): | 46 | case uint(gdk.KEY_Delete), uint(gdk.KEY_KP_Delete): | ||
| @@ -94,7 +94,7 @@ func (hs *HistorySidebar) setupKeyboardNavigation() { | |||||
| 94 | 94 | ||||
| 95 | // handleEnterKey processes Enter, Ctrl+Enter, and Shift+Enter on a selected row. | 95 | // handleEnterKey processes Enter, Ctrl+Enter, and Shift+Enter on a selected row. | ||
| 96 | // Returns true if the key was consumed. | 96 | // Returns true if the key was consumed. | ||
| 97 | - | func (hs *HistorySidebar) handleEnterKey(keyval uint, state gdk.ModifierType) bool { | 97 | + | func (hs *HistorySidebar) handleEnterKey(state gdk.ModifierType) bool { |
| 98 | // Determine activation mode from modifiers | 98 | // Determine activation mode from modifiers | ||
| 99 | var action HistorySidebarKeyboardAction | 99 | var action HistorySidebarKeyboardAction | ||
| 100 | 100 | ||||
| @@ -175,7 +175,7 @@ func (hs *HistorySidebar) handleDeleteKey() bool { | |||||
| 175 | hs.mu.Unlock() | 175 | hs.mu.Unlock() | ||
| 176 | return false | 176 | return false | ||
| 177 | } | 177 | } | ||
| 178 | - | hs.applyDeletedEntryLocked(url, entryID, nextSelectedURL) | 178 | + | hs.applyDeletedEntryLocked(entryID, nextSelectedURL) |
| 179 | hs.mu.Unlock() | 179 | hs.mu.Unlock() | ||
| 180 | 180 | ||||
| 181 | hs.rebuildList() | 181 | hs.rebuildList() | ||
| @@ -189,7 +189,7 @@ func (hs *HistorySidebar) handleDeleteKey() bool { | |||||
| 189 | 189 | ||||
| 190 | // applyDeletedEntryLocked updates local sidebar state after a successful | 190 | // applyDeletedEntryLocked updates local sidebar state after a successful | ||
| 191 | // history delete. Must be called with hs.mu write lock held. | 191 | // history delete. Must be called with hs.mu write lock held. | ||
| 192 | - | func (hs *HistorySidebar) applyDeletedEntryLocked(url string, entryID int64, nextSelectedURL string) { | 192 | + | func (hs *HistorySidebar) applyDeletedEntryLocked(entryID int64, nextSelectedURL string) { |
| 193 | hs.preserveScrollAndSelection() | 193 | hs.preserveScrollAndSelection() | ||
| 194 | hs.prevSelectedURL = nextSelectedURL | 194 | hs.prevSelectedURL = nextSelectedURL | ||
| 195 | hs.searchGen++ | 195 | hs.searchGen++ | ||
| @@ -428,7 +428,7 @@ func (hs *HistorySidebar) selectAdjacentRow(direction int) { | |||||
| 428 | 428 | ||||
| 429 | hs.mu.RLock() | 429 | hs.mu.RLock() | ||
| 430 | model := newKeyboardNavModelFromRows(hs.displayRows) | 430 | model := newKeyboardNavModelFromRows(hs.displayRows) | ||
| 431 | - | target := -1 | 431 | + | var target int |
| 432 | if current < 0 { | 432 | if current < 0 { | ||
| 433 | if direction > 0 { | 433 | if direction > 0 { | ||
| 434 | target = model.firstSelectableIndex() | 434 | target = model.firstSelectableIndex() | ||
M internal/ui/component/history_sidebar_loading_search.go +2 -2
| @@ -48,7 +48,7 @@ func (hs *HistorySidebar) fetchPage(offset int, gen uint64) { | |||
| 48 | 48 | hs.scheduleRebuild() | |
| 49 | 49 | return false | |
| 50 | 50 | }) | |
| 51 | - | glib.IdleAdd(&cb, 0) | |
| 51 | + | hs.scheduleIdle(cb) | |
| 52 | 52 | return | |
| 53 | 53 | } | |
| 54 | 54 | ||
| @@ -105,7 +105,7 @@ func (hs *HistorySidebar) fetchPage(offset int, gen uint64) { | |||
| 105 | 105 | hs.rebuildList() | |
| 106 | 106 | return false | |
| 107 | 107 | }) | |
| 108 | - | glib.IdleAdd(&cb, 0) | |
| 108 | + | hs.scheduleIdle(cb) | |
| 109 | 109 | } | |
| 110 | 110 | ||
| 111 | 111 | // LoadMore fetches the next page and appends it to the existing entries. | |
| @@ -48,7 +48,7 @@ func (hs *HistorySidebar) fetchPage(offset int, gen uint64) { | |||||
| 48 | hs.scheduleRebuild() | 48 | hs.scheduleRebuild() | ||
| 49 | return false | 49 | return false | ||
| 50 | }) | 50 | }) | ||
| 51 | - | glib.IdleAdd(&cb, 0) | 51 | + | hs.scheduleIdle(cb) |
| 52 | return | 52 | return | ||
| 53 | } | 53 | } | ||
| 54 | 54 | ||||
| @@ -105,7 +105,7 @@ func (hs *HistorySidebar) fetchPage(offset int, gen uint64) { | |||||
| 105 | hs.rebuildList() | 105 | hs.rebuildList() | ||
| 106 | return false | 106 | return false | ||
| 107 | }) | 107 | }) | ||
| 108 | - | glib.IdleAdd(&cb, 0) | 108 | + | hs.scheduleIdle(cb) |
| 109 | } | 109 | } | ||
| 110 | 110 | ||||
| 111 | // LoadMore fetches the next page and appends it to the existing entries. | 111 | // LoadMore fetches the next page and appends it to the existing entries. | ||
M internal/ui/component/history_sidebar_rendering.go +2 -2
| @@ -78,7 +78,7 @@ func (hs *HistorySidebar) showLoadingOrEmpty(query string, searchDone bool) { | |||
| 78 | 78 | case query != "" && !searchDone: | |
| 79 | 79 | label.SetText("Searching...") | |
| 80 | 80 | case query != "": | |
| 81 | - | label.SetText(fmt.Sprintf("No results for \"%s\"", query)) | |
| 81 | + | label.SetText(fmt.Sprintf("No results for %q", query)) | |
| 82 | 82 | default: | |
| 83 | 83 | label.SetText("No browsing history") | |
| 84 | 84 | } | |
| @@ -105,7 +105,7 @@ func (hs *HistorySidebar) showEmptyState(query string) { | |||
| 105 | 105 | label.AddCssClass("history-sidebar-empty") | |
| 106 | 106 | ||
| 107 | 107 | if query != "" { | |
| 108 | - | label.SetText(fmt.Sprintf("No results for \"%s\"", query)) | |
| 108 | + | label.SetText(fmt.Sprintf("No results for %q", query)) | |
| 109 | 109 | } else { | |
| 110 | 110 | label.SetText("No browsing history") | |
| 111 | 111 | } | |
| @@ -78,7 +78,7 @@ func (hs *HistorySidebar) showLoadingOrEmpty(query string, searchDone bool) { | |||||
| 78 | case query != "" && !searchDone: | 78 | case query != "" && !searchDone: | ||
| 79 | label.SetText("Searching...") | 79 | label.SetText("Searching...") | ||
| 80 | case query != "": | 80 | case query != "": | ||
| 81 | - | label.SetText(fmt.Sprintf("No results for \"%s\"", query)) | 81 | + | label.SetText(fmt.Sprintf("No results for %q", query)) |
| 82 | default: | 82 | default: | ||
| 83 | label.SetText("No browsing history") | 83 | label.SetText("No browsing history") | ||
| 84 | } | 84 | } | ||
| @@ -105,7 +105,7 @@ func (hs *HistorySidebar) showEmptyState(query string) { | |||||
| 105 | label.AddCssClass("history-sidebar-empty") | 105 | label.AddCssClass("history-sidebar-empty") | ||
| 106 | 106 | ||||
| 107 | if query != "" { | 107 | if query != "" { | ||
| 108 | - | label.SetText(fmt.Sprintf("No results for \"%s\"", query)) | 108 | + | label.SetText(fmt.Sprintf("No results for %q", query)) |
| 109 | } else { | 109 | } else { | ||
| 110 | label.SetText("No browsing history") | 110 | label.SetText("No browsing history") | ||
| 111 | } | 111 | } | ||
M internal/ui/component/history_sidebar_search_test.go +4 -4
| @@ -40,7 +40,7 @@ func TestApplySearchResults_NonStaleApplied(t *testing.T) { | |||
| 40 | 40 | applied := hs.applySearchResults(entries, 1, nil) | |
| 41 | 41 | assert.True(t, applied, "non-stale results must be applied") | |
| 42 | 42 | assert.True(t, hs.searchDone) | |
| 43 | - | assert.Nil(t, hs.searchErr) | |
| 43 | + | require.NoError(t, hs.searchErr) | |
| 44 | 44 | require.NotNil(t, hs.searchResults) | |
| 45 | 45 | assert.Len(t, hs.searchResults, 1) | |
| 46 | 46 | assert.Equal(t, "https://example.com", hs.searchResults[0].URL) | |
| @@ -100,7 +100,7 @@ func TestApplySearchResults_ErrorStored(t *testing.T) { | |||
| 100 | 100 | applied := hs.applySearchResults(entries, 1, wantErr) | |
| 101 | 101 | assert.True(t, applied) | |
| 102 | 102 | assert.True(t, hs.searchDone) | |
| 103 | - | assert.ErrorIs(t, hs.searchErr, wantErr) | |
| 103 | + | require.ErrorIs(t, hs.searchErr, wantErr) | |
| 104 | 104 | require.NotNil(t, hs.searchResults) | |
| 105 | 105 | assert.Empty(t, hs.searchResults) | |
| 106 | 106 | } | |
| @@ -112,7 +112,7 @@ func TestApplySearchResults_EmptyResultsApplied(t *testing.T) { | |||
| 112 | 112 | applied := hs.applySearchResults([]*entity.HistoryEntry{}, 1, nil) | |
| 113 | 113 | assert.True(t, applied) | |
| 114 | 114 | assert.True(t, hs.searchDone) | |
| 115 | - | assert.Nil(t, hs.searchErr) | |
| 115 | + | require.NoError(t, hs.searchErr) | |
| 116 | 116 | require.NotNil(t, hs.searchResults) | |
| 117 | 117 | assert.Empty(t, hs.searchResults) | |
| 118 | 118 | // Groups from empty entries should be nil or empty | |
| @@ -135,7 +135,7 @@ func TestApplyDeletedEntryLocked_RecomputesBrowseState(t *testing.T) { | |||
| 135 | 135 | hs.setDisplayGroupsLocked(groupHistoryByDay(hs.allEntries)) | |
| 136 | 136 | ||
| 137 | 137 | hs.mu.Lock() | |
| 138 | - | hs.applyDeletedEntryLocked(deleteMe.URL, deleteMe.ID, keepB.URL) | |
| 138 | + | hs.applyDeletedEntryLocked(deleteMe.ID, keepB.URL) | |
| 139 | 139 | hs.mu.Unlock() | |
| 140 | 140 | ||
| 141 | 141 | assert.Equal(t, uint64(8), hs.loadGen, "deletes must invalidate in-flight browse loads") | |
| @@ -40,7 +40,7 @@ func TestApplySearchResults_NonStaleApplied(t *testing.T) { | |||||
| 40 | applied := hs.applySearchResults(entries, 1, nil) | 40 | applied := hs.applySearchResults(entries, 1, nil) | ||
| 41 | assert.True(t, applied, "non-stale results must be applied") | 41 | assert.True(t, applied, "non-stale results must be applied") | ||
| 42 | assert.True(t, hs.searchDone) | 42 | assert.True(t, hs.searchDone) | ||
| 43 | - | assert.Nil(t, hs.searchErr) | 43 | + | require.NoError(t, hs.searchErr) |
| 44 | require.NotNil(t, hs.searchResults) | 44 | require.NotNil(t, hs.searchResults) | ||
| 45 | assert.Len(t, hs.searchResults, 1) | 45 | assert.Len(t, hs.searchResults, 1) | ||
| 46 | assert.Equal(t, "https://example.com", hs.searchResults[0].URL) | 46 | assert.Equal(t, "https://example.com", hs.searchResults[0].URL) | ||
| @@ -100,7 +100,7 @@ func TestApplySearchResults_ErrorStored(t *testing.T) { | |||||
| 100 | applied := hs.applySearchResults(entries, 1, wantErr) | 100 | applied := hs.applySearchResults(entries, 1, wantErr) | ||
| 101 | assert.True(t, applied) | 101 | assert.True(t, applied) | ||
| 102 | assert.True(t, hs.searchDone) | 102 | assert.True(t, hs.searchDone) | ||
| 103 | - | assert.ErrorIs(t, hs.searchErr, wantErr) | 103 | + | require.ErrorIs(t, hs.searchErr, wantErr) |
| 104 | require.NotNil(t, hs.searchResults) | 104 | require.NotNil(t, hs.searchResults) | ||
| 105 | assert.Empty(t, hs.searchResults) | 105 | assert.Empty(t, hs.searchResults) | ||
| 106 | } | 106 | } | ||
| @@ -112,7 +112,7 @@ func TestApplySearchResults_EmptyResultsApplied(t *testing.T) { | |||||
| 112 | applied := hs.applySearchResults([]*entity.HistoryEntry{}, 1, nil) | 112 | applied := hs.applySearchResults([]*entity.HistoryEntry{}, 1, nil) | ||
| 113 | assert.True(t, applied) | 113 | assert.True(t, applied) | ||
| 114 | assert.True(t, hs.searchDone) | 114 | assert.True(t, hs.searchDone) | ||
| 115 | - | assert.Nil(t, hs.searchErr) | 115 | + | require.NoError(t, hs.searchErr) |
| 116 | require.NotNil(t, hs.searchResults) | 116 | require.NotNil(t, hs.searchResults) | ||
| 117 | assert.Empty(t, hs.searchResults) | 117 | assert.Empty(t, hs.searchResults) | ||
| 118 | // Groups from empty entries should be nil or empty | 118 | // Groups from empty entries should be nil or empty | ||
| @@ -135,7 +135,7 @@ func TestApplyDeletedEntryLocked_RecomputesBrowseState(t *testing.T) { | |||||
| 135 | hs.setDisplayGroupsLocked(groupHistoryByDay(hs.allEntries)) | 135 | hs.setDisplayGroupsLocked(groupHistoryByDay(hs.allEntries)) | ||
| 136 | 136 | ||||
| 137 | hs.mu.Lock() | 137 | hs.mu.Lock() | ||
| 138 | - | hs.applyDeletedEntryLocked(deleteMe.URL, deleteMe.ID, keepB.URL) | 138 | + | hs.applyDeletedEntryLocked(deleteMe.ID, keepB.URL) |
| 139 | hs.mu.Unlock() | 139 | hs.mu.Unlock() | ||
| 140 | 140 | ||||
| 141 | assert.Equal(t, uint64(8), hs.loadGen, "deletes must invalidate in-flight browse loads") | 141 | assert.Equal(t, uint64(8), hs.loadGen, "deletes must invalidate in-flight browse loads") | ||
M internal/ui/component/history_sidebar_widgets.go +1 -1
| @@ -76,7 +76,7 @@ func (hs *HistorySidebar) initListArea() error { | |||
| 76 | 76 | hs.listBox.SetActivateOnSingleClick(true) | |
| 77 | 77 | hs.listBox.SetSelectionMode(gtk.SelectionSingleValue) | |
| 78 | 78 | ||
| 79 | - | // Connect row activation (Enter or double-click) | |
| 79 | + | // Connect row activation (single-click and keyboard activation) | |
| 80 | 80 | rowActivatedCb := func(_ gtk.ListBox, rowPtr uintptr) { | |
| 81 | 81 | row := gtk.ListBoxRowNewFromInternalPtr(rowPtr) | |
| 82 | 82 | if row == nil { | |
| @@ -76,7 +76,7 @@ func (hs *HistorySidebar) initListArea() error { | |||||
| 76 | hs.listBox.SetActivateOnSingleClick(true) | 76 | hs.listBox.SetActivateOnSingleClick(true) | ||
| 77 | hs.listBox.SetSelectionMode(gtk.SelectionSingleValue) | 77 | hs.listBox.SetSelectionMode(gtk.SelectionSingleValue) | ||
| 78 | 78 | ||||
| 79 | - | // Connect row activation (Enter or double-click) | 79 | + | // Connect row activation (single-click and keyboard activation) |
| 80 | rowActivatedCb := func(_ gtk.ListBox, rowPtr uintptr) { | 80 | rowActivatedCb := func(_ gtk.ListBox, rowPtr uintptr) { | ||
| 81 | row := gtk.ListBoxRowNewFromInternalPtr(rowPtr) | 81 | row := gtk.ListBoxRowNewFromInternalPtr(rowPtr) | ||
| 82 | if row == nil { | 82 | if row == nil { | ||
M internal/ui/window/main_window.go +37 -15
| @@ -61,10 +61,26 @@ func New(ctx context.Context, app *gtk.Application, tabBarPosition string) (*Mai | |||
| 61 | 61 | mw.window.SetTitle(&title) | |
| 62 | 62 | mw.window.SetDefaultSize(defaultWidth, defaultHeight) | |
| 63 | 63 | ||
| 64 | + | if err := mw.initLayoutWidgets(); err != nil { | |
| 65 | + | return nil, err | |
| 66 | + | } | |
| 67 | + | ||
| 68 | + | mw.contentAreaBox.Append(&mw.mainContentBox.Widget) | |
| 69 | + | mw.contentAreaBox.Append(&mw.sidebarBox.Widget) | |
| 70 | + | mw.contentOverlay.SetChild(&mw.contentAreaBox.Widget) | |
| 71 | + | ||
| 72 | + | mw.assembleLayout() | |
| 73 | + | ||
| 74 | + | mw.window.SetChild(&mw.rootBox.Widget) | |
| 75 | + | ||
| 76 | + | return mw, nil | |
| 77 | + | } | |
| 78 | + | ||
| 79 | + | func (mw *MainWindow) initLayoutWidgets() error { | |
| 64 | 80 | mw.rootBox = gtk.NewBox(gtk.OrientationVerticalValue, 0) | |
| 65 | 81 | if mw.rootBox == nil { | |
| 66 | 82 | mw.window.Unref() | |
| 67 | - | return nil, ErrWidgetCreationFailed("rootBox") | |
| 83 | + | return ErrWidgetCreationFailed("rootBox") | |
| 68 | 84 | } | |
| 69 | 85 | mw.rootBox.SetHexpand(true) | |
| 70 | 86 | mw.rootBox.SetVexpand(true) | |
| @@ -74,15 +90,22 @@ func New(ctx context.Context, app *gtk.Application, tabBarPosition string) (*Mai | |||
| 74 | 90 | if mw.tabBar == nil { | |
| 75 | 91 | mw.rootBox.Unref() | |
| 76 | 92 | mw.window.Unref() | |
| 77 | - | return nil, ErrWidgetCreationFailed("tabBar") | |
| 93 | + | return ErrWidgetCreationFailed("tabBar") | |
| 94 | + | } | |
| 95 | + | ||
| 96 | + | if err := mw.initContentWidgets(); err != nil { | |
| 97 | + | return err | |
| 78 | 98 | } | |
| 99 | + | return nil | |
| 100 | + | } | |
| 79 | 101 | ||
| 102 | + | func (mw *MainWindow) initContentWidgets() error { | |
| 80 | 103 | mw.contentOverlay = gtk.NewOverlay() | |
| 81 | 104 | if mw.contentOverlay == nil { | |
| 82 | 105 | mw.tabBar.Destroy() | |
| 83 | 106 | mw.rootBox.Unref() | |
| 84 | 107 | mw.window.Unref() | |
| 85 | - | return nil, ErrWidgetCreationFailed("contentOverlay") | |
| 108 | + | return ErrWidgetCreationFailed("contentOverlay") | |
| 86 | 109 | } | |
| 87 | 110 | mw.contentOverlay.SetHexpand(true) | |
| 88 | 111 | mw.contentOverlay.SetVexpand(true) | |
| @@ -95,12 +118,19 @@ func New(ctx context.Context, app *gtk.Application, tabBarPosition string) (*Mai | |||
| 95 | 118 | mw.tabBar.Destroy() | |
| 96 | 119 | mw.rootBox.Unref() | |
| 97 | 120 | mw.window.Unref() | |
| 98 | - | return nil, ErrWidgetCreationFailed("contentAreaBox") | |
| 121 | + | return ErrWidgetCreationFailed("contentAreaBox") | |
| 99 | 122 | } | |
| 100 | 123 | mw.contentAreaBox.SetHexpand(true) | |
| 101 | 124 | mw.contentAreaBox.SetVexpand(true) | |
| 102 | 125 | mw.contentAreaBox.SetVisible(true) | |
| 103 | 126 | ||
| 127 | + | if err := mw.initContentBoxes(); err != nil { | |
| 128 | + | return err | |
| 129 | + | } | |
| 130 | + | return nil | |
| 131 | + | } | |
| 132 | + | ||
| 133 | + | func (mw *MainWindow) initContentBoxes() error { | |
| 104 | 134 | // Main content box (vertical) for workspace content. | |
| 105 | 135 | mw.mainContentBox = gtk.NewBox(gtk.OrientationVerticalValue, 0) | |
| 106 | 136 | if mw.mainContentBox == nil { | |
| @@ -109,7 +139,7 @@ func New(ctx context.Context, app *gtk.Application, tabBarPosition string) (*Mai | |||
| 109 | 139 | mw.tabBar.Destroy() | |
| 110 | 140 | mw.rootBox.Unref() | |
| 111 | 141 | mw.window.Unref() | |
| 112 | - | return nil, ErrWidgetCreationFailed("mainContentBox") | |
| 142 | + | return ErrWidgetCreationFailed("mainContentBox") | |
| 113 | 143 | } | |
| 114 | 144 | mw.mainContentBox.SetHexpand(true) | |
| 115 | 145 | mw.mainContentBox.SetVexpand(true) | |
| @@ -124,21 +154,13 @@ func New(ctx context.Context, app *gtk.Application, tabBarPosition string) (*Mai | |||
| 124 | 154 | mw.tabBar.Destroy() | |
| 125 | 155 | mw.rootBox.Unref() | |
| 126 | 156 | mw.window.Unref() | |
| 127 | - | return nil, ErrWidgetCreationFailed("sidebarBox") | |
| 157 | + | return ErrWidgetCreationFailed("sidebarBox") | |
| 128 | 158 | } | |
| 129 | 159 | mw.sidebarBox.SetHexpand(false) | |
| 130 | 160 | mw.sidebarBox.SetVexpand(true) | |
| 131 | 161 | mw.sidebarBox.SetVisible(false) | |
| 132 | 162 | ||
| 133 | - | mw.contentAreaBox.Append(&mw.mainContentBox.Widget) | |
| 134 | - | mw.contentAreaBox.Append(&mw.sidebarBox.Widget) | |
| 135 | - | mw.contentOverlay.SetChild(&mw.contentAreaBox.Widget) | |
| 136 | - | ||
| 137 | - | mw.assembleLayout() | |
| 138 | - | ||
| 139 | - | mw.window.SetChild(&mw.rootBox.Widget) | |
| 140 | - | ||
| 141 | - | return mw, nil | |
| 163 | + | return nil | |
| 142 | 164 | } | |
| 143 | 165 | ||
| 144 | 166 | func (mw *MainWindow) assembleLayout() { | |
| @@ -61,10 +61,26 @@ func New(ctx context.Context, app *gtk.Application, tabBarPosition string) (*Mai | |||||
| 61 | mw.window.SetTitle(&title) | 61 | mw.window.SetTitle(&title) | ||
| 62 | mw.window.SetDefaultSize(defaultWidth, defaultHeight) | 62 | mw.window.SetDefaultSize(defaultWidth, defaultHeight) | ||
| 63 | 63 | ||||
| 64 | + | if err := mw.initLayoutWidgets(); err != nil { | |||
| 65 | + | return nil, err | |||
| 66 | + | } | |||
| 67 | + | ||||
| 68 | + | mw.contentAreaBox.Append(&mw.mainContentBox.Widget) | |||
| 69 | + | mw.contentAreaBox.Append(&mw.sidebarBox.Widget) | |||
| 70 | + | mw.contentOverlay.SetChild(&mw.contentAreaBox.Widget) | |||
| 71 | + | ||||
| 72 | + | mw.assembleLayout() | |||
| 73 | + | ||||
| 74 | + | mw.window.SetChild(&mw.rootBox.Widget) | |||
| 75 | + | ||||
| 76 | + | return mw, nil | |||
| 77 | + | } | |||
| 78 | + | ||||
| 79 | + | func (mw *MainWindow) initLayoutWidgets() error { | |||
| 64 | mw.rootBox = gtk.NewBox(gtk.OrientationVerticalValue, 0) | 80 | mw.rootBox = gtk.NewBox(gtk.OrientationVerticalValue, 0) | ||
| 65 | if mw.rootBox == nil { | 81 | if mw.rootBox == nil { | ||
| 66 | mw.window.Unref() | 82 | mw.window.Unref() | ||
| 67 | - | return nil, ErrWidgetCreationFailed("rootBox") | 83 | + | return ErrWidgetCreationFailed("rootBox") |
| 68 | } | 84 | } | ||
| 69 | mw.rootBox.SetHexpand(true) | 85 | mw.rootBox.SetHexpand(true) | ||
| 70 | mw.rootBox.SetVexpand(true) | 86 | mw.rootBox.SetVexpand(true) | ||
| @@ -74,15 +90,22 @@ func New(ctx context.Context, app *gtk.Application, tabBarPosition string) (*Mai | |||||
| 74 | if mw.tabBar == nil { | 90 | if mw.tabBar == nil { | ||
| 75 | mw.rootBox.Unref() | 91 | mw.rootBox.Unref() | ||
| 76 | mw.window.Unref() | 92 | mw.window.Unref() | ||
| 77 | - | return nil, ErrWidgetCreationFailed("tabBar") | 93 | + | return ErrWidgetCreationFailed("tabBar") |
| 94 | + | } | |||
| 95 | + | ||||
| 96 | + | if err := mw.initContentWidgets(); err != nil { | |||
| 97 | + | return err | |||
| 78 | } | 98 | } | ||
| 99 | + | return nil | |||
| 100 | + | } | |||
| 79 | 101 | ||||
| 102 | + | func (mw *MainWindow) initContentWidgets() error { | |||
| 80 | mw.contentOverlay = gtk.NewOverlay() | 103 | mw.contentOverlay = gtk.NewOverlay() | ||
| 81 | if mw.contentOverlay == nil { | 104 | if mw.contentOverlay == nil { | ||
| 82 | mw.tabBar.Destroy() | 105 | mw.tabBar.Destroy() | ||
| 83 | mw.rootBox.Unref() | 106 | mw.rootBox.Unref() | ||
| 84 | mw.window.Unref() | 107 | mw.window.Unref() | ||
| 85 | - | return nil, ErrWidgetCreationFailed("contentOverlay") | 108 | + | return ErrWidgetCreationFailed("contentOverlay") |
| 86 | } | 109 | } | ||
| 87 | mw.contentOverlay.SetHexpand(true) | 110 | mw.contentOverlay.SetHexpand(true) | ||
| 88 | mw.contentOverlay.SetVexpand(true) | 111 | mw.contentOverlay.SetVexpand(true) | ||
| @@ -95,12 +118,19 @@ func New(ctx context.Context, app *gtk.Application, tabBarPosition string) (*Mai | |||||
| 95 | mw.tabBar.Destroy() | 118 | mw.tabBar.Destroy() | ||
| 96 | mw.rootBox.Unref() | 119 | mw.rootBox.Unref() | ||
| 97 | mw.window.Unref() | 120 | mw.window.Unref() | ||
| 98 | - | return nil, ErrWidgetCreationFailed("contentAreaBox") | 121 | + | return ErrWidgetCreationFailed("contentAreaBox") |
| 99 | } | 122 | } | ||
| 100 | mw.contentAreaBox.SetHexpand(true) | 123 | mw.contentAreaBox.SetHexpand(true) | ||
| 101 | mw.contentAreaBox.SetVexpand(true) | 124 | mw.contentAreaBox.SetVexpand(true) | ||
| 102 | mw.contentAreaBox.SetVisible(true) | 125 | mw.contentAreaBox.SetVisible(true) | ||
| 103 | 126 | ||||
| 127 | + | if err := mw.initContentBoxes(); err != nil { | |||
| 128 | + | return err | |||
| 129 | + | } | |||
| 130 | + | return nil | |||
| 131 | + | } | |||
| 132 | + | ||||
| 133 | + | func (mw *MainWindow) initContentBoxes() error { | |||
| 104 | // Main content box (vertical) for workspace content. | 134 | // Main content box (vertical) for workspace content. | ||
| 105 | mw.mainContentBox = gtk.NewBox(gtk.OrientationVerticalValue, 0) | 135 | mw.mainContentBox = gtk.NewBox(gtk.OrientationVerticalValue, 0) | ||
| 106 | if mw.mainContentBox == nil { | 136 | if mw.mainContentBox == nil { | ||
| @@ -109,7 +139,7 @@ func New(ctx context.Context, app *gtk.Application, tabBarPosition string) (*Mai | |||||
| 109 | mw.tabBar.Destroy() | 139 | mw.tabBar.Destroy() | ||
| 110 | mw.rootBox.Unref() | 140 | mw.rootBox.Unref() | ||
| 111 | mw.window.Unref() | 141 | mw.window.Unref() | ||
| 112 | - | return nil, ErrWidgetCreationFailed("mainContentBox") | 142 | + | return ErrWidgetCreationFailed("mainContentBox") |
| 113 | } | 143 | } | ||
| 114 | mw.mainContentBox.SetHexpand(true) | 144 | mw.mainContentBox.SetHexpand(true) | ||
| 115 | mw.mainContentBox.SetVexpand(true) | 145 | mw.mainContentBox.SetVexpand(true) | ||
| @@ -124,21 +154,13 @@ func New(ctx context.Context, app *gtk.Application, tabBarPosition string) (*Mai | |||||
| 124 | mw.tabBar.Destroy() | 154 | mw.tabBar.Destroy() | ||
| 125 | mw.rootBox.Unref() | 155 | mw.rootBox.Unref() | ||
| 126 | mw.window.Unref() | 156 | mw.window.Unref() | ||
| 127 | - | return nil, ErrWidgetCreationFailed("sidebarBox") | 157 | + | return ErrWidgetCreationFailed("sidebarBox") |
| 128 | } | 158 | } | ||
| 129 | mw.sidebarBox.SetHexpand(false) | 159 | mw.sidebarBox.SetHexpand(false) | ||
| 130 | mw.sidebarBox.SetVexpand(true) | 160 | mw.sidebarBox.SetVexpand(true) | ||
| 131 | mw.sidebarBox.SetVisible(false) | 161 | mw.sidebarBox.SetVisible(false) | ||
| 132 | 162 | ||||
| 133 | - | mw.contentAreaBox.Append(&mw.mainContentBox.Widget) | 163 | + | return nil |
| 134 | - | mw.contentAreaBox.Append(&mw.sidebarBox.Widget) | |||
| 135 | - | mw.contentOverlay.SetChild(&mw.contentAreaBox.Widget) | |||
| 136 | - | ||||
| 137 | - | mw.assembleLayout() | |||
| 138 | - | ||||
| 139 | - | mw.window.SetChild(&mw.rootBox.Widget) | |||
| 140 | - | ||||
| 141 | - | return mw, nil | |||
| 142 | } | 164 | } | ||
| 143 | 165 | ||||
| 144 | func (mw *MainWindow) assembleLayout() { | 166 | func (mw *MainWindow) assembleLayout() { | ||