Skip to content

Commit cbe806a

Browse files
authored
Merge pull request #304 from bnema/feat/gtk-history-sidebar
feat(history): add native GTK Ctrl+H sidebar
2 parents ac3d540 + 217c6f0 commit cbe806a

31 files changed

Lines changed: 5035 additions & 88 deletions

.mockery.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ packages:
6565
EntryInputTarget: {}
6666
HomepageFavorites: {}
6767
HomepageHistory: {}
68+
HistorySidebarHistory: {}
6869
AllKeybindingsResetter: {}
6970
KeybindingResetter: {}
7071
KeybindingSetter: {}

docs/reference/keybindings.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ These work outside modal modes:
8383
| Action | Keys |
8484
|--------|------|
8585
| Toggle floating pane | `Alt+F` |
86-
| Toggle History system view in right split (may conflict with the browser's default History shortcut; behavior can vary by browser) | `Ctrl+H` |
86+
| Toggle History sidebar (native GTK sidebar panel only). Ctrl+H may conflict with the browser's default History shortcut; behavior can vary by browser. | `Ctrl+H` |
8787
| Toggle Favorites system view in right split | unbound by default |
8888
| Toggle Config system view in right split | unbound by default |
8989
| Close pane (or release floating pane) | `Ctrl+W` |
@@ -96,7 +96,7 @@ These work outside modal modes:
9696

9797
- `Alt+F` is the only floating-pane shortcut enabled by default.
9898
- `Alt+F` toggles floating visibility and keeps floating pane state intact.
99-
- `Ctrl+H` toggles `dumb://history`: it focuses an existing History pane, opens it in a right split if missing, or closes it when already active. Depending on the browser, the default History shortcut may intercept `Ctrl+H`, so behavior can vary.
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`.
100100
- `Ctrl+W` closes the active pane; when the floating pane is active, it fully releases that floating session.
101101
- Any URL shortcut (for example `Alt+G`) must be defined explicitly in `workspace.floating_pane.profiles`.
102102
- Floating profile shortcuts support modifier combos with `ctrl`, `shift`, and `alt` (for example `ctrl+shift+y` or `ctrl+alt+m`).
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package port
2+
3+
import (
4+
"context"
5+
6+
"github.com/bnema/dumber/internal/application/dto"
7+
"github.com/bnema/dumber/internal/domain/entity"
8+
)
9+
10+
// HistorySidebarHistory provides the narrow history operations needed by the
11+
// native GTK history sidebar.
12+
type HistorySidebarHistory interface {
13+
GetRecent(ctx context.Context, limit, offset int) ([]*entity.HistoryEntry, error)
14+
Search(ctx context.Context, input dto.HistorySearchInput) (*dto.HistorySearchOutput, error)
15+
Delete(ctx context.Context, id int64) error
16+
}

internal/application/usecase/search_history.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@ import (
1515
"github.com/bnema/dumber/internal/logging"
1616
)
1717

18-
// Compile-time check: SearchHistoryUseCase must satisfy port.HomepageHistory.
18+
// Compile-time checks: SearchHistoryUseCase must satisfy the application
19+
// history ports used by WebUI handlers and the native GTK history sidebar.
1920
var _ port.HomepageHistory = (*SearchHistoryUseCase)(nil)
21+
var _ port.HistorySidebarHistory = (*SearchHistoryUseCase)(nil)
2022

2123
const (
2224
historyWindowDuration = 24 * time.Hour

internal/infrastructure/config/defaults.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ const (
2121
defaultMaxLogFiles = 100 // session log files
2222

2323
// Appearance defaults
24-
defaultFontSize = 16 // points
24+
defaultSidebarWidth = 320 // px, clamped to [280, 380]
25+
defaultFontSize = 16 // points
2526
defaultExternalThemeProvider = "noctalia"
2627
defaultExternalThemeFormat = "colors-json"
2728
defaultExternalThemeColorsFilename = "colors.json"
@@ -251,8 +252,9 @@ func DefaultConfig() *Config {
251252
GLRenderingMode: GLRenderingModeAuto,
252253
},
253254
},
254-
DefaultWebpageZoom: 1.2, // 120% default zoom for better readability
255-
DefaultUIScale: defaultUIScale, // 1.0 = 100%, 2.0 = 200%
255+
DefaultWebpageZoom: 1.2, // 120% default zoom for better readability
256+
DefaultUIScale: defaultUIScale, // 1.0 = 100%, 2.0 = 200%
257+
SidebarWidth: defaultSidebarWidth, // 320px, clamped to [280, 380]
256258
Workspace: WorkspaceConfig{
257259
NewPaneURL: defaultNewPaneURL,
258260
SwitchToTabOnMove: true,

internal/infrastructure/config/loader.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,7 @@ func (m *Manager) setAppearanceDefaults(defaults *Config) {
664664
func (m *Manager) setZoomAndScaleDefaults(defaults *Config) {
665665
m.viper.SetDefault("default_webpage_zoom", defaults.DefaultWebpageZoom)
666666
m.viper.SetDefault("default_ui_scale", defaults.DefaultUIScale)
667+
m.viper.SetDefault("sidebar_width", defaults.SidebarWidth)
667668
}
668669

669670
func (m *Manager) setWorkspaceDefaults(defaults *Config) {

internal/infrastructure/config/migrate.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -283,10 +283,10 @@ const (
283283

284284
// Config format constants.
285285
const (
286-
configFormatTOML = "toml"
287-
configFormatYAML = "yaml"
288-
configFormatJSON = "json"
289-
databasePathKey = "database.path"
286+
configFormatTOML = "toml"
287+
configFormatYAML = "yaml"
288+
configFormatJSON = "json"
289+
databasePathKey = "database.path"
290290
)
291291

292292
type defaultActionMap struct {

internal/infrastructure/config/schema.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ type Config struct {
9595
DefaultWebpageZoom float64 `mapstructure:"default_webpage_zoom" yaml:"default_webpage_zoom" toml:"default_webpage_zoom"`
9696
// DefaultUIScale sets the default UI scale for GTK widgets (1.0 = 100%, 2.0 = 200%)
9797
DefaultUIScale float64 `mapstructure:"default_ui_scale" yaml:"default_ui_scale" toml:"default_ui_scale"`
98+
// SidebarWidth sets the preferred width (px) for the history sidebar.
99+
// Clamped to [280, 380] at runtime. 0 means use default (320).
100+
SidebarWidth int `mapstructure:"sidebar_width" yaml:"sidebar_width" toml:"sidebar_width"`
98101
// Workspace defines workspace, pane, and tab handling behavior.
99102
Workspace WorkspaceConfig `mapstructure:"workspace" yaml:"workspace" toml:"workspace"`
100103
// Session controls session persistence and restoration.

internal/infrastructure/config/schema_provider.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,14 @@ func (*SchemaProvider) getAppearanceKeys(defaults *Config) []entity.ConfigKeyInf
161161
Range: "0.5-3.0",
162162
Section: SectionAppearance,
163163
},
164+
{
165+
Key: "sidebar_width",
166+
Type: "int",
167+
Default: fmt.Sprintf("%d", defaults.SidebarWidth),
168+
Description: "Preferred width (px) for the history sidebar. 0 = default",
169+
Range: "0 or 280-380",
170+
Section: SectionAppearance,
171+
},
164172
{
165173
Key: "appearance.light_palette.*",
166174
Type: "string",

internal/infrastructure/config/validation.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@ func validateAppearance(config *Config) []string {
104104
if config.DefaultUIScale < 0.5 || config.DefaultUIScale > 3.0 {
105105
validationErrors = append(validationErrors, "default_ui_scale must be between 0.5 and 3.0")
106106
}
107+
if config.SidebarWidth != 0 && (config.SidebarWidth < 280 || config.SidebarWidth > 380) {
108+
validationErrors = append(validationErrors, "sidebar_width must be between 280 and 380, or 0 for default")
109+
}
107110
validationErrors = append(validationErrors, validateExternalTheme(config)...)
108111
return validationErrors
109112
}

0 commit comments

Comments
 (0)