fix(history): keep sidebar open on navigation
- commit
- bf8765ac687916051c2cf55cc33bd05151ba248e
- author
- brice · · 2026-06-13 06:16 UTC
- parent
- 0109d89
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
4 files · +63 -35
M docs/reference/keybindings.md +1 -1
| @@ -96,7 +96,7 @@ These work outside modal modes: | |||
| 96 | 96 | ||
| 97 | 97 | - `Alt+F` is the only floating-pane shortcut enabled by default. | |
| 98 | 98 | - `Alt+F` toggles floating visibility and keeps floating pane state intact. | |
| 99 | - | - `Ctrl+H` toggles the native GTK history sidebar. The sidebar shows browsing history grouped by day with search/filter, keyboard navigation (arrows, Home/End, Ctrl+arrows for day jumps), and activation modes (Enter to navigate and close sidebar, Ctrl+Enter to navigate while keeping the sidebar open, Shift+Enter to open in a new split). If the native sidebar is unavailable, the shortcut returns an error instead of falling back to `dumb://history`. | |
| 99 | + | - `Ctrl+H` toggles the native GTK history sidebar. The sidebar shows browsing history grouped by day with search/filter, keyboard navigation (arrows, Home/End, Ctrl+arrows for day jumps), and activation modes (Enter to navigate while keeping the sidebar open, Ctrl+Enter to navigate while keeping the sidebar open, Shift+Enter to open in a new split). If the native sidebar is unavailable, the shortcut returns an error instead of falling back to `dumb://history`. | |
| 100 | 100 | - `Ctrl+W` closes the active pane; when the floating pane is active, it fully releases that floating session. | |
| 101 | 101 | - Any URL shortcut (for example `Alt+G`) must be defined explicitly in `workspace.floating_pane.profiles`. | |
| 102 | 102 | - Floating profile shortcuts support modifier combos with `ctrl`, `shift`, and `alt` (for example `ctrl+shift+y` or `ctrl+alt+m`). | |
| @@ -96,7 +96,7 @@ These work outside modal modes: | |||||
| 96 | 96 | ||||
| 97 | - `Alt+F` is the only floating-pane shortcut enabled by default. | 97 | - `Alt+F` is the only floating-pane shortcut enabled by default. | ||
| 98 | - `Alt+F` toggles floating visibility and keeps floating pane state intact. | 98 | - `Alt+F` toggles floating visibility and keeps floating pane state intact. | ||
| 99 | - | - `Ctrl+H` toggles the native GTK history sidebar. The sidebar shows browsing history grouped by day with search/filter, keyboard navigation (arrows, Home/End, Ctrl+arrows for day jumps), and activation modes (Enter to navigate and close sidebar, Ctrl+Enter to navigate while keeping the sidebar open, Shift+Enter to open in a new split). If the native sidebar is unavailable, the shortcut returns an error instead of falling back to `dumb://history`. | 99 | + | - `Ctrl+H` toggles the native GTK history sidebar. The sidebar shows browsing history grouped by day with search/filter, keyboard navigation (arrows, Home/End, Ctrl+arrows for day jumps), and activation modes (Enter to navigate while keeping the sidebar open, Ctrl+Enter to navigate while keeping the sidebar open, Shift+Enter to open in a new split). If the native sidebar is unavailable, the shortcut returns an error instead of falling back to `dumb://history`. |
| 100 | - `Ctrl+W` closes the active pane; when the floating pane is active, it fully releases that floating session. | 100 | - `Ctrl+W` closes the active pane; when the floating pane is active, it fully releases that floating session. | ||
| 101 | - Any URL shortcut (for example `Alt+G`) must be defined explicitly in `workspace.floating_pane.profiles`. | 101 | - Any URL shortcut (for example `Alt+G`) must be defined explicitly in `workspace.floating_pane.profiles`. | ||
| 102 | - Floating profile shortcuts support modifier combos with `ctrl`, `shift`, and `alt` (for example `ctrl+shift+y` or `ctrl+alt+m`). | 102 | - Floating profile shortcuts support modifier combos with `ctrl`, `shift`, and `alt` (for example `ctrl+shift+y` or `ctrl+alt+m`). | ||
M internal/ui/browser_window.go +6 -10
| @@ -309,18 +309,10 @@ func (a *App) buildHistorySidebarConfig(ctx context.Context, bw *browserWindow) | |||
| 309 | 309 | return component.HistorySidebarConfig{ | |
| 310 | 310 | HistoryUC: historyUC, | |
| 311 | 311 | OnNavigate: func(navCtx context.Context, url string) error { | |
| 312 | - | if err := a.navigateFromBrowserWindow(navCtx, bw, url); err != nil { | |
| 313 | - | return err | |
| 314 | - | } | |
| 315 | - | cb := glib.SourceFunc(func(_ uintptr) bool { | |
| 316 | - | a.hideAndRestoreFocusForBrowserWindow(bw) | |
| 317 | - | return false | |
| 318 | - | }) | |
| 319 | - | glib.IdleAdd(&cb, 0) | |
| 320 | - | return nil | |
| 312 | + | return a.navigateHistorySidebarSelection(navCtx, bw, url) | |
| 321 | 313 | }, | |
| 322 | 314 | OnNavigateKeepOpen: func(navCtx context.Context, url string) error { | |
| 323 | - | return a.navigateFromBrowserWindow(navCtx, bw, url) | |
| 315 | + | return a.navigateHistorySidebarSelection(navCtx, bw, url) | |
| 324 | 316 | }, | |
| 325 | 317 | OnOpenInNewPane: func(splitCtx context.Context, url string) error { | |
| 326 | 318 | if a.wsCoord == nil { | |
| @@ -335,6 +327,10 @@ func (a *App) buildHistorySidebarConfig(ctx context.Context, bw *browserWindow) | |||
| 335 | 327 | } | |
| 336 | 328 | } | |
| 337 | 329 | ||
| 330 | + | func (a *App) navigateHistorySidebarSelection(ctx context.Context, bw *browserWindow, url string) error { | |
| 331 | + | return a.navigateFromBrowserWindow(ctx, bw, url) | |
| 332 | + | } | |
| 333 | + | ||
| 338 | 334 | // toggleHistorySidebar toggles sidebar visibility. An optional width config | |
| 339 | 335 | // can be provided and is applied when showing the sidebar. | |
| 340 | 336 | func (bw *browserWindow) toggleHistorySidebar(widthCfg ...window.SidebarWidthConfig) { | |
| @@ -309,18 +309,10 @@ func (a *App) buildHistorySidebarConfig(ctx context.Context, bw *browserWindow) | |||||
| 309 | return component.HistorySidebarConfig{ | 309 | return component.HistorySidebarConfig{ | ||
| 310 | HistoryUC: historyUC, | 310 | HistoryUC: historyUC, | ||
| 311 | OnNavigate: func(navCtx context.Context, url string) error { | 311 | OnNavigate: func(navCtx context.Context, url string) error { | ||
| 312 | - | if err := a.navigateFromBrowserWindow(navCtx, bw, url); err != nil { | 312 | + | return a.navigateHistorySidebarSelection(navCtx, bw, url) |
| 313 | - | return err | |||
| 314 | - | } | |||
| 315 | - | cb := glib.SourceFunc(func(_ uintptr) bool { | |||
| 316 | - | a.hideAndRestoreFocusForBrowserWindow(bw) | |||
| 317 | - | return false | |||
| 318 | - | }) | |||
| 319 | - | glib.IdleAdd(&cb, 0) | |||
| 320 | - | return nil | |||
| 321 | }, | 313 | }, | ||
| 322 | OnNavigateKeepOpen: func(navCtx context.Context, url string) error { | 314 | OnNavigateKeepOpen: func(navCtx context.Context, url string) error { | ||
| 323 | - | return a.navigateFromBrowserWindow(navCtx, bw, url) | 315 | + | return a.navigateHistorySidebarSelection(navCtx, bw, url) |
| 324 | }, | 316 | }, | ||
| 325 | OnOpenInNewPane: func(splitCtx context.Context, url string) error { | 317 | OnOpenInNewPane: func(splitCtx context.Context, url string) error { | ||
| 326 | if a.wsCoord == nil { | 318 | if a.wsCoord == nil { | ||
| @@ -335,6 +327,10 @@ func (a *App) buildHistorySidebarConfig(ctx context.Context, bw *browserWindow) | |||||
| 335 | } | 327 | } | ||
| 336 | } | 328 | } | ||
| 337 | 329 | ||||
| 330 | + | func (a *App) navigateHistorySidebarSelection(ctx context.Context, bw *browserWindow, url string) error { | |||
| 331 | + | return a.navigateFromBrowserWindow(ctx, bw, url) | |||
| 332 | + | } | |||
| 333 | + | ||||
| 338 | // toggleHistorySidebar toggles sidebar visibility. An optional width config | 334 | // toggleHistorySidebar toggles sidebar visibility. An optional width config | ||
| 339 | // can be provided and is applied when showing the sidebar. | 335 | // can be provided and is applied when showing the sidebar. | ||
| 340 | func (bw *browserWindow) toggleHistorySidebar(widthCfg ...window.SidebarWidthConfig) { | 336 | func (bw *browserWindow) toggleHistorySidebar(widthCfg ...window.SidebarWidthConfig) { | ||
M internal/ui/browser_window_test.go +48 -1
| @@ -1411,7 +1411,13 @@ func TestApp_HistorySidebarConfig_NavigateCallbackNavigates(t *testing.T) { | |||
| 1411 | 1411 | bwTabs.Add(tab) | |
| 1412 | 1412 | bwTabs.SetActive(tab.ID) | |
| 1413 | 1413 | ||
| 1414 | - | bw := &browserWindow{id: "window-1", tabs: bwTabs} | |
| 1414 | + | bw := &browserWindow{ | |
| 1415 | + | id: "window-1", | |
| 1416 | + | tabs: bwTabs, | |
| 1417 | + | mainWindow: &window.MainWindow{}, | |
| 1418 | + | historySidebar: &component.HistorySidebar{}, | |
| 1419 | + | sidebarVisible: true, | |
| 1420 | + | } | |
| 1415 | 1421 | ||
| 1416 | 1422 | fakeWv := &fakeRecordingWebView{id: 1} | |
| 1417 | 1423 | contentCoord := contentcoord.NewCoordinator(ctx, nil, nil, nil, nil, nil, nil, nil) | |
| @@ -1441,6 +1447,47 @@ func TestApp_HistorySidebarConfig_NavigateCallbackNavigates(t *testing.T) { | |||
| 1441 | 1447 | // Verify the navigation reached the correct webview. | |
| 1442 | 1448 | assert.True(t, fakeWv.loadURICalled, "webview must receive navigation") | |
| 1443 | 1449 | assert.Equal(t, navigateURL, fakeWv.loadURILastURI) | |
| 1450 | + | assert.True(t, bw.sidebarVisible, "default history navigation should keep the sidebar open") | |
| 1451 | + | } | |
| 1452 | + | ||
| 1453 | + | func TestApp_NavigateHistorySidebarSelection_KeepsSidebarVisible(t *testing.T) { | |
| 1454 | + | ctx := context.Background() | |
| 1455 | + | ||
| 1456 | + | paneID := entity.PaneID("pane-1") | |
| 1457 | + | tab := entity.NewTab(entity.TabID("tab-1"), entity.WorkspaceID("ws-1"), entity.NewPane(paneID)) | |
| 1458 | + | bwTabs := entity.NewTabList() | |
| 1459 | + | bwTabs.Add(tab) | |
| 1460 | + | bwTabs.SetActive(tab.ID) | |
| 1461 | + | ||
| 1462 | + | bw := &browserWindow{ | |
| 1463 | + | id: "window-1", | |
| 1464 | + | tabs: bwTabs, | |
| 1465 | + | mainWindow: &window.MainWindow{}, | |
| 1466 | + | historySidebar: &component.HistorySidebar{}, | |
| 1467 | + | sidebarVisible: true, | |
| 1468 | + | } | |
| 1469 | + | ||
| 1470 | + | fakeWv := &fakeRecordingWebView{id: 1} | |
| 1471 | + | contentCoord := contentcoord.NewCoordinator(ctx, nil, nil, nil, nil, nil, nil, nil) | |
| 1472 | + | contentCoord.RegisterPopupWebView(paneID, fakeWv) | |
| 1473 | + | navCoord := coordinator.NewNavigationCoordinator(ctx, nil, contentCoord) | |
| 1474 | + | ||
| 1475 | + | app := &App{ | |
| 1476 | + | tabs: entity.NewTabList(), | |
| 1477 | + | browserWindows: map[string]*browserWindow{bw.id: bw}, | |
| 1478 | + | contentCoord: contentCoord, | |
| 1479 | + | navCoord: navCoord, | |
| 1480 | + | workspaceViews: map[entity.TabID]*component.WorkspaceView{ | |
| 1481 | + | tab.ID: {}, | |
| 1482 | + | }, | |
| 1483 | + | } | |
| 1484 | + | app.tabs.Add(tab) | |
| 1485 | + | ||
| 1486 | + | err := app.navigateHistorySidebarSelection(ctx, bw, "https://open.com") | |
| 1487 | + | require.NoError(t, err) | |
| 1488 | + | assert.True(t, fakeWv.loadURICalled) | |
| 1489 | + | assert.Equal(t, "https://open.com", fakeWv.loadURILastURI) | |
| 1490 | + | assert.True(t, bw.sidebarVisible, "history selection navigation should not hide the sidebar") | |
| 1444 | 1491 | } | |
| 1445 | 1492 | ||
| 1446 | 1493 | // TestApp_HistorySidebarConfig_NavigateCallbackOwnership verifies that | |
| @@ -1411,7 +1411,13 @@ func TestApp_HistorySidebarConfig_NavigateCallbackNavigates(t *testing.T) { | |||||
| 1411 | bwTabs.Add(tab) | 1411 | bwTabs.Add(tab) | ||
| 1412 | bwTabs.SetActive(tab.ID) | 1412 | bwTabs.SetActive(tab.ID) | ||
| 1413 | 1413 | ||||
| 1414 | - | bw := &browserWindow{id: "window-1", tabs: bwTabs} | 1414 | + | bw := &browserWindow{ |
| 1415 | + | id: "window-1", | |||
| 1416 | + | tabs: bwTabs, | |||
| 1417 | + | mainWindow: &window.MainWindow{}, | |||
| 1418 | + | historySidebar: &component.HistorySidebar{}, | |||
| 1419 | + | sidebarVisible: true, | |||
| 1420 | + | } | |||
| 1415 | 1421 | ||||
| 1416 | fakeWv := &fakeRecordingWebView{id: 1} | 1422 | fakeWv := &fakeRecordingWebView{id: 1} | ||
| 1417 | contentCoord := contentcoord.NewCoordinator(ctx, nil, nil, nil, nil, nil, nil, nil) | 1423 | contentCoord := contentcoord.NewCoordinator(ctx, nil, nil, nil, nil, nil, nil, nil) | ||
| @@ -1441,6 +1447,47 @@ func TestApp_HistorySidebarConfig_NavigateCallbackNavigates(t *testing.T) { | |||||
| 1441 | // Verify the navigation reached the correct webview. | 1447 | // Verify the navigation reached the correct webview. | ||
| 1442 | assert.True(t, fakeWv.loadURICalled, "webview must receive navigation") | 1448 | assert.True(t, fakeWv.loadURICalled, "webview must receive navigation") | ||
| 1443 | assert.Equal(t, navigateURL, fakeWv.loadURILastURI) | 1449 | assert.Equal(t, navigateURL, fakeWv.loadURILastURI) | ||
| 1450 | + | assert.True(t, bw.sidebarVisible, "default history navigation should keep the sidebar open") | |||
| 1451 | + | } | |||
| 1452 | + | ||||
| 1453 | + | func TestApp_NavigateHistorySidebarSelection_KeepsSidebarVisible(t *testing.T) { | |||
| 1454 | + | ctx := context.Background() | |||
| 1455 | + | ||||
| 1456 | + | paneID := entity.PaneID("pane-1") | |||
| 1457 | + | tab := entity.NewTab(entity.TabID("tab-1"), entity.WorkspaceID("ws-1"), entity.NewPane(paneID)) | |||
| 1458 | + | bwTabs := entity.NewTabList() | |||
| 1459 | + | bwTabs.Add(tab) | |||
| 1460 | + | bwTabs.SetActive(tab.ID) | |||
| 1461 | + | ||||
| 1462 | + | bw := &browserWindow{ | |||
| 1463 | + | id: "window-1", | |||
| 1464 | + | tabs: bwTabs, | |||
| 1465 | + | mainWindow: &window.MainWindow{}, | |||
| 1466 | + | historySidebar: &component.HistorySidebar{}, | |||
| 1467 | + | sidebarVisible: true, | |||
| 1468 | + | } | |||
| 1469 | + | ||||
| 1470 | + | fakeWv := &fakeRecordingWebView{id: 1} | |||
| 1471 | + | contentCoord := contentcoord.NewCoordinator(ctx, nil, nil, nil, nil, nil, nil, nil) | |||
| 1472 | + | contentCoord.RegisterPopupWebView(paneID, fakeWv) | |||
| 1473 | + | navCoord := coordinator.NewNavigationCoordinator(ctx, nil, contentCoord) | |||
| 1474 | + | ||||
| 1475 | + | app := &App{ | |||
| 1476 | + | tabs: entity.NewTabList(), | |||
| 1477 | + | browserWindows: map[string]*browserWindow{bw.id: bw}, | |||
| 1478 | + | contentCoord: contentCoord, | |||
| 1479 | + | navCoord: navCoord, | |||
| 1480 | + | workspaceViews: map[entity.TabID]*component.WorkspaceView{ | |||
| 1481 | + | tab.ID: {}, | |||
| 1482 | + | }, | |||
| 1483 | + | } | |||
| 1484 | + | app.tabs.Add(tab) | |||
| 1485 | + | ||||
| 1486 | + | err := app.navigateHistorySidebarSelection(ctx, bw, "https://open.com") | |||
| 1487 | + | require.NoError(t, err) | |||
| 1488 | + | assert.True(t, fakeWv.loadURICalled) | |||
| 1489 | + | assert.Equal(t, "https://open.com", fakeWv.loadURILastURI) | |||
| 1490 | + | assert.True(t, bw.sidebarVisible, "history selection navigation should not hide the sidebar") | |||
| 1444 | } | 1491 | } | ||
| 1445 | 1492 | ||||
| 1446 | // TestApp_HistorySidebarConfig_NavigateCallbackOwnership verifies that | 1493 | // TestApp_HistorySidebarConfig_NavigateCallbackOwnership verifies that | ||
M internal/ui/component/history_sidebar.go +8 -23
| @@ -31,7 +31,8 @@ const ( | |||
| 31 | 31 | type HistorySidebarKeyboardAction int | |
| 32 | 32 | ||
| 33 | 33 | const ( | |
| 34 | - | // SidebarActionCloseOnActivate is the default: navigate and close the sidebar. | |
| 34 | + | // SidebarActionCloseOnActivate is the default activation path. | |
| 35 | + | // The host currently keeps the sidebar visible for default activation. | |
| 35 | 36 | SidebarActionCloseOnActivate HistorySidebarKeyboardAction = iota | |
| 36 | 37 | // SidebarActionKeepOpenOnActivate navigates but leaves the sidebar visible. | |
| 37 | 38 | SidebarActionKeepOpenOnActivate | |
| @@ -104,7 +105,7 @@ type HistorySidebarConfig struct { | |||
| 104 | 105 | HistoryUC *usecase.SearchHistoryUseCase | |
| 105 | 106 | ||
| 106 | 107 | // OnNavigate is called when the user activates a history entry. | |
| 107 | - | // The default Enter / click behavior closes the sidebar after navigating. | |
| 108 | + | // The default Enter / click behavior keeps the sidebar open after navigating. | |
| 108 | 109 | OnNavigate func(ctx context.Context, url string) error | |
| 109 | 110 | ||
| 110 | 111 | // OnOpenInNewPane is called when Shift+Enter activates a URL. | |
| @@ -153,7 +154,8 @@ func NewHistorySidebar(ctx context.Context, cfg HistorySidebarConfig) *HistorySi | |||
| 153 | 154 | log.Error().Err(err).Str("url", url).Msg("history sidebar keep-open navigate failed") | |
| 154 | 155 | } | |
| 155 | 156 | } else if cfg.OnNavigate != nil { | |
| 156 | - | // Fall back to default navigate (which may close the sidebar) | |
| 157 | + | // Fall back to the default navigate path when no dedicated | |
| 158 | + | // keep-open callback is configured. | |
| 157 | 159 | if err := cfg.OnNavigate(callCtx, url); err != nil { | |
| 158 | 160 | log.Error().Err(err).Str("url", url).Msg("history sidebar keep-open fallback failed") | |
| 159 | 161 | } | |
| @@ -1143,7 +1145,7 @@ func (hs *HistorySidebar) handleEnterKey(keyval uint, state gdk.ModifierType) bo | |||
| 1143 | 1145 | // Shift+Enter: navigate in new pane | |
| 1144 | 1146 | action = SidebarActionNewPaneOnActivate | |
| 1145 | 1147 | default: | |
| 1146 | - | // Plain Enter: navigate and close sidebar (via onRowActivated or direct) | |
| 1148 | + | // Plain Enter: navigate using the default activation behavior. | |
| 1147 | 1149 | action = SidebarActionCloseOnActivate | |
| 1148 | 1150 | } | |
| 1149 | 1151 | ||
| @@ -1578,29 +1580,12 @@ func (hs *HistorySidebar) navigateWithoutClosing(url string) { | |||
| 1578 | 1580 | if hs.onNavigateKeepOpen == nil || url == "" { | |
| 1579 | 1581 | return | |
| 1580 | 1582 | } | |
| 1581 | - | ||
| 1582 | - | // Wrap onURL so that the configured OnNavigate callback is called | |
| 1583 | - | // but we do NOT trigger the auto-close path (which is in OnNavigate). | |
| 1584 | - | // Since OnNavigate already controls closing, we call the raw onURL | |
| 1585 | - | // closure which calls OnNavigate directly — the OnNavigate callback | |
| 1586 | - | // in browser_window.go has the auto-hide logic. We override that by | |
| 1587 | - | // scheduling the navigation on idle but NOT scheduling a hide. | |
| 1588 | - | // | |
| 1589 | - | // The cleanest approach: call the raw onURL (which calls OnNavigate) | |
| 1590 | - | // and let the caller decide about sidebar visibility. | |
| 1591 | - | // OnNavigate in the browser window already hides; for keep-open we | |
| 1592 | - | // need a different path. | |
| 1593 | - | // | |
| 1594 | - | // Solution: call a deferred navigate that does NOT include the | |
| 1595 | - | // auto-close. We use a dedicated idle callback that navigates | |
| 1596 | - | // without scheduling hide. | |
| 1597 | 1583 | hs.doNavigateWithoutClose(url) | |
| 1598 | 1584 | } | |
| 1599 | 1585 | ||
| 1600 | 1586 | // doNavigateWithoutClose schedules navigation without closing the sidebar. | |
| 1601 | - | // Uses the dedicated OnNavigateKeepOpen path so the host never hides | |
| 1602 | - | // the sidebar. When OnNavigateKeepOpen is not configured, falls back | |
| 1603 | - | // to the normal onURL path (which may close the sidebar). | |
| 1587 | + | // Uses the dedicated OnNavigateKeepOpen path so hosts can override the | |
| 1588 | + | // default activation behavior when they need a distinct keep-open action. | |
| 1604 | 1589 | func (hs *HistorySidebar) doNavigateWithoutClose(url string) { | |
| 1605 | 1590 | navigateCb := glib.SourceFunc(func(uintptr) bool { | |
| 1606 | 1591 | hs.onNavigateKeepOpen(hs.ctx, url) | |
| @@ -31,7 +31,8 @@ const ( | |||||
| 31 | type HistorySidebarKeyboardAction int | 31 | type HistorySidebarKeyboardAction int | ||
| 32 | 32 | ||||
| 33 | const ( | 33 | const ( | ||
| 34 | - | // SidebarActionCloseOnActivate is the default: navigate and close the sidebar. | 34 | + | // SidebarActionCloseOnActivate is the default activation path. |
| 35 | + | // The host currently keeps the sidebar visible for default activation. | |||
| 35 | SidebarActionCloseOnActivate HistorySidebarKeyboardAction = iota | 36 | SidebarActionCloseOnActivate HistorySidebarKeyboardAction = iota | ||
| 36 | // SidebarActionKeepOpenOnActivate navigates but leaves the sidebar visible. | 37 | // SidebarActionKeepOpenOnActivate navigates but leaves the sidebar visible. | ||
| 37 | SidebarActionKeepOpenOnActivate | 38 | SidebarActionKeepOpenOnActivate | ||
| @@ -104,7 +105,7 @@ type HistorySidebarConfig struct { | |||||
| 104 | HistoryUC *usecase.SearchHistoryUseCase | 105 | HistoryUC *usecase.SearchHistoryUseCase | ||
| 105 | 106 | ||||
| 106 | // OnNavigate is called when the user activates a history entry. | 107 | // OnNavigate is called when the user activates a history entry. | ||
| 107 | - | // The default Enter / click behavior closes the sidebar after navigating. | 108 | + | // The default Enter / click behavior keeps the sidebar open after navigating. |
| 108 | OnNavigate func(ctx context.Context, url string) error | 109 | OnNavigate func(ctx context.Context, url string) error | ||
| 109 | 110 | ||||
| 110 | // OnOpenInNewPane is called when Shift+Enter activates a URL. | 111 | // OnOpenInNewPane is called when Shift+Enter activates a URL. | ||
| @@ -153,7 +154,8 @@ func NewHistorySidebar(ctx context.Context, cfg HistorySidebarConfig) *HistorySi | |||||
| 153 | log.Error().Err(err).Str("url", url).Msg("history sidebar keep-open navigate failed") | 154 | log.Error().Err(err).Str("url", url).Msg("history sidebar keep-open navigate failed") | ||
| 154 | } | 155 | } | ||
| 155 | } else if cfg.OnNavigate != nil { | 156 | } else if cfg.OnNavigate != nil { | ||
| 156 | - | // Fall back to default navigate (which may close the sidebar) | 157 | + | // Fall back to the default navigate path when no dedicated |
| 158 | + | // keep-open callback is configured. | |||
| 157 | if err := cfg.OnNavigate(callCtx, url); err != nil { | 159 | if err := cfg.OnNavigate(callCtx, url); err != nil { | ||
| 158 | log.Error().Err(err).Str("url", url).Msg("history sidebar keep-open fallback failed") | 160 | log.Error().Err(err).Str("url", url).Msg("history sidebar keep-open fallback failed") | ||
| 159 | } | 161 | } | ||
| @@ -1143,7 +1145,7 @@ func (hs *HistorySidebar) handleEnterKey(keyval uint, state gdk.ModifierType) bo | |||||
| 1143 | // Shift+Enter: navigate in new pane | 1145 | // Shift+Enter: navigate in new pane | ||
| 1144 | action = SidebarActionNewPaneOnActivate | 1146 | action = SidebarActionNewPaneOnActivate | ||
| 1145 | default: | 1147 | default: | ||
| 1146 | - | // Plain Enter: navigate and close sidebar (via onRowActivated or direct) | 1148 | + | // Plain Enter: navigate using the default activation behavior. |
| 1147 | action = SidebarActionCloseOnActivate | 1149 | action = SidebarActionCloseOnActivate | ||
| 1148 | } | 1150 | } | ||
| 1149 | 1151 | ||||
| @@ -1578,29 +1580,12 @@ func (hs *HistorySidebar) navigateWithoutClosing(url string) { | |||||
| 1578 | if hs.onNavigateKeepOpen == nil || url == "" { | 1580 | if hs.onNavigateKeepOpen == nil || url == "" { | ||
| 1579 | return | 1581 | return | ||
| 1580 | } | 1582 | } | ||
| 1581 | - | ||||
| 1582 | - | // Wrap onURL so that the configured OnNavigate callback is called | |||
| 1583 | - | // but we do NOT trigger the auto-close path (which is in OnNavigate). | |||
| 1584 | - | // Since OnNavigate already controls closing, we call the raw onURL | |||
| 1585 | - | // closure which calls OnNavigate directly — the OnNavigate callback | |||
| 1586 | - | // in browser_window.go has the auto-hide logic. We override that by | |||
| 1587 | - | // scheduling the navigation on idle but NOT scheduling a hide. | |||
| 1588 | - | // | |||
| 1589 | - | // The cleanest approach: call the raw onURL (which calls OnNavigate) | |||
| 1590 | - | // and let the caller decide about sidebar visibility. | |||
| 1591 | - | // OnNavigate in the browser window already hides; for keep-open we | |||
| 1592 | - | // need a different path. | |||
| 1593 | - | // | |||
| 1594 | - | // Solution: call a deferred navigate that does NOT include the | |||
| 1595 | - | // auto-close. We use a dedicated idle callback that navigates | |||
| 1596 | - | // without scheduling hide. | |||
| 1597 | hs.doNavigateWithoutClose(url) | 1583 | hs.doNavigateWithoutClose(url) | ||
| 1598 | } | 1584 | } | ||
| 1599 | 1585 | ||||
| 1600 | // doNavigateWithoutClose schedules navigation without closing the sidebar. | 1586 | // doNavigateWithoutClose schedules navigation without closing the sidebar. | ||
| 1601 | - | // Uses the dedicated OnNavigateKeepOpen path so the host never hides | 1587 | + | // Uses the dedicated OnNavigateKeepOpen path so hosts can override the |
| 1602 | - | // the sidebar. When OnNavigateKeepOpen is not configured, falls back | 1588 | + | // default activation behavior when they need a distinct keep-open action. |
| 1603 | - | // to the normal onURL path (which may close the sidebar). | |||
| 1604 | func (hs *HistorySidebar) doNavigateWithoutClose(url string) { | 1589 | func (hs *HistorySidebar) doNavigateWithoutClose(url string) { | ||
| 1605 | navigateCb := glib.SourceFunc(func(uintptr) bool { | 1590 | navigateCb := glib.SourceFunc(func(uintptr) bool { | ||
| 1606 | hs.onNavigateKeepOpen(hs.ctx, url) | 1591 | hs.onNavigateKeepOpen(hs.ctx, url) | ||