detached d0d5c96

refactor(M1+M5): decouple handlers from usecase/config, deps.go from infrastructure

M1: Define narrow port interfaces (HomepageHistory, HomepageFavorites,
AccentKeyHandler, DownloadPreparer, AutoCopyConfig) and refactor all
webkit handlers to depend on port interfaces instead of concrete
usecase/config types. Move keybinding and config handler construction
to bootstrap.

M5: Replace infrastructure types in ui/Dependencies with port interfaces
(ToolkitAvailabilityNotifier, FaviconService, FilterManager). Move
FilterStatus to port package with type alias in filtering.
commit
d0d5c96339691afe4174d789f1d3026b41dd2d8a
author
brice · · 2026-03-17 10:12 UTC
parent
cfe4a9c
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
27 files · +336 -181
M cmd/dumber/main.go +2 -4
@@ -18,11 +18,9 @@ import (
1818 "github.com/bnema/dumber/internal/domain/repository"
1919 "github.com/bnema/dumber/internal/infrastructure/cache"
2020 "github.com/bnema/dumber/internal/infrastructure/clipboard"
21- "github.com/bnema/dumber/internal/infrastructure/colorscheme"
2221 "github.com/bnema/dumber/internal/infrastructure/config"
2322 "github.com/bnema/dumber/internal/infrastructure/deps"
2423 "github.com/bnema/dumber/internal/infrastructure/favicon"
25- "github.com/bnema/dumber/internal/infrastructure/filtering"
2624 "github.com/bnema/dumber/internal/infrastructure/idle"
2725 "github.com/bnema/dumber/internal/infrastructure/persistence/sqlite"
2826 "github.com/bnema/dumber/internal/infrastructure/updater"
@@ -507,7 +505,7 @@ func buildUIDependencies(
507505 cfg *config.Config,
508506 themeManager *theme.Manager,
509507 colorResolver port.ColorSchemeResolver,
510- adwaitaDetector *colorscheme.AdwaitaDetector,
508+ adwaitaDetector port.ToolkitAvailabilityNotifier,
511509 engine port.Engine,
512510 repos *repositories,
513511 uc *useCases,
@@ -516,7 +514,7 @@ func buildUIDependencies(
516514 startupCrashReports []string,
517515 ) *ui.Dependencies {
518516 // Type-assert to *webkit.Engine to access FilterManager (not yet on port.Engine).
519- var filterManager *filtering.Manager
517+ var filterManager port.FilterManager
520518 if wkEngine, ok := engine.(*webkit.Engine); ok {
521519 filterManager = wkEngine.InternalFilterManager()
522520 }
@@ -18,11 +18,9 @@ import (
18 "github.com/bnema/dumber/internal/domain/repository"18 "github.com/bnema/dumber/internal/domain/repository"
19 "github.com/bnema/dumber/internal/infrastructure/cache"19 "github.com/bnema/dumber/internal/infrastructure/cache"
20 "github.com/bnema/dumber/internal/infrastructure/clipboard"20 "github.com/bnema/dumber/internal/infrastructure/clipboard"
21- "github.com/bnema/dumber/internal/infrastructure/colorscheme"
22 "github.com/bnema/dumber/internal/infrastructure/config"21 "github.com/bnema/dumber/internal/infrastructure/config"
23 "github.com/bnema/dumber/internal/infrastructure/deps"22 "github.com/bnema/dumber/internal/infrastructure/deps"
24 "github.com/bnema/dumber/internal/infrastructure/favicon"23 "github.com/bnema/dumber/internal/infrastructure/favicon"
25- "github.com/bnema/dumber/internal/infrastructure/filtering"
26 "github.com/bnema/dumber/internal/infrastructure/idle"24 "github.com/bnema/dumber/internal/infrastructure/idle"
27 "github.com/bnema/dumber/internal/infrastructure/persistence/sqlite"25 "github.com/bnema/dumber/internal/infrastructure/persistence/sqlite"
28 "github.com/bnema/dumber/internal/infrastructure/updater"26 "github.com/bnema/dumber/internal/infrastructure/updater"
@@ -507,7 +505,7 @@ func buildUIDependencies(
507 cfg *config.Config,505 cfg *config.Config,
508 themeManager *theme.Manager,506 themeManager *theme.Manager,
509 colorResolver port.ColorSchemeResolver,507 colorResolver port.ColorSchemeResolver,
510- adwaitaDetector *colorscheme.AdwaitaDetector,508+ adwaitaDetector port.ToolkitAvailabilityNotifier,
511 engine port.Engine,509 engine port.Engine,
512 repos *repositories,510 repos *repositories,
513 uc *useCases,511 uc *useCases,
@@ -516,7 +514,7 @@ func buildUIDependencies(
516 startupCrashReports []string,514 startupCrashReports []string,
517 ) *ui.Dependencies {515 ) *ui.Dependencies {
518 // Type-assert to *webkit.Engine to access FilterManager (not yet on port.Engine).516 // Type-assert to *webkit.Engine to access FilterManager (not yet on port.Engine).
519- var filterManager *filtering.Manager517+ var filterManager port.FilterManager
520 if wkEngine, ok := engine.(*webkit.Engine); ok {518 if wkEngine, ok := engine.(*webkit.Engine); ok {
521 filterManager = wkEngine.InternalFilterManager()519 filterManager = wkEngine.InternalFilterManager()
522 }520 }
M internal/application/port/color_scheme.go +5 -0
@@ -33,6 +33,11 @@ type ColorSchemeDetector interface {
3333 Detect() (prefersDark bool, ok bool)
3434 }
3535
36+// ToolkitAvailabilityNotifier signals that a toolkit (e.g., GTK/Adwaita) is available.
37+type ToolkitAvailabilityNotifier interface {
38+ MarkAvailable()
39+}
40+
3641 // ColorSchemeResolver resolves the effective color scheme preference.
3742 // It manages multiple detectors and respects config overrides.
3843 type ColorSchemeResolver interface {
@@ -33,6 +33,11 @@ type ColorSchemeDetector interface {
33 Detect() (prefersDark bool, ok bool)33 Detect() (prefersDark bool, ok bool)
34 }34 }
35 35
36+// ToolkitAvailabilityNotifier signals that a toolkit (e.g., GTK/Adwaita) is available.
37+type ToolkitAvailabilityNotifier interface {
38+ MarkAvailable()
39+}
40+
36 // ColorSchemeResolver resolves the effective color scheme preference.41 // ColorSchemeResolver resolves the effective color scheme preference.
37 // It manages multiple detectors and respects config overrides.42 // It manages multiple detectors and respects config overrides.
38 type ColorSchemeResolver interface {43 type ColorSchemeResolver interface {
A internal/application/port/favicon_service.go +16 -0
@@ -0,0 +1,16 @@
1+package port
2+
3+import "context"
4+
5+// FaviconService provides favicon retrieval and caching.
6+type FaviconService interface {
7+ GetCached(domain string) ([]byte, bool)
8+ Get(ctx context.Context, domain string) ([]byte, error)
9+ DiskPathPNG(domain string) string
10+ HasPNGOnDisk(domain string) bool
11+ HasPNGSizedOnDisk(domain string, size int) bool
12+ EnsureSizedPNG(ctx context.Context, domain string, size int) error
13+ EnsureCacheDir() error
14+ EnsureDiskCache(ctx context.Context, domain string)
15+ Close()
16+}
@@ -0,0 +1,16 @@
1+package port
2+
3+import "context"
4+
5+// FaviconService provides favicon retrieval and caching.
6+type FaviconService interface {
7+ GetCached(domain string) ([]byte, bool)
8+ Get(ctx context.Context, domain string) ([]byte, error)
9+ DiskPathPNG(domain string) string
10+ HasPNGOnDisk(domain string) bool
11+ HasPNGSizedOnDisk(domain string, size int) bool
12+ EnsureSizedPNG(ctx context.Context, domain string, size int) error
13+ EnsureCacheDir() error
14+ EnsureDiskCache(ctx context.Context, domain string)
15+ Close()
16+}
A internal/application/port/filter_manager.go +33 -0
@@ -0,0 +1,33 @@
1+package port
2+
3+import "context"
4+
5+// FilterState represents the current state of the content filter system.
6+type FilterState string
7+
8+const (
9+ // FilterStateUninitialized means the filter manager has not been initialized yet.
10+ FilterStateUninitialized FilterState = "uninitialized"
11+ // FilterStateLoading means filters are being downloaded or compiled.
12+ FilterStateLoading FilterState = "loading"
13+ // FilterStateActive means filters are loaded and active.
14+ FilterStateActive FilterState = "active"
15+ // FilterStateDisabled means filtering is disabled by configuration.
16+ FilterStateDisabled FilterState = "disabled"
17+ // FilterStateError means an error occurred during filter loading.
18+ FilterStateError FilterState = "error"
19+)
20+
21+// FilterStatus represents the current status of the content filter system.
22+// Used for UI toast notifications and status reporting.
23+type FilterStatus struct {
24+ State FilterState
25+ Message string
26+ Version string
27+}
28+
29+// FilterManager manages content filters.
30+type FilterManager interface {
31+ SetStatusCallback(fn func(FilterStatus))
32+ LoadAsync(ctx context.Context)
33+}
@@ -0,0 +1,33 @@
1+package port
2+
3+import "context"
4+
5+// FilterState represents the current state of the content filter system.
6+type FilterState string
7+
8+const (
9+ // FilterStateUninitialized means the filter manager has not been initialized yet.
10+ FilterStateUninitialized FilterState = "uninitialized"
11+ // FilterStateLoading means filters are being downloaded or compiled.
12+ FilterStateLoading FilterState = "loading"
13+ // FilterStateActive means filters are loaded and active.
14+ FilterStateActive FilterState = "active"
15+ // FilterStateDisabled means filtering is disabled by configuration.
16+ FilterStateDisabled FilterState = "disabled"
17+ // FilterStateError means an error occurred during filter loading.
18+ FilterStateError FilterState = "error"
19+)
20+
21+// FilterStatus represents the current status of the content filter system.
22+// Used for UI toast notifications and status reporting.
23+type FilterStatus struct {
24+ State FilterState
25+ Message string
26+ Version string
27+}
28+
29+// FilterManager manages content filters.
30+type FilterManager interface {
31+ SetStatusCallback(fn func(FilterStatus))
32+ LoadAsync(ctx context.Context)
33+}
A internal/application/port/handler_deps.go +32 -0
@@ -0,0 +1,32 @@
1+package port
2+
3+import "context"
4+
5+// AccentKeyHandler receives accent/dead-key events from the WebView JS bridge.
6+type AccentKeyHandler interface {
7+ OnKeyPressed(ctx context.Context, char rune, shiftHeld bool) bool
8+ OnKeyReleased(ctx context.Context, char rune)
9+}
10+
11+// DownloadPreparer resolves download destination paths with deduplication.
12+type DownloadPreparer interface {
13+ Execute(ctx context.Context, input DownloadPrepareInput) *DownloadPrepareOutput
14+}
15+
16+// DownloadPrepareInput matches usecase.PrepareDownloadInput.
17+type DownloadPrepareInput struct {
18+ SuggestedFilename string
19+ Response DownloadResponse
20+ DownloadDir string
21+}
22+
23+// DownloadPrepareOutput matches usecase.PrepareDownloadOutput.
24+type DownloadPrepareOutput struct {
25+ Filename string
26+ DestinationPath string
27+}
28+
29+// AutoCopyConfig provides the clipboard auto-copy setting.
30+type AutoCopyConfig interface {
31+ IsAutoCopyEnabled() bool
32+}
@@ -0,0 +1,32 @@
1+package port
2+
3+import "context"
4+
5+// AccentKeyHandler receives accent/dead-key events from the WebView JS bridge.
6+type AccentKeyHandler interface {
7+ OnKeyPressed(ctx context.Context, char rune, shiftHeld bool) bool
8+ OnKeyReleased(ctx context.Context, char rune)
9+}
10+
11+// DownloadPreparer resolves download destination paths with deduplication.
12+type DownloadPreparer interface {
13+ Execute(ctx context.Context, input DownloadPrepareInput) *DownloadPrepareOutput
14+}
15+
16+// DownloadPrepareInput matches usecase.PrepareDownloadInput.
17+type DownloadPrepareInput struct {
18+ SuggestedFilename string
19+ Response DownloadResponse
20+ DownloadDir string
21+}
22+
23+// DownloadPrepareOutput matches usecase.PrepareDownloadOutput.
24+type DownloadPrepareOutput struct {
25+ Filename string
26+ DestinationPath string
27+}
28+
29+// AutoCopyConfig provides the clipboard auto-copy setting.
30+type AutoCopyConfig interface {
31+ IsAutoCopyEnabled() bool
32+}
A internal/application/port/homepage.go +50 -0
@@ -0,0 +1,50 @@
1+package port
2+
3+import (
4+ "context"
5+ "time"
6+
7+ "github.com/bnema/dumber/internal/domain/entity"
8+)
9+
10+// HistorySearchInput holds search parameters (moved from usecase.SearchInput).
11+type HistorySearchInput struct {
12+ Query string
13+ Limit int
14+}
15+
16+// HistorySearchOutput holds search results (moved from usecase.SearchOutput).
17+type HistorySearchOutput struct {
18+ Matches []entity.HistoryMatch
19+}
20+
21+// HomepageHistory provides history operations needed by the WebUI homepage handlers.
22+type HomepageHistory interface {
23+ GetRecent(ctx context.Context, limit, offset int) ([]*entity.HistoryEntry, error)
24+ Search(ctx context.Context, input HistorySearchInput) (*HistorySearchOutput, error)
25+ Delete(ctx context.Context, id int64) error
26+ ClearAll(ctx context.Context) error
27+ ClearOlderThan(ctx context.Context, before time.Time) error
28+ GetAnalytics(ctx context.Context) (*entity.HistoryAnalytics, error)
29+ GetDomainStats(ctx context.Context, limit int) ([]*entity.DomainStat, error)
30+ DeleteByDomain(ctx context.Context, domain string) error
31+}
32+
33+// HomepageFavorites provides favorites/folders/tags operations
34+// needed by the WebUI homepage handlers.
35+type HomepageFavorites interface {
36+ GetAll(ctx context.Context) ([]*entity.Favorite, error)
37+ SetShortcut(ctx context.Context, id entity.FavoriteID, key *int) error
38+ GetByShortcut(ctx context.Context, key int) (*entity.Favorite, error)
39+ Move(ctx context.Context, id entity.FavoriteID, folderID *entity.FolderID) error
40+ GetAllFolders(ctx context.Context) ([]*entity.Folder, error)
41+ CreateFolder(ctx context.Context, name string, parentID *entity.FolderID) (*entity.Folder, error)
42+ DeleteFolder(ctx context.Context, id entity.FolderID) error
43+ UpdateFolder(ctx context.Context, id entity.FolderID, name, icon string) error
44+ GetAllTags(ctx context.Context) ([]*entity.Tag, error)
45+ AddTag(ctx context.Context, name, color string) (*entity.Tag, error)
46+ DeleteTag(ctx context.Context, id entity.TagID) error
47+ UpdateTag(ctx context.Context, id entity.TagID, name, color string) error
48+ TagFavorite(ctx context.Context, favID entity.FavoriteID, tagID entity.TagID) error
49+ UntagFavorite(ctx context.Context, favID entity.FavoriteID, tagID entity.TagID) error
50+}
@@ -0,0 +1,50 @@
1+package port
2+
3+import (
4+ "context"
5+ "time"
6+
7+ "github.com/bnema/dumber/internal/domain/entity"
8+)
9+
10+// HistorySearchInput holds search parameters (moved from usecase.SearchInput).
11+type HistorySearchInput struct {
12+ Query string
13+ Limit int
14+}
15+
16+// HistorySearchOutput holds search results (moved from usecase.SearchOutput).
17+type HistorySearchOutput struct {
18+ Matches []entity.HistoryMatch
19+}
20+
21+// HomepageHistory provides history operations needed by the WebUI homepage handlers.
22+type HomepageHistory interface {
23+ GetRecent(ctx context.Context, limit, offset int) ([]*entity.HistoryEntry, error)
24+ Search(ctx context.Context, input HistorySearchInput) (*HistorySearchOutput, error)
25+ Delete(ctx context.Context, id int64) error
26+ ClearAll(ctx context.Context) error
27+ ClearOlderThan(ctx context.Context, before time.Time) error
28+ GetAnalytics(ctx context.Context) (*entity.HistoryAnalytics, error)
29+ GetDomainStats(ctx context.Context, limit int) ([]*entity.DomainStat, error)
30+ DeleteByDomain(ctx context.Context, domain string) error
31+}
32+
33+// HomepageFavorites provides favorites/folders/tags operations
34+// needed by the WebUI homepage handlers.
35+type HomepageFavorites interface {
36+ GetAll(ctx context.Context) ([]*entity.Favorite, error)
37+ SetShortcut(ctx context.Context, id entity.FavoriteID, key *int) error
38+ GetByShortcut(ctx context.Context, key int) (*entity.Favorite, error)
39+ Move(ctx context.Context, id entity.FavoriteID, folderID *entity.FolderID) error
40+ GetAllFolders(ctx context.Context) ([]*entity.Folder, error)
41+ CreateFolder(ctx context.Context, name string, parentID *entity.FolderID) (*entity.Folder, error)
42+ DeleteFolder(ctx context.Context, id entity.FolderID) error
43+ UpdateFolder(ctx context.Context, id entity.FolderID, name, icon string) error
44+ GetAllTags(ctx context.Context) ([]*entity.Tag, error)
45+ AddTag(ctx context.Context, name, color string) (*entity.Tag, error)
46+ DeleteTag(ctx context.Context, id entity.TagID) error
47+ UpdateTag(ctx context.Context, id entity.TagID, name, color string) error
48+ TagFavorite(ctx context.Context, favID entity.FavoriteID, tagID entity.TagID) error
49+ UntagFavorite(ctx context.Context, favID entity.FavoriteID, tagID entity.TagID) error
50+}
M internal/application/port/keybindings.go +20 -0
@@ -64,3 +64,23 @@ type KeybindingsSaver interface {
6464 ResetKeybinding(ctx context.Context, req ResetKeybindingRequest) error
6565 ResetAllKeybindings(ctx context.Context) error
6666 }
67+
68+// KeybindingsGetter retrieves all keybindings (narrow interface for the handler).
69+type KeybindingsGetter interface {
70+ Execute(ctx context.Context) (KeybindingsConfig, error)
71+}
72+
73+// KeybindingSetter updates a single keybinding (narrow interface for the handler).
74+type KeybindingSetter interface {
75+ Execute(ctx context.Context, req SetKeybindingRequest) (SetKeybindingResponse, error)
76+}
77+
78+// KeybindingResetter resets a single keybinding to default (narrow interface for the handler).
79+type KeybindingResetter interface {
80+ Execute(ctx context.Context, req ResetKeybindingRequest) error
81+}
82+
83+// AllKeybindingsResetter resets all keybindings to defaults (narrow interface for the handler).
84+type AllKeybindingsResetter interface {
85+ Execute(ctx context.Context) error
86+}
@@ -64,3 +64,23 @@ type KeybindingsSaver interface {
64 ResetKeybinding(ctx context.Context, req ResetKeybindingRequest) error64 ResetKeybinding(ctx context.Context, req ResetKeybindingRequest) error
65 ResetAllKeybindings(ctx context.Context) error65 ResetAllKeybindings(ctx context.Context) error
66 }66 }
67+
68+// KeybindingsGetter retrieves all keybindings (narrow interface for the handler).
69+type KeybindingsGetter interface {
70+ Execute(ctx context.Context) (KeybindingsConfig, error)
71+}
72+
73+// KeybindingSetter updates a single keybinding (narrow interface for the handler).
74+type KeybindingSetter interface {
75+ Execute(ctx context.Context, req SetKeybindingRequest) (SetKeybindingResponse, error)
76+}
77+
78+// KeybindingResetter resets a single keybinding to default (narrow interface for the handler).
79+type KeybindingResetter interface {
80+ Execute(ctx context.Context, req ResetKeybindingRequest) error
81+}
82+
83+// AllKeybindingsResetter resets all keybindings to defaults (narrow interface for the handler).
84+type AllKeybindingsResetter interface {
85+ Execute(ctx context.Context) error
86+}
M internal/application/usecase/insert_accent.go +3 -0
@@ -11,6 +11,9 @@ import (
1111 "github.com/bnema/dumber/internal/logging"
1212 )
1313
14+// Compile-time check: InsertAccentUseCase must satisfy port.AccentKeyHandler.
15+var _ port.AccentKeyHandler = (*InsertAccentUseCase)(nil)
16+
1417 // LongPressDelay is the duration a key must be held to trigger the accent picker.
1518 const LongPressDelay = 400 * time.Millisecond
1619
@@ -11,6 +11,9 @@ import (
11 "github.com/bnema/dumber/internal/logging"11 "github.com/bnema/dumber/internal/logging"
12 )12 )
13 13
14+// Compile-time check: InsertAccentUseCase must satisfy port.AccentKeyHandler.
15+var _ port.AccentKeyHandler = (*InsertAccentUseCase)(nil)
16+
14 // LongPressDelay is the duration a key must be held to trigger the accent picker.17 // LongPressDelay is the duration a key must be held to trigger the accent picker.
15 const LongPressDelay = 400 * time.Millisecond18 const LongPressDelay = 400 * time.Millisecond
16 19
M internal/application/usecase/keybindings.go +6 -0
@@ -7,6 +7,12 @@ import (
77 "github.com/bnema/dumber/internal/application/port"
88 )
99
10+// Compile-time checks: keybinding use cases must satisfy their port interfaces.
11+var _ port.KeybindingsGetter = (*GetKeybindingsUseCase)(nil)
12+var _ port.KeybindingSetter = (*SetKeybindingUseCase)(nil)
13+var _ port.KeybindingResetter = (*ResetKeybindingUseCase)(nil)
14+var _ port.AllKeybindingsResetter = (*ResetAllKeybindingsUseCase)(nil)
15+
1016 // validModes defines the valid keybinding modes.
1117 var validModes = map[string]bool{
1218 "global": true,
@@ -7,6 +7,12 @@ import (
7 "github.com/bnema/dumber/internal/application/port"7 "github.com/bnema/dumber/internal/application/port"
8 )8 )
9 9
10+// Compile-time checks: keybinding use cases must satisfy their port interfaces.
11+var _ port.KeybindingsGetter = (*GetKeybindingsUseCase)(nil)
12+var _ port.KeybindingSetter = (*SetKeybindingUseCase)(nil)
13+var _ port.KeybindingResetter = (*ResetKeybindingUseCase)(nil)
14+var _ port.AllKeybindingsResetter = (*ResetAllKeybindingsUseCase)(nil)
15+
10 // validModes defines the valid keybinding modes.16 // validModes defines the valid keybinding modes.
11 var validModes = map[string]bool{17 var validModes = map[string]bool{
12 "global": true,18 "global": true,
M internal/application/usecase/manage_favorites.go +4 -0
@@ -8,12 +8,16 @@ import (
88 "sync"
99 "time"
1010
11+ "github.com/bnema/dumber/internal/application/port"
1112 "github.com/bnema/dumber/internal/domain/entity"
1213 "github.com/bnema/dumber/internal/domain/repository"
1314 domainurl "github.com/bnema/dumber/internal/domain/url"
1415 "github.com/bnema/dumber/internal/logging"
1516 )
1617
18+// Compile-time check: ManageFavoritesUseCase must satisfy port.HomepageFavorites.
19+var _ port.HomepageFavorites = (*ManageFavoritesUseCase)(nil)
20+
1721 // ManageFavoritesUseCase handles favorite, folder, and tag operations.
1822 type ManageFavoritesUseCase struct {
1923 favoriteRepo repository.FavoriteRepository
@@ -8,12 +8,16 @@ import (
8 "sync"8 "sync"
9 "time"9 "time"
10 10
11+ "github.com/bnema/dumber/internal/application/port"
11 "github.com/bnema/dumber/internal/domain/entity"12 "github.com/bnema/dumber/internal/domain/entity"
12 "github.com/bnema/dumber/internal/domain/repository"13 "github.com/bnema/dumber/internal/domain/repository"
13 domainurl "github.com/bnema/dumber/internal/domain/url"14 domainurl "github.com/bnema/dumber/internal/domain/url"
14 "github.com/bnema/dumber/internal/logging"15 "github.com/bnema/dumber/internal/logging"
15 )16 )
16 17
18+// Compile-time check: ManageFavoritesUseCase must satisfy port.HomepageFavorites.
19+var _ port.HomepageFavorites = (*ManageFavoritesUseCase)(nil)
20+
17 // ManageFavoritesUseCase handles favorite, folder, and tag operations.21 // ManageFavoritesUseCase handles favorite, folder, and tag operations.
18 type ManageFavoritesUseCase struct {22 type ManageFavoritesUseCase struct {
19 favoriteRepo repository.FavoriteRepository23 favoriteRepo repository.FavoriteRepository
M internal/application/usecase/prepare_download.go +7 -17
@@ -9,24 +9,14 @@ import (
99 "github.com/bnema/dumber/internal/logging"
1010 )
1111
12-// PrepareDownloadInput contains the inputs for preparing a download destination.
13-type PrepareDownloadInput struct {
14- // SuggestedFilename is the filename suggested by WebKit (from Content-Disposition or other sources).
15- SuggestedFilename string
16- // Response provides additional metadata (MIME type, URI) for filename resolution.
17- // May be nil if not available.
18- Response port.DownloadResponse
19- // DownloadDir is the directory where downloads should be saved.
20- DownloadDir string
21-}
12+// PrepareDownloadInput is an alias for port.DownloadPrepareInput.
13+type PrepareDownloadInput = port.DownloadPrepareInput
2214
23-// PrepareDownloadOutput contains the resolved download destination.
24-type PrepareDownloadOutput struct {
25- // Filename is the sanitized, safe filename to use.
26- Filename string
27- // DestinationPath is the full path where the download should be saved.
28- DestinationPath string
29-}
15+// PrepareDownloadOutput is an alias for port.DownloadPrepareOutput.
16+type PrepareDownloadOutput = port.DownloadPrepareOutput
17+
18+// Compile-time check: PrepareDownloadUseCase must satisfy port.DownloadPreparer.
19+var _ port.DownloadPreparer = (*PrepareDownloadUseCase)(nil)
3020
3121 // PrepareDownloadUseCase orchestrates filename resolution for downloads.
3222 // It combines multiple sources (suggested name, response headers, URI) to
@@ -9,24 +9,14 @@ import (
9 "github.com/bnema/dumber/internal/logging"9 "github.com/bnema/dumber/internal/logging"
10 )10 )
11 11
12-// PrepareDownloadInput contains the inputs for preparing a download destination.12+// PrepareDownloadInput is an alias for port.DownloadPrepareInput.
13-type PrepareDownloadInput struct {13+type PrepareDownloadInput = port.DownloadPrepareInput
14- // SuggestedFilename is the filename suggested by WebKit (from Content-Disposition or other sources).
15- SuggestedFilename string
16- // Response provides additional metadata (MIME type, URI) for filename resolution.
17- // May be nil if not available.
18- Response port.DownloadResponse
19- // DownloadDir is the directory where downloads should be saved.
20- DownloadDir string
21-}
22 14
23-// PrepareDownloadOutput contains the resolved download destination.15+// PrepareDownloadOutput is an alias for port.DownloadPrepareOutput.
24-type PrepareDownloadOutput struct {16+type PrepareDownloadOutput = port.DownloadPrepareOutput
25- // Filename is the sanitized, safe filename to use.17+
26- Filename string18+// Compile-time check: PrepareDownloadUseCase must satisfy port.DownloadPreparer.
27- // DestinationPath is the full path where the download should be saved.19+var _ port.DownloadPreparer = (*PrepareDownloadUseCase)(nil)
28- DestinationPath string
29-}
30 20
31 // PrepareDownloadUseCase orchestrates filename resolution for downloads.21 // PrepareDownloadUseCase orchestrates filename resolution for downloads.
32 // It combines multiple sources (suggested name, response headers, URI) to22 // It combines multiple sources (suggested name, response headers, URI) to
M internal/application/usecase/search_history.go +8 -9
@@ -5,11 +5,15 @@ import (
55 "fmt"
66 "time"
77
8+ "github.com/bnema/dumber/internal/application/port"
89 "github.com/bnema/dumber/internal/domain/entity"
910 "github.com/bnema/dumber/internal/domain/repository"
1011 "github.com/bnema/dumber/internal/logging"
1112 )
1213
14+// Compile-time check: SearchHistoryUseCase must satisfy port.HomepageHistory.
15+var _ port.HomepageHistory = (*SearchHistoryUseCase)(nil)
16+
1317 // SearchHistoryUseCase handles history search and retrieval operations.
1418 type SearchHistoryUseCase struct {
1519 historyRepo repository.HistoryRepository
@@ -22,16 +26,11 @@ func NewSearchHistoryUseCase(historyRepo repository.HistoryRepository) *SearchHi
2226 }
2327 }
2428
25-// SearchInput contains search parameters.
26-type SearchInput struct {
27- Query string
28- Limit int
29-}
29+// SearchInput is an alias for port.HistorySearchInput.
30+type SearchInput = port.HistorySearchInput
3031
31-// SearchOutput contains search results.
32-type SearchOutput struct {
33- Matches []entity.HistoryMatch
34-}
32+// SearchOutput is an alias for port.HistorySearchOutput.
33+type SearchOutput = port.HistorySearchOutput
3534
3635 // Search performs a full-text search on history entries using SQLite FTS5.
3736 // Returns only entries that actually match the query terms.
@@ -5,11 +5,15 @@ import (
5 "fmt"5 "fmt"
6 "time"6 "time"
7 7
8+ "github.com/bnema/dumber/internal/application/port"
8 "github.com/bnema/dumber/internal/domain/entity"9 "github.com/bnema/dumber/internal/domain/entity"
9 "github.com/bnema/dumber/internal/domain/repository"10 "github.com/bnema/dumber/internal/domain/repository"
10 "github.com/bnema/dumber/internal/logging"11 "github.com/bnema/dumber/internal/logging"
11 )12 )
12 13
14+// Compile-time check: SearchHistoryUseCase must satisfy port.HomepageHistory.
15+var _ port.HomepageHistory = (*SearchHistoryUseCase)(nil)
16+
13 // SearchHistoryUseCase handles history search and retrieval operations.17 // SearchHistoryUseCase handles history search and retrieval operations.
14 type SearchHistoryUseCase struct {18 type SearchHistoryUseCase struct {
15 historyRepo repository.HistoryRepository19 historyRepo repository.HistoryRepository
@@ -22,16 +26,11 @@ func NewSearchHistoryUseCase(historyRepo repository.HistoryRepository) *SearchHi
22 }26 }
23 }27 }
24 28
25-// SearchInput contains search parameters.29+// SearchInput is an alias for port.HistorySearchInput.
26-type SearchInput struct {30+type SearchInput = port.HistorySearchInput
27- Query string
28- Limit int
29-}
30 31
31-// SearchOutput contains search results.32+// SearchOutput is an alias for port.HistorySearchOutput.
32-type SearchOutput struct {33+type SearchOutput = port.HistorySearchOutput
33- Matches []entity.HistoryMatch
34-}
35 34
36 // Search performs a full-text search on history entries using SQLite FTS5.35 // Search performs a full-text search on history entries using SQLite FTS5.
37 // Returns only entries that actually match the query terms.36 // Returns only entries that actually match the query terms.
M internal/bootstrap/engine.go +55 -20
@@ -40,41 +40,46 @@ func BuildEngine(input EngineInput) (port.Engine, error) {
4040 CookiePolicy: port.CookiePolicy(cfg.Engine.CookiePolicy),
4141 }
4242 wkCfg := webkit.EngineConfigFromConfig(cfg.Engine.WebKit)
43+
44+ // Pre-build keybindings handler for handler registration.
45+ keybindingsHandler, err := buildKeybindingsHandler()
46+ if err != nil {
47+ return nil, fmt.Errorf("failed to build keybindings handler: %w", err)
48+ }
49+
50+ // Pre-build config save function for handler registration.
51+ saveConfigFunc := buildSaveConfigFunc()
52+
4353 return webkit.NewEngine(
4454 input.Ctx, cfg, opts, wkCfg,
4555 input.ThemeManager, input.ColorResolver, input.Logger,
4656 func(ctx context.Context, router *webkit.MessageRouter, deps port.HandlerDependencies) error {
47- var historyUC *usecase.SearchHistoryUseCase
57+ var historyUC port.HomepageHistory
4858 if deps.HistoryUC != nil {
49- historyUC, _ = deps.HistoryUC.(*usecase.SearchHistoryUseCase)
59+ historyUC, _ = deps.HistoryUC.(port.HomepageHistory)
5060 }
51- var favoritesUC *usecase.ManageFavoritesUseCase
61+ var favoritesUC port.HomepageFavorites
5262 if deps.FavoritesUC != nil {
53- favoritesUC, _ = deps.FavoritesUC.(*usecase.ManageFavoritesUseCase)
63+ favoritesUC, _ = deps.FavoritesUC.(port.HomepageFavorites)
5464 }
55- var configGetter func() *config.Config
65+ var autoCopyConfig port.AutoCopyConfig
5666 if deps.ConfigGetter != nil {
57- configGetter = func() *config.Config {
58- raw := deps.ConfigGetter()
59- if raw == nil {
60- return nil
61- }
62- cfg, _ := raw.(*config.Config)
63- return cfg
64- }
67+ autoCopyConfig, _ = deps.ConfigGetter().(port.AutoCopyConfig)
6568 }
6669 return handlers.RegisterAll(ctx, router, handlers.Config{
67- HistoryUC: historyUC,
68- FavoritesUC: favoritesUC,
69- Clipboard: deps.Clipboard,
70- ConfigGetter: configGetter,
71- OnClipboardCopied: deps.OnClipboardCopied,
70+ HistoryUC: historyUC,
71+ FavoritesUC: favoritesUC,
72+ Clipboard: deps.Clipboard,
73+ AutoCopyConfig: autoCopyConfig,
74+ SaveConfig: saveConfigFunc,
75+ KeybindingsHandler: keybindingsHandler,
76+ OnClipboardCopied: deps.OnClipboardCopied,
7277 })
7378 },
7479 func(ctx context.Context, router *webkit.MessageRouter, handler any) error {
75- accentHandler, ok := handler.(handlers.AccentKeyHandler)
80+ accentHandler, ok := handler.(port.AccentKeyHandler)
7681 if !ok {
77- return fmt.Errorf("handler does not implement AccentKeyHandler")
82+ return fmt.Errorf("handler does not implement port.AccentKeyHandler")
7883 }
7984 return handlers.RegisterAccentHandlers(ctx, router, accentHandler)
8085 },
@@ -85,3 +90,33 @@ func BuildEngine(input EngineInput) (port.Engine, error) {
8590 return nil, fmt.Errorf("unknown engine type: %q", engineType)
8691 }
8792 }
93+
94+// buildKeybindingsHandler constructs the keybindings handler using the config manager.
95+func buildKeybindingsHandler() (*handlers.KeybindingsHandler, error) {
96+ mgr := config.GetManager()
97+ if mgr == nil {
98+ return nil, fmt.Errorf("config manager not initialized")
99+ }
100+
101+ gateway := config.NewKeybindingsGateway(mgr)
102+
103+ return handlers.NewKeybindingsHandler(
104+ usecase.NewGetKeybindingsUseCase(gateway),
105+ usecase.NewSetKeybindingUseCase(gateway, gateway),
106+ usecase.NewResetKeybindingUseCase(gateway),
107+ usecase.NewResetAllKeybindingsUseCase(gateway),
108+ ), nil
109+}
110+
111+// buildSaveConfigFunc constructs the config save function using the config manager.
112+func buildSaveConfigFunc() func(context.Context, port.WebUIConfig) error {
113+ mgr := config.GetManager()
114+ if mgr == nil {
115+ // Return a function that errors — config manager may not be ready yet.
116+ return func(_ context.Context, _ port.WebUIConfig) error {
117+ return fmt.Errorf("config manager not initialized")
118+ }
119+ }
120+ uc := usecase.NewSaveWebUIConfigUseCase(config.NewWebUIConfigGateway(mgr))
121+ return uc.Execute
122+}
@@ -40,41 +40,46 @@ func BuildEngine(input EngineInput) (port.Engine, error) {
40 CookiePolicy: port.CookiePolicy(cfg.Engine.CookiePolicy),40 CookiePolicy: port.CookiePolicy(cfg.Engine.CookiePolicy),
41 }41 }
42 wkCfg := webkit.EngineConfigFromConfig(cfg.Engine.WebKit)42 wkCfg := webkit.EngineConfigFromConfig(cfg.Engine.WebKit)
43+
44+ // Pre-build keybindings handler for handler registration.
45+ keybindingsHandler, err := buildKeybindingsHandler()
46+ if err != nil {
47+ return nil, fmt.Errorf("failed to build keybindings handler: %w", err)
48+ }
49+
50+ // Pre-build config save function for handler registration.
51+ saveConfigFunc := buildSaveConfigFunc()
52+
43 return webkit.NewEngine(53 return webkit.NewEngine(
44 input.Ctx, cfg, opts, wkCfg,54 input.Ctx, cfg, opts, wkCfg,
45 input.ThemeManager, input.ColorResolver, input.Logger,55 input.ThemeManager, input.ColorResolver, input.Logger,
46 func(ctx context.Context, router *webkit.MessageRouter, deps port.HandlerDependencies) error {56 func(ctx context.Context, router *webkit.MessageRouter, deps port.HandlerDependencies) error {
47- var historyUC *usecase.SearchHistoryUseCase57+ var historyUC port.HomepageHistory
48 if deps.HistoryUC != nil {58 if deps.HistoryUC != nil {
49- historyUC, _ = deps.HistoryUC.(*usecase.SearchHistoryUseCase)59+ historyUC, _ = deps.HistoryUC.(port.HomepageHistory)
50 }60 }
51- var favoritesUC *usecase.ManageFavoritesUseCase61+ var favoritesUC port.HomepageFavorites
52 if deps.FavoritesUC != nil {62 if deps.FavoritesUC != nil {
53- favoritesUC, _ = deps.FavoritesUC.(*usecase.ManageFavoritesUseCase)63+ favoritesUC, _ = deps.FavoritesUC.(port.HomepageFavorites)
54 }64 }
55- var configGetter func() *config.Config65+ var autoCopyConfig port.AutoCopyConfig
56 if deps.ConfigGetter != nil {66 if deps.ConfigGetter != nil {
57- configGetter = func() *config.Config {67+ autoCopyConfig, _ = deps.ConfigGetter().(port.AutoCopyConfig)
58- raw := deps.ConfigGetter()
59- if raw == nil {
60- return nil
61- }
62- cfg, _ := raw.(*config.Config)
63- return cfg
64- }
65 }68 }
66 return handlers.RegisterAll(ctx, router, handlers.Config{69 return handlers.RegisterAll(ctx, router, handlers.Config{
67- HistoryUC: historyUC,70+ HistoryUC: historyUC,
68- FavoritesUC: favoritesUC,71+ FavoritesUC: favoritesUC,
69- Clipboard: deps.Clipboard,72+ Clipboard: deps.Clipboard,
70- ConfigGetter: configGetter,73+ AutoCopyConfig: autoCopyConfig,
71- OnClipboardCopied: deps.OnClipboardCopied,74+ SaveConfig: saveConfigFunc,
75+ KeybindingsHandler: keybindingsHandler,
76+ OnClipboardCopied: deps.OnClipboardCopied,
72 })77 })
73 },78 },
74 func(ctx context.Context, router *webkit.MessageRouter, handler any) error {79 func(ctx context.Context, router *webkit.MessageRouter, handler any) error {
75- accentHandler, ok := handler.(handlers.AccentKeyHandler)80+ accentHandler, ok := handler.(port.AccentKeyHandler)
76 if !ok {81 if !ok {
77- return fmt.Errorf("handler does not implement AccentKeyHandler")82+ return fmt.Errorf("handler does not implement port.AccentKeyHandler")
78 }83 }
79 return handlers.RegisterAccentHandlers(ctx, router, accentHandler)84 return handlers.RegisterAccentHandlers(ctx, router, accentHandler)
80 },85 },
@@ -85,3 +90,33 @@ func BuildEngine(input EngineInput) (port.Engine, error) {
85 return nil, fmt.Errorf("unknown engine type: %q", engineType)90 return nil, fmt.Errorf("unknown engine type: %q", engineType)
86 }91 }
87 }92 }
93+
94+// buildKeybindingsHandler constructs the keybindings handler using the config manager.
95+func buildKeybindingsHandler() (*handlers.KeybindingsHandler, error) {
96+ mgr := config.GetManager()
97+ if mgr == nil {
98+ return nil, fmt.Errorf("config manager not initialized")
99+ }
100+
101+ gateway := config.NewKeybindingsGateway(mgr)
102+
103+ return handlers.NewKeybindingsHandler(
104+ usecase.NewGetKeybindingsUseCase(gateway),
105+ usecase.NewSetKeybindingUseCase(gateway, gateway),
106+ usecase.NewResetKeybindingUseCase(gateway),
107+ usecase.NewResetAllKeybindingsUseCase(gateway),
108+ ), nil
109+}
110+
111+// buildSaveConfigFunc constructs the config save function using the config manager.
112+func buildSaveConfigFunc() func(context.Context, port.WebUIConfig) error {
113+ mgr := config.GetManager()
114+ if mgr == nil {
115+ // Return a function that errors — config manager may not be ready yet.
116+ return func(_ context.Context, _ port.WebUIConfig) error {
117+ return fmt.Errorf("config manager not initialized")
118+ }
119+ }
120+ uc := usecase.NewSaveWebUIConfigUseCase(config.NewWebUIConfigGateway(mgr))
121+ return uc.Execute
122+}
M internal/infrastructure/filtering/manager.go +4 -0
@@ -12,10 +12,14 @@ import (
1212 "sync/atomic"
1313 "time"
1414
15+ "github.com/bnema/dumber/internal/application/port"
1516 "github.com/bnema/dumber/internal/logging"
1617 "github.com/bnema/puregotk-webkit/webkit"
1718 )
1819
20+// Compile-time check that Manager satisfies port.FilterManager.
21+var _ port.FilterManager = (*Manager)(nil)
22+
1923 const (
2024 storeDirPerm = 0o755
2125 jsonDirPerm = 0o755
@@ -12,10 +12,14 @@ import (
12 "sync/atomic"12 "sync/atomic"
13 "time"13 "time"
14 14
15+ "github.com/bnema/dumber/internal/application/port"
15 "github.com/bnema/dumber/internal/logging"16 "github.com/bnema/dumber/internal/logging"
16 "github.com/bnema/puregotk-webkit/webkit"17 "github.com/bnema/puregotk-webkit/webkit"
17 )18 )
18 19
20+// Compile-time check that Manager satisfies port.FilterManager.
21+var _ port.FilterManager = (*Manager)(nil)
22+
19 const (23 const (
20 storeDirPerm = 0o75524 storeDirPerm = 0o755
21 jsonDirPerm = 0o75525 jsonDirPerm = 0o755
M internal/infrastructure/filtering/types.go +15 -16
@@ -1,31 +1,30 @@
11 package filtering
22
3-import "time"
3+import (
4+ "time"
45
5-// FilterState represents the current state of the content filter system.
6-type FilterState string
6+ "github.com/bnema/dumber/internal/application/port"
7+)
8+
9+// FilterState is an alias for port.FilterState to avoid breaking existing references.
10+type FilterState = port.FilterState
11+
12+// FilterStatus is an alias for port.FilterStatus so filtering.Manager satisfies port.FilterManager.
13+type FilterStatus = port.FilterStatus
714
815 const (
916 // StateUninitialized means the filter manager has not been initialized yet.
10- StateUninitialized FilterState = "uninitialized"
17+ StateUninitialized FilterState = port.FilterStateUninitialized
1118 // StateLoading means filters are being downloaded or compiled.
12- StateLoading FilterState = "loading"
19+ StateLoading FilterState = port.FilterStateLoading
1320 // StateActive means filters are loaded and active.
14- StateActive FilterState = "active"
21+ StateActive FilterState = port.FilterStateActive
1522 // StateDisabled means filtering is disabled by configuration.
16- StateDisabled FilterState = "disabled"
23+ StateDisabled FilterState = port.FilterStateDisabled
1724 // StateError means an error occurred during filter loading.
18- StateError FilterState = "error"
25+ StateError FilterState = port.FilterStateError
1926 )
2027
21-// FilterStatus represents the current status of the content filter system.
22-// Used for UI toast notifications and status reporting.
23-type FilterStatus struct {
24- State FilterState
25- Message string
26- Version string
27-}
28-
2928 // Manifest represents the manifest.json from ublock-webkit-filters releases.
3029 // It contains metadata about the filter lists and their rule counts.
3130 type Manifest struct {
@@ -1,31 +1,30 @@
1 package filtering1 package filtering
2 2
3-import "time"3+import (
4+ "time"
4 5
5-// FilterState represents the current state of the content filter system.6+ "github.com/bnema/dumber/internal/application/port"
6-type FilterState string7+)
8+
9+// FilterState is an alias for port.FilterState to avoid breaking existing references.
10+type FilterState = port.FilterState
11+
12+// FilterStatus is an alias for port.FilterStatus so filtering.Manager satisfies port.FilterManager.
13+type FilterStatus = port.FilterStatus
7 14
8 const (15 const (
9 // StateUninitialized means the filter manager has not been initialized yet.16 // StateUninitialized means the filter manager has not been initialized yet.
10- StateUninitialized FilterState = "uninitialized"17+ StateUninitialized FilterState = port.FilterStateUninitialized
11 // StateLoading means filters are being downloaded or compiled.18 // StateLoading means filters are being downloaded or compiled.
12- StateLoading FilterState = "loading"19+ StateLoading FilterState = port.FilterStateLoading
13 // StateActive means filters are loaded and active.20 // StateActive means filters are loaded and active.
14- StateActive FilterState = "active"21+ StateActive FilterState = port.FilterStateActive
15 // StateDisabled means filtering is disabled by configuration.22 // StateDisabled means filtering is disabled by configuration.
16- StateDisabled FilterState = "disabled"23+ StateDisabled FilterState = port.FilterStateDisabled
17 // StateError means an error occurred during filter loading.24 // StateError means an error occurred during filter loading.
18- StateError FilterState = "error"25+ StateError FilterState = port.FilterStateError
19 )26 )
20 27
21-// FilterStatus represents the current status of the content filter system.
22-// Used for UI toast notifications and status reporting.
23-type FilterStatus struct {
24- State FilterState
25- Message string
26- Version string
27-}
28-
29 // Manifest represents the manifest.json from ublock-webkit-filters releases.28 // Manifest represents the manifest.json from ublock-webkit-filters releases.
30 // It contains metadata about the filter lists and their rule counts.29 // It contains metadata about the filter lists and their rule counts.
31 type Manifest struct {30 type Manifest struct {
M internal/infrastructure/webkit/handlers/clipboard.go +10 -12
@@ -5,24 +5,23 @@ import (
55 "encoding/json"
66
77 "github.com/bnema/dumber/internal/application/port"
8- "github.com/bnema/dumber/internal/infrastructure/config"
98 "github.com/bnema/dumber/internal/infrastructure/webkit"
109 "github.com/bnema/dumber/internal/logging"
1110 )
1211
1312 // ClipboardHandler handles clipboard-related messages from webviews.
1413 type ClipboardHandler struct {
15- clipboard port.Clipboard
16- configGetter func() *config.Config
17- onCopied func(textLen int) // Called after successful auto-copy (for toast notification)
14+ clipboard port.Clipboard
15+ autoCopyConfig port.AutoCopyConfig
16+ onCopied func(textLen int) // Called after successful auto-copy (for toast notification)
1817 }
1918
2019 // NewClipboardHandler creates a new ClipboardHandler.
21-func NewClipboardHandler(clipboard port.Clipboard, configGetter func() *config.Config, onCopied func(textLen int)) *ClipboardHandler {
20+func NewClipboardHandler(clipboard port.Clipboard, autoCopyConfig port.AutoCopyConfig, onCopied func(textLen int)) *ClipboardHandler {
2221 return &ClipboardHandler{
23- clipboard: clipboard,
24- configGetter: configGetter,
25- onCopied: onCopied,
22+ clipboard: clipboard,
23+ autoCopyConfig: autoCopyConfig,
24+ onCopied: onCopied,
2625 }
2726 }
2827
@@ -38,8 +37,7 @@ func (h *ClipboardHandler) HandleAutoCopySelection() webkit.MessageHandler {
3837 log := logging.FromContext(ctx)
3938
4039 // Check if feature is enabled
41- cfg := h.configGetter()
42- if cfg == nil || !cfg.Clipboard.AutoCopyOnSelection {
40+ if h.autoCopyConfig == nil || !h.autoCopyConfig.IsAutoCopyEnabled() {
4341 return nil, nil
4442 }
4543
@@ -75,10 +73,10 @@ func RegisterClipboardHandlers(
7573 ctx context.Context,
7674 router *webkit.MessageRouter,
7775 clipboard port.Clipboard,
78- configGetter func() *config.Config,
76+ autoCopyConfig port.AutoCopyConfig,
7977 onCopied func(textLen int),
8078 ) error {
81- handler := NewClipboardHandler(clipboard, configGetter, onCopied)
79+ handler := NewClipboardHandler(clipboard, autoCopyConfig, onCopied)
8280
8381 // Register auto_copy_selection handler
8482 if err := router.RegisterHandler("auto_copy_selection", handler.HandleAutoCopySelection()); err != nil {
@@ -5,24 +5,23 @@ import (
5 "encoding/json"5 "encoding/json"
6 6
7 "github.com/bnema/dumber/internal/application/port"7 "github.com/bnema/dumber/internal/application/port"
8- "github.com/bnema/dumber/internal/infrastructure/config"
9 "github.com/bnema/dumber/internal/infrastructure/webkit"8 "github.com/bnema/dumber/internal/infrastructure/webkit"
10 "github.com/bnema/dumber/internal/logging"9 "github.com/bnema/dumber/internal/logging"
11 )10 )
12 11
13 // ClipboardHandler handles clipboard-related messages from webviews.12 // ClipboardHandler handles clipboard-related messages from webviews.
14 type ClipboardHandler struct {13 type ClipboardHandler struct {
15- clipboard port.Clipboard14+ clipboard port.Clipboard
16- configGetter func() *config.Config15+ autoCopyConfig port.AutoCopyConfig
17- onCopied func(textLen int) // Called after successful auto-copy (for toast notification)16+ onCopied func(textLen int) // Called after successful auto-copy (for toast notification)
18 }17 }
19 18
20 // NewClipboardHandler creates a new ClipboardHandler.19 // NewClipboardHandler creates a new ClipboardHandler.
21-func NewClipboardHandler(clipboard port.Clipboard, configGetter func() *config.Config, onCopied func(textLen int)) *ClipboardHandler {20+func NewClipboardHandler(clipboard port.Clipboard, autoCopyConfig port.AutoCopyConfig, onCopied func(textLen int)) *ClipboardHandler {
22 return &ClipboardHandler{21 return &ClipboardHandler{
23- clipboard: clipboard,22+ clipboard: clipboard,
24- configGetter: configGetter,23+ autoCopyConfig: autoCopyConfig,
25- onCopied: onCopied,24+ onCopied: onCopied,
26 }25 }
27 }26 }
28 27
@@ -38,8 +37,7 @@ func (h *ClipboardHandler) HandleAutoCopySelection() webkit.MessageHandler {
38 log := logging.FromContext(ctx)37 log := logging.FromContext(ctx)
39 38
40 // Check if feature is enabled39 // Check if feature is enabled
41- cfg := h.configGetter()40+ if h.autoCopyConfig == nil || !h.autoCopyConfig.IsAutoCopyEnabled() {
42- if cfg == nil || !cfg.Clipboard.AutoCopyOnSelection {
43 return nil, nil41 return nil, nil
44 }42 }
45 43
@@ -75,10 +73,10 @@ func RegisterClipboardHandlers(
75 ctx context.Context,73 ctx context.Context,
76 router *webkit.MessageRouter,74 router *webkit.MessageRouter,
77 clipboard port.Clipboard,75 clipboard port.Clipboard,
78- configGetter func() *config.Config,76+ autoCopyConfig port.AutoCopyConfig,
79 onCopied func(textLen int),77 onCopied func(textLen int),
80 ) error {78 ) error {
81- handler := NewClipboardHandler(clipboard, configGetter, onCopied)79+ handler := NewClipboardHandler(clipboard, autoCopyConfig, onCopied)
82 80
83 // Register auto_copy_selection handler81 // Register auto_copy_selection handler
84 if err := router.RegisterHandler("auto_copy_selection", handler.HandleAutoCopySelection()); err != nil {82 if err := router.RegisterHandler("auto_copy_selection", handler.HandleAutoCopySelection()); err != nil {
M internal/infrastructure/webkit/handlers/config.go +12 -19
@@ -6,18 +6,19 @@ import (
66 "fmt"
77
88 "github.com/bnema/dumber/internal/application/port"
9- "github.com/bnema/dumber/internal/application/usecase"
10- "github.com/bnema/dumber/internal/infrastructure/config"
119 "github.com/bnema/dumber/internal/infrastructure/webkit"
1210 "github.com/bnema/dumber/internal/logging"
1311 )
1412
1513 // ConfigHandler handles configuration-related messages.
16-type ConfigHandler struct{}
14+type ConfigHandler struct {
15+ saveConfig func(context.Context, port.WebUIConfig) error
16+}
1717
1818 // NewConfigHandler creates a new ConfigHandler.
19-func NewConfigHandler() *ConfigHandler {
20- return &ConfigHandler{}
19+// saveConfig is called to persist config changes (typically usecase.SaveWebUIConfigUseCase.Execute).
20+func NewConfigHandler(saveConfig func(context.Context, port.WebUIConfig) error) *ConfigHandler {
21+ return &ConfigHandler{saveConfig: saveConfig}
2122 }
2223
2324 // Handle processes the save_config message.
@@ -25,6 +26,9 @@ func (h *ConfigHandler) Handle(ctx context.Context, _ webkit.WebViewID, payload
2526 if h == nil {
2627 return nil, fmt.Errorf("config handler is nil")
2728 }
29+ if h.saveConfig == nil {
30+ return nil, fmt.Errorf("config handler: saveConfig not initialized")
31+ }
2832 log := logging.FromContext(ctx).With().Str("handler", "config").Logger()
2933
3034 var payloadCfg port.WebUIConfig
@@ -34,13 +38,8 @@ func (h *ConfigHandler) Handle(ctx context.Context, _ webkit.WebViewID, payload
3438 }
3539
3640 log.Info().Msg("saving appearance configuration from webui")
37- mgr := config.GetManager()
38- if mgr == nil {
39- return nil, fmt.Errorf("config manager not initialized")
40- }
4141
42- uc := usecase.NewSaveWebUIConfigUseCase(config.NewWebUIConfigGateway(mgr))
43- if err := uc.Execute(ctx, payloadCfg); err != nil {
42+ if err := h.saveConfig(ctx, payloadCfg); err != nil {
4443 log.Error().Err(err).Msg("failed to save config")
4544 return nil, fmt.Errorf("failed to save config: %w", err)
4645 }
@@ -49,17 +48,11 @@ func (h *ConfigHandler) Handle(ctx context.Context, _ webkit.WebViewID, payload
4948 }
5049
5150 // RegisterConfigHandlers registers configuration handlers with the router.
52-func RegisterConfigHandlers(ctx context.Context, router *webkit.MessageRouter) error {
53- handler := NewConfigHandler()
54-
55- // worldName empty means main world (since dumb:// pages run in main world)
56- // callback/errorCallback can be used if we want to notify JS of success/fail
57- // In Svelte we use fetch() for GET, but for POST we use message bridge
58- // Actually, if we use message bridge, we might want response callbacks.
51+func RegisterConfigHandlers(ctx context.Context, router *webkit.MessageRouter, saveConfig func(context.Context, port.WebUIConfig) error) error {
52+ handler := NewConfigHandler(saveConfig)
5953
6054 log := logging.FromContext(ctx).With().Str("component", "handlers").Logger()
6155
62- // Message type: save_config
6356 if err := router.RegisterHandlerWithCallbacks(
6457 "save_config",
6558 "__dumber_config_saved",
@@ -6,18 +6,19 @@ import (
6 "fmt"6 "fmt"
7 7
8 "github.com/bnema/dumber/internal/application/port"8 "github.com/bnema/dumber/internal/application/port"
9- "github.com/bnema/dumber/internal/application/usecase"
10- "github.com/bnema/dumber/internal/infrastructure/config"
11 "github.com/bnema/dumber/internal/infrastructure/webkit"9 "github.com/bnema/dumber/internal/infrastructure/webkit"
12 "github.com/bnema/dumber/internal/logging"10 "github.com/bnema/dumber/internal/logging"
13 )11 )
14 12
15 // ConfigHandler handles configuration-related messages.13 // ConfigHandler handles configuration-related messages.
16-type ConfigHandler struct{}14+type ConfigHandler struct {
15+ saveConfig func(context.Context, port.WebUIConfig) error
16+}
17 17
18 // NewConfigHandler creates a new ConfigHandler.18 // NewConfigHandler creates a new ConfigHandler.
19-func NewConfigHandler() *ConfigHandler {19+// saveConfig is called to persist config changes (typically usecase.SaveWebUIConfigUseCase.Execute).
20- return &ConfigHandler{}20+func NewConfigHandler(saveConfig func(context.Context, port.WebUIConfig) error) *ConfigHandler {
21+ return &ConfigHandler{saveConfig: saveConfig}
21 }22 }
22 23
23 // Handle processes the save_config message.24 // Handle processes the save_config message.
@@ -25,6 +26,9 @@ func (h *ConfigHandler) Handle(ctx context.Context, _ webkit.WebViewID, payload
25 if h == nil {26 if h == nil {
26 return nil, fmt.Errorf("config handler is nil")27 return nil, fmt.Errorf("config handler is nil")
27 }28 }
29+ if h.saveConfig == nil {
30+ return nil, fmt.Errorf("config handler: saveConfig not initialized")
31+ }
28 log := logging.FromContext(ctx).With().Str("handler", "config").Logger()32 log := logging.FromContext(ctx).With().Str("handler", "config").Logger()
29 33
30 var payloadCfg port.WebUIConfig34 var payloadCfg port.WebUIConfig
@@ -34,13 +38,8 @@ func (h *ConfigHandler) Handle(ctx context.Context, _ webkit.WebViewID, payload
34 }38 }
35 39
36 log.Info().Msg("saving appearance configuration from webui")40 log.Info().Msg("saving appearance configuration from webui")
37- mgr := config.GetManager()
38- if mgr == nil {
39- return nil, fmt.Errorf("config manager not initialized")
40- }
41 41
42- uc := usecase.NewSaveWebUIConfigUseCase(config.NewWebUIConfigGateway(mgr))42+ if err := h.saveConfig(ctx, payloadCfg); err != nil {
43- if err := uc.Execute(ctx, payloadCfg); err != nil {
44 log.Error().Err(err).Msg("failed to save config")43 log.Error().Err(err).Msg("failed to save config")
45 return nil, fmt.Errorf("failed to save config: %w", err)44 return nil, fmt.Errorf("failed to save config: %w", err)
46 }45 }
@@ -49,17 +48,11 @@ func (h *ConfigHandler) Handle(ctx context.Context, _ webkit.WebViewID, payload
49 }48 }
50 49
51 // RegisterConfigHandlers registers configuration handlers with the router.50 // RegisterConfigHandlers registers configuration handlers with the router.
52-func RegisterConfigHandlers(ctx context.Context, router *webkit.MessageRouter) error {51+func RegisterConfigHandlers(ctx context.Context, router *webkit.MessageRouter, saveConfig func(context.Context, port.WebUIConfig) error) error {
53- handler := NewConfigHandler()52+ handler := NewConfigHandler(saveConfig)
54-
55- // worldName empty means main world (since dumb:// pages run in main world)
56- // callback/errorCallback can be used if we want to notify JS of success/fail
57- // In Svelte we use fetch() for GET, but for POST we use message bridge
58- // Actually, if we use message bridge, we might want response callbacks.
59 53
60 log := logging.FromContext(ctx).With().Str("component", "handlers").Logger()54 log := logging.FromContext(ctx).With().Str("component", "handlers").Logger()
61 55
62- // Message type: save_config
63 if err := router.RegisterHandlerWithCallbacks(56 if err := router.RegisterHandlerWithCallbacks(
64 "save_config",57 "save_config",
65 "__dumber_config_saved",58 "__dumber_config_saved",
M internal/infrastructure/webkit/handlers/homepage/favorites.go +3 -3
@@ -4,7 +4,7 @@ import (
44 "context"
55 "encoding/json"
66
7- "github.com/bnema/dumber/internal/application/usecase"
7+ "github.com/bnema/dumber/internal/application/port"
88 "github.com/bnema/dumber/internal/domain/entity"
99 "github.com/bnema/dumber/internal/infrastructure/webkit"
1010 "github.com/bnema/dumber/internal/logging"
@@ -12,11 +12,11 @@ import (
1212
1313 // FavoritesHandlers handles favorite-related messages from the homepage.
1414 type FavoritesHandlers struct {
15- favoritesUC *usecase.ManageFavoritesUseCase
15+ favoritesUC port.HomepageFavorites
1616 }
1717
1818 // NewFavoritesHandlers creates a new FavoritesHandlers instance.
19-func NewFavoritesHandlers(favoritesUC *usecase.ManageFavoritesUseCase) *FavoritesHandlers {
19+func NewFavoritesHandlers(favoritesUC port.HomepageFavorites) *FavoritesHandlers {
2020 return &FavoritesHandlers{favoritesUC: favoritesUC}
2121 }
2222
@@ -4,7 +4,7 @@ import (
4 "context"4 "context"
5 "encoding/json"5 "encoding/json"
6 6
7- "github.com/bnema/dumber/internal/application/usecase"7+ "github.com/bnema/dumber/internal/application/port"
8 "github.com/bnema/dumber/internal/domain/entity"8 "github.com/bnema/dumber/internal/domain/entity"
9 "github.com/bnema/dumber/internal/infrastructure/webkit"9 "github.com/bnema/dumber/internal/infrastructure/webkit"
10 "github.com/bnema/dumber/internal/logging"10 "github.com/bnema/dumber/internal/logging"
@@ -12,11 +12,11 @@ import (
12 12
13 // FavoritesHandlers handles favorite-related messages from the homepage.13 // FavoritesHandlers handles favorite-related messages from the homepage.
14 type FavoritesHandlers struct {14 type FavoritesHandlers struct {
15- favoritesUC *usecase.ManageFavoritesUseCase15+ favoritesUC port.HomepageFavorites
16 }16 }
17 17
18 // NewFavoritesHandlers creates a new FavoritesHandlers instance.18 // NewFavoritesHandlers creates a new FavoritesHandlers instance.
19-func NewFavoritesHandlers(favoritesUC *usecase.ManageFavoritesUseCase) *FavoritesHandlers {19+func NewFavoritesHandlers(favoritesUC port.HomepageFavorites) *FavoritesHandlers {
20 return &FavoritesHandlers{favoritesUC: favoritesUC}20 return &FavoritesHandlers{favoritesUC: favoritesUC}
21 }21 }
22 22
M internal/infrastructure/webkit/handlers/homepage/folders.go +3 -3
@@ -4,7 +4,7 @@ import (
44 "context"
55 "encoding/json"
66
7- "github.com/bnema/dumber/internal/application/usecase"
7+ "github.com/bnema/dumber/internal/application/port"
88 "github.com/bnema/dumber/internal/domain/entity"
99 "github.com/bnema/dumber/internal/infrastructure/webkit"
1010 "github.com/bnema/dumber/internal/logging"
@@ -12,11 +12,11 @@ import (
1212
1313 // FolderHandlers handles folder-related messages from the homepage.
1414 type FolderHandlers struct {
15- favoritesUC *usecase.ManageFavoritesUseCase
15+ favoritesUC port.HomepageFavorites
1616 }
1717
1818 // NewFolderHandlers creates a new FolderHandlers instance.
19-func NewFolderHandlers(favoritesUC *usecase.ManageFavoritesUseCase) *FolderHandlers {
19+func NewFolderHandlers(favoritesUC port.HomepageFavorites) *FolderHandlers {
2020 return &FolderHandlers{favoritesUC: favoritesUC}
2121 }
2222
@@ -4,7 +4,7 @@ import (
4 "context"4 "context"
5 "encoding/json"5 "encoding/json"
6 6
7- "github.com/bnema/dumber/internal/application/usecase"7+ "github.com/bnema/dumber/internal/application/port"
8 "github.com/bnema/dumber/internal/domain/entity"8 "github.com/bnema/dumber/internal/domain/entity"
9 "github.com/bnema/dumber/internal/infrastructure/webkit"9 "github.com/bnema/dumber/internal/infrastructure/webkit"
10 "github.com/bnema/dumber/internal/logging"10 "github.com/bnema/dumber/internal/logging"
@@ -12,11 +12,11 @@ import (
12 12
13 // FolderHandlers handles folder-related messages from the homepage.13 // FolderHandlers handles folder-related messages from the homepage.
14 type FolderHandlers struct {14 type FolderHandlers struct {
15- favoritesUC *usecase.ManageFavoritesUseCase15+ favoritesUC port.HomepageFavorites
16 }16 }
17 17
18 // NewFolderHandlers creates a new FolderHandlers instance.18 // NewFolderHandlers creates a new FolderHandlers instance.
19-func NewFolderHandlers(favoritesUC *usecase.ManageFavoritesUseCase) *FolderHandlers {19+func NewFolderHandlers(favoritesUC port.HomepageFavorites) *FolderHandlers {
20 return &FolderHandlers{favoritesUC: favoritesUC}20 return &FolderHandlers{favoritesUC: favoritesUC}
21 }21 }
22 22
M internal/infrastructure/webkit/handlers/homepage/history.go +4 -4
@@ -5,7 +5,7 @@ import (
55 "encoding/json"
66 "time"
77
8- "github.com/bnema/dumber/internal/application/usecase"
8+ "github.com/bnema/dumber/internal/application/port"
99 "github.com/bnema/dumber/internal/domain/entity"
1010 "github.com/bnema/dumber/internal/infrastructure/webkit"
1111 "github.com/bnema/dumber/internal/logging"
@@ -13,11 +13,11 @@ import (
1313
1414 // HistoryHandlers handles history-related messages from the homepage.
1515 type HistoryHandlers struct {
16- historyUC *usecase.SearchHistoryUseCase
16+ historyUC port.HomepageHistory
1717 }
1818
1919 // NewHistoryHandlers creates a new HistoryHandlers instance.
20-func NewHistoryHandlers(historyUC *usecase.SearchHistoryUseCase) *HistoryHandlers {
20+func NewHistoryHandlers(historyUC port.HomepageHistory) *HistoryHandlers {
2121 return &HistoryHandlers{historyUC: historyUC}
2222 }
2323
@@ -75,7 +75,7 @@ func (h *HistoryHandlers) HandleSearchFTS() webkit.MessageHandler {
7575 Str("query", req.Query).
7676 Msg("handling history_search_fts")
7777
78- output, err := h.historyUC.Search(ctx, usecase.SearchInput{
78+ output, err := h.historyUC.Search(ctx, port.HistorySearchInput{
7979 Query: req.Query,
8080 Limit: req.Limit,
8181 })
@@ -5,7 +5,7 @@ import (
5 "encoding/json"5 "encoding/json"
6 "time"6 "time"
7 7
8- "github.com/bnema/dumber/internal/application/usecase"8+ "github.com/bnema/dumber/internal/application/port"
9 "github.com/bnema/dumber/internal/domain/entity"9 "github.com/bnema/dumber/internal/domain/entity"
10 "github.com/bnema/dumber/internal/infrastructure/webkit"10 "github.com/bnema/dumber/internal/infrastructure/webkit"
11 "github.com/bnema/dumber/internal/logging"11 "github.com/bnema/dumber/internal/logging"
@@ -13,11 +13,11 @@ import (
13 13
14 // HistoryHandlers handles history-related messages from the homepage.14 // HistoryHandlers handles history-related messages from the homepage.
15 type HistoryHandlers struct {15 type HistoryHandlers struct {
16- historyUC *usecase.SearchHistoryUseCase16+ historyUC port.HomepageHistory
17 }17 }
18 18
19 // NewHistoryHandlers creates a new HistoryHandlers instance.19 // NewHistoryHandlers creates a new HistoryHandlers instance.
20-func NewHistoryHandlers(historyUC *usecase.SearchHistoryUseCase) *HistoryHandlers {20+func NewHistoryHandlers(historyUC port.HomepageHistory) *HistoryHandlers {
21 return &HistoryHandlers{historyUC: historyUC}21 return &HistoryHandlers{historyUC: historyUC}
22 }22 }
23 23
@@ -75,7 +75,7 @@ func (h *HistoryHandlers) HandleSearchFTS() webkit.MessageHandler {
75 Str("query", req.Query).75 Str("query", req.Query).
76 Msg("handling history_search_fts")76 Msg("handling history_search_fts")
77 77
78- output, err := h.historyUC.Search(ctx, usecase.SearchInput{78+ output, err := h.historyUC.Search(ctx, port.HistorySearchInput{
79 Query: req.Query,79 Query: req.Query,
80 Limit: req.Limit,80 Limit: req.Limit,
81 })81 })
M internal/infrastructure/webkit/handlers/homepage/registry.go +3 -3
@@ -4,15 +4,15 @@ import (
44 "context"
55 "fmt"
66
7- "github.com/bnema/dumber/internal/application/usecase"
7+ "github.com/bnema/dumber/internal/application/port"
88 "github.com/bnema/dumber/internal/infrastructure/webkit"
99 "github.com/bnema/dumber/internal/logging"
1010 )
1111
1212 // Config holds dependencies for homepage handlers.
1313 type Config struct {
14- HistoryUC *usecase.SearchHistoryUseCase
15- FavoritesUC *usecase.ManageFavoritesUseCase
14+ HistoryUC port.HomepageHistory
15+ FavoritesUC port.HomepageFavorites
1616 }
1717
1818 // RegisterHandlers registers all homepage message handlers with the router.
@@ -4,15 +4,15 @@ import (
4 "context"4 "context"
5 "fmt"5 "fmt"
6 6
7- "github.com/bnema/dumber/internal/application/usecase"7+ "github.com/bnema/dumber/internal/application/port"
8 "github.com/bnema/dumber/internal/infrastructure/webkit"8 "github.com/bnema/dumber/internal/infrastructure/webkit"
9 "github.com/bnema/dumber/internal/logging"9 "github.com/bnema/dumber/internal/logging"
10 )10 )
11 11
12 // Config holds dependencies for homepage handlers.12 // Config holds dependencies for homepage handlers.
13 type Config struct {13 type Config struct {
14- HistoryUC *usecase.SearchHistoryUseCase14+ HistoryUC port.HomepageHistory
15- FavoritesUC *usecase.ManageFavoritesUseCase15+ FavoritesUC port.HomepageFavorites
16 }16 }
17 17
18 // RegisterHandlers registers all homepage message handlers with the router.18 // RegisterHandlers registers all homepage message handlers with the router.
M internal/infrastructure/webkit/handlers/homepage/tags.go +3 -3
@@ -4,7 +4,7 @@ import (
44 "context"
55 "encoding/json"
66
7- "github.com/bnema/dumber/internal/application/usecase"
7+ "github.com/bnema/dumber/internal/application/port"
88 "github.com/bnema/dumber/internal/domain/entity"
99 "github.com/bnema/dumber/internal/infrastructure/webkit"
1010 "github.com/bnema/dumber/internal/logging"
@@ -12,11 +12,11 @@ import (
1212
1313 // TagHandlers handles tag-related messages from the homepage.
1414 type TagHandlers struct {
15- favoritesUC *usecase.ManageFavoritesUseCase
15+ favoritesUC port.HomepageFavorites
1616 }
1717
1818 // NewTagHandlers creates a new TagHandlers instance.
19-func NewTagHandlers(favoritesUC *usecase.ManageFavoritesUseCase) *TagHandlers {
19+func NewTagHandlers(favoritesUC port.HomepageFavorites) *TagHandlers {
2020 return &TagHandlers{favoritesUC: favoritesUC}
2121 }
2222
@@ -4,7 +4,7 @@ import (
4 "context"4 "context"
5 "encoding/json"5 "encoding/json"
6 6
7- "github.com/bnema/dumber/internal/application/usecase"7+ "github.com/bnema/dumber/internal/application/port"
8 "github.com/bnema/dumber/internal/domain/entity"8 "github.com/bnema/dumber/internal/domain/entity"
9 "github.com/bnema/dumber/internal/infrastructure/webkit"9 "github.com/bnema/dumber/internal/infrastructure/webkit"
10 "github.com/bnema/dumber/internal/logging"10 "github.com/bnema/dumber/internal/logging"
@@ -12,11 +12,11 @@ import (
12 12
13 // TagHandlers handles tag-related messages from the homepage.13 // TagHandlers handles tag-related messages from the homepage.
14 type TagHandlers struct {14 type TagHandlers struct {
15- favoritesUC *usecase.ManageFavoritesUseCase15+ favoritesUC port.HomepageFavorites
16 }16 }
17 17
18 // NewTagHandlers creates a new TagHandlers instance.18 // NewTagHandlers creates a new TagHandlers instance.
19-func NewTagHandlers(favoritesUC *usecase.ManageFavoritesUseCase) *TagHandlers {19+func NewTagHandlers(favoritesUC port.HomepageFavorites) *TagHandlers {
20 return &TagHandlers{favoritesUC: favoritesUC}20 return &TagHandlers{favoritesUC: favoritesUC}
21 }21 }
22 22
M internal/infrastructure/webkit/handlers/keybindings.go +8 -9
@@ -6,25 +6,24 @@ import (
66 "fmt"
77
88 "github.com/bnema/dumber/internal/application/port"
9- "github.com/bnema/dumber/internal/application/usecase"
109 "github.com/bnema/dumber/internal/infrastructure/webkit"
1110 "github.com/bnema/dumber/internal/logging"
1211 )
1312
1413 // KeybindingsHandler handles keybinding-related messages.
1514 type KeybindingsHandler struct {
16- getUC *usecase.GetKeybindingsUseCase
17- setUC *usecase.SetKeybindingUseCase
18- resetUC *usecase.ResetKeybindingUseCase
19- resetAllUC *usecase.ResetAllKeybindingsUseCase
15+ getUC port.KeybindingsGetter
16+ setUC port.KeybindingSetter
17+ resetUC port.KeybindingResetter
18+ resetAllUC port.AllKeybindingsResetter
2019 }
2120
2221 // NewKeybindingsHandler creates a new KeybindingsHandler.
2322 func NewKeybindingsHandler(
24- getUC *usecase.GetKeybindingsUseCase,
25- setUC *usecase.SetKeybindingUseCase,
26- resetUC *usecase.ResetKeybindingUseCase,
27- resetAllUC *usecase.ResetAllKeybindingsUseCase,
23+ getUC port.KeybindingsGetter,
24+ setUC port.KeybindingSetter,
25+ resetUC port.KeybindingResetter,
26+ resetAllUC port.AllKeybindingsResetter,
2827 ) *KeybindingsHandler {
2928 return &KeybindingsHandler{
3029 getUC: getUC,
@@ -6,25 +6,24 @@ import (
6 "fmt"6 "fmt"
7 7
8 "github.com/bnema/dumber/internal/application/port"8 "github.com/bnema/dumber/internal/application/port"
9- "github.com/bnema/dumber/internal/application/usecase"
10 "github.com/bnema/dumber/internal/infrastructure/webkit"9 "github.com/bnema/dumber/internal/infrastructure/webkit"
11 "github.com/bnema/dumber/internal/logging"10 "github.com/bnema/dumber/internal/logging"
12 )11 )
13 12
14 // KeybindingsHandler handles keybinding-related messages.13 // KeybindingsHandler handles keybinding-related messages.
15 type KeybindingsHandler struct {14 type KeybindingsHandler struct {
16- getUC *usecase.GetKeybindingsUseCase15+ getUC port.KeybindingsGetter
17- setUC *usecase.SetKeybindingUseCase16+ setUC port.KeybindingSetter
18- resetUC *usecase.ResetKeybindingUseCase17+ resetUC port.KeybindingResetter
19- resetAllUC *usecase.ResetAllKeybindingsUseCase18+ resetAllUC port.AllKeybindingsResetter
20 }19 }
21 20
22 // NewKeybindingsHandler creates a new KeybindingsHandler.21 // NewKeybindingsHandler creates a new KeybindingsHandler.
23 func NewKeybindingsHandler(22 func NewKeybindingsHandler(
24- getUC *usecase.GetKeybindingsUseCase,23+ getUC port.KeybindingsGetter,
25- setUC *usecase.SetKeybindingUseCase,24+ setUC port.KeybindingSetter,
26- resetUC *usecase.ResetKeybindingUseCase,25+ resetUC port.KeybindingResetter,
27- resetAllUC *usecase.ResetAllKeybindingsUseCase,26+ resetAllUC port.AllKeybindingsResetter,
28 ) *KeybindingsHandler {27 ) *KeybindingsHandler {
29 return &KeybindingsHandler{28 return &KeybindingsHandler{
30 getUC: getUC,29 getUC: getUC,
M internal/infrastructure/webkit/handlers/register.go +16 -41
@@ -7,26 +7,24 @@ import (
77 "unicode/utf8"
88
99 "github.com/bnema/dumber/internal/application/port"
10- "github.com/bnema/dumber/internal/application/usecase"
11- "github.com/bnema/dumber/internal/infrastructure/config"
1210 "github.com/bnema/dumber/internal/infrastructure/webkit"
1311 "github.com/bnema/dumber/internal/infrastructure/webkit/handlers/homepage"
1412 )
1513
1614 // AccentKeyHandler is implemented by the InsertAccentUseCase to receive
1715 // key press/release events forwarded from WebView JS via the message bridge.
18-type AccentKeyHandler interface {
19- OnKeyPressed(ctx context.Context, char rune, shiftHeld bool) bool
20- OnKeyReleased(ctx context.Context, char rune)
21-}
16+// Deprecated: use port.AccentKeyHandler instead. This alias is kept for compatibility.
17+type AccentKeyHandler = port.AccentKeyHandler
2218
2319 // Config holds all dependencies for message handlers.
2420 type Config struct {
25- HistoryUC *usecase.SearchHistoryUseCase
26- FavoritesUC *usecase.ManageFavoritesUseCase
27- Clipboard port.Clipboard
28- ConfigGetter func() *config.Config
29- OnClipboardCopied func(textLen int) // Called when auto-copy completes (for toast notification)
21+ HistoryUC port.HomepageHistory
22+ FavoritesUC port.HomepageFavorites
23+ Clipboard port.Clipboard
24+ AutoCopyConfig port.AutoCopyConfig
25+ SaveConfig func(context.Context, port.WebUIConfig) error // Pre-built by bootstrap (usecase.SaveWebUIConfigUseCase.Execute)
26+ KeybindingsHandler *KeybindingsHandler // Pre-built by bootstrap
27+ OnClipboardCopied func(textLen int) // Called when auto-copy completes (for toast notification)
3028 }
3129
3230 // RegisterAll registers all message handlers with the router.
@@ -41,32 +39,26 @@ func RegisterAll(ctx context.Context, router *webkit.MessageRouter, cfg Config)
4139 }
4240 }
4341
44- // Configuration handlers (always available)
45- if err := RegisterConfigHandlers(ctx, router); err != nil {
42+ // Configuration handlers
43+ if err := RegisterConfigHandlers(ctx, router, cfg.SaveConfig); err != nil {
4644 return err
4745 }
4846
4947 // Keybindings handlers (always available)
50- keybindingsHandler, err := createKeybindingsHandler()
51- if err != nil {
52- return fmt.Errorf("failed to create keybindings handler: %w", err)
48+ if cfg.KeybindingsHandler == nil {
49+ return fmt.Errorf("KeybindingsHandler is required")
5350 }
54- if err := RegisterKeybindingsHandlers(ctx, router, keybindingsHandler); err != nil {
51+ if err := RegisterKeybindingsHandlers(ctx, router, cfg.KeybindingsHandler); err != nil {
5552 return err
5653 }
5754
5855 // Clipboard handlers (for auto-copy on selection feature)
59- if cfg.Clipboard != nil && cfg.ConfigGetter != nil {
60- if err := RegisterClipboardHandlers(ctx, router, cfg.Clipboard, cfg.ConfigGetter, cfg.OnClipboardCopied); err != nil {
56+ if cfg.Clipboard != nil && cfg.AutoCopyConfig != nil {
57+ if err := RegisterClipboardHandlers(ctx, router, cfg.Clipboard, cfg.AutoCopyConfig, cfg.OnClipboardCopied); err != nil {
6158 return err
6259 }
6360 }
6461
65- // Future handler groups go here:
66- // - settings handlers
67- // - downloads handlers
68- // - etc.
69-
7062 return nil
7163 }
7264
@@ -117,20 +109,3 @@ func RegisterAccentHandlers(_ context.Context, router *webkit.MessageRouter, han
117109
118110 return nil
119111 }
120-
121-// createKeybindingsHandler wires the gateway and use cases for keybindings.
122-func createKeybindingsHandler() (*KeybindingsHandler, error) {
123- mgr := config.GetManager()
124- if mgr == nil {
125- return nil, fmt.Errorf("config manager not initialized")
126- }
127-
128- gateway := config.NewKeybindingsGateway(mgr)
129-
130- return NewKeybindingsHandler(
131- usecase.NewGetKeybindingsUseCase(gateway),
132- usecase.NewSetKeybindingUseCase(gateway, gateway),
133- usecase.NewResetKeybindingUseCase(gateway),
134- usecase.NewResetAllKeybindingsUseCase(gateway),
135- ), nil
136-}
@@ -7,26 +7,24 @@ import (
7 "unicode/utf8"7 "unicode/utf8"
8 8
9 "github.com/bnema/dumber/internal/application/port"9 "github.com/bnema/dumber/internal/application/port"
10- "github.com/bnema/dumber/internal/application/usecase"
11- "github.com/bnema/dumber/internal/infrastructure/config"
12 "github.com/bnema/dumber/internal/infrastructure/webkit"10 "github.com/bnema/dumber/internal/infrastructure/webkit"
13 "github.com/bnema/dumber/internal/infrastructure/webkit/handlers/homepage"11 "github.com/bnema/dumber/internal/infrastructure/webkit/handlers/homepage"
14 )12 )
15 13
16 // AccentKeyHandler is implemented by the InsertAccentUseCase to receive14 // AccentKeyHandler is implemented by the InsertAccentUseCase to receive
17 // key press/release events forwarded from WebView JS via the message bridge.15 // key press/release events forwarded from WebView JS via the message bridge.
18-type AccentKeyHandler interface {16+// Deprecated: use port.AccentKeyHandler instead. This alias is kept for compatibility.
19- OnKeyPressed(ctx context.Context, char rune, shiftHeld bool) bool17+type AccentKeyHandler = port.AccentKeyHandler
20- OnKeyReleased(ctx context.Context, char rune)
21-}
22 18
23 // Config holds all dependencies for message handlers.19 // Config holds all dependencies for message handlers.
24 type Config struct {20 type Config struct {
25- HistoryUC *usecase.SearchHistoryUseCase21+ HistoryUC port.HomepageHistory
26- FavoritesUC *usecase.ManageFavoritesUseCase22+ FavoritesUC port.HomepageFavorites
27- Clipboard port.Clipboard23+ Clipboard port.Clipboard
28- ConfigGetter func() *config.Config24+ AutoCopyConfig port.AutoCopyConfig
29- OnClipboardCopied func(textLen int) // Called when auto-copy completes (for toast notification)25+ SaveConfig func(context.Context, port.WebUIConfig) error // Pre-built by bootstrap (usecase.SaveWebUIConfigUseCase.Execute)
26+ KeybindingsHandler *KeybindingsHandler // Pre-built by bootstrap
27+ OnClipboardCopied func(textLen int) // Called when auto-copy completes (for toast notification)
30 }28 }
31 29
32 // RegisterAll registers all message handlers with the router.30 // RegisterAll registers all message handlers with the router.
@@ -41,32 +39,26 @@ func RegisterAll(ctx context.Context, router *webkit.MessageRouter, cfg Config)
41 }39 }
42 }40 }
43 41
44- // Configuration handlers (always available)42+ // Configuration handlers
45- if err := RegisterConfigHandlers(ctx, router); err != nil {43+ if err := RegisterConfigHandlers(ctx, router, cfg.SaveConfig); err != nil {
46 return err44 return err
47 }45 }
48 46
49 // Keybindings handlers (always available)47 // Keybindings handlers (always available)
50- keybindingsHandler, err := createKeybindingsHandler()48+ if cfg.KeybindingsHandler == nil {
51- if err != nil {49+ return fmt.Errorf("KeybindingsHandler is required")
52- return fmt.Errorf("failed to create keybindings handler: %w", err)
53 }50 }
54- if err := RegisterKeybindingsHandlers(ctx, router, keybindingsHandler); err != nil {51+ if err := RegisterKeybindingsHandlers(ctx, router, cfg.KeybindingsHandler); err != nil {
55 return err52 return err
56 }53 }
57 54
58 // Clipboard handlers (for auto-copy on selection feature)55 // Clipboard handlers (for auto-copy on selection feature)
59- if cfg.Clipboard != nil && cfg.ConfigGetter != nil {56+ if cfg.Clipboard != nil && cfg.AutoCopyConfig != nil {
60- if err := RegisterClipboardHandlers(ctx, router, cfg.Clipboard, cfg.ConfigGetter, cfg.OnClipboardCopied); err != nil {57+ if err := RegisterClipboardHandlers(ctx, router, cfg.Clipboard, cfg.AutoCopyConfig, cfg.OnClipboardCopied); err != nil {
61 return err58 return err
62 }59 }
63 }60 }
64 61
65- // Future handler groups go here:
66- // - settings handlers
67- // - downloads handlers
68- // - etc.
69-
70 return nil62 return nil
71 }63 }
72 64
@@ -117,20 +109,3 @@ func RegisterAccentHandlers(_ context.Context, router *webkit.MessageRouter, han
117 109
118 return nil110 return nil
119 }111 }
120-
121-// createKeybindingsHandler wires the gateway and use cases for keybindings.
122-func createKeybindingsHandler() (*KeybindingsHandler, error) {
123- mgr := config.GetManager()
124- if mgr == nil {
125- return nil, fmt.Errorf("config manager not initialized")
126- }
127-
128- gateway := config.NewKeybindingsGateway(mgr)
129-
130- return NewKeybindingsHandler(
131- usecase.NewGetKeybindingsUseCase(gateway),
132- usecase.NewSetKeybindingUseCase(gateway, gateway),
133- usecase.NewResetKeybindingUseCase(gateway),
134- usecase.NewResetAllKeybindingsUseCase(gateway),
135- ), nil
136-}
M internal/ui/adapter/favicon.go +3 -3
@@ -17,7 +17,7 @@ import (
1717 // FaviconAdapter bridges the domain FaviconService to GTK by providing
1818 // gdk.Texture conversion and WebKit FaviconDatabase integration.
1919 type FaviconAdapter struct {
20- service *favicon.Service
20+ service port.FaviconService
2121 faviconDB port.FaviconDatabase
2222 textureCache map[string]*gdk.Texture
2323 mu sync.RWMutex
@@ -29,7 +29,7 @@ type warningLogFunc func(log *zerolog.Logger, err error)
2929
3030 // NewFaviconAdapter creates a new FaviconAdapter.
3131 // The faviconDB can be nil if a favicon database is not available.
32-func NewFaviconAdapter(service *favicon.Service, faviconDB port.FaviconDatabase) *FaviconAdapter {
32+func NewFaviconAdapter(service port.FaviconService, faviconDB port.FaviconDatabase) *FaviconAdapter {
3333 return &FaviconAdapter{
3434 service: service,
3535 faviconDB: faviconDB,
@@ -272,7 +272,7 @@ func (a *FaviconAdapter) PreloadFromCache(pageURL string) *gdk.Texture {
272272
273273 // Service returns the underlying favicon service.
274274 // Used by CLI components that need disk paths.
275-func (a *FaviconAdapter) Service() *favicon.Service {
275+func (a *FaviconAdapter) Service() port.FaviconService {
276276 return a.service
277277 }
278278
@@ -17,7 +17,7 @@ import (
17 // FaviconAdapter bridges the domain FaviconService to GTK by providing17 // FaviconAdapter bridges the domain FaviconService to GTK by providing
18 // gdk.Texture conversion and WebKit FaviconDatabase integration.18 // gdk.Texture conversion and WebKit FaviconDatabase integration.
19 type FaviconAdapter struct {19 type FaviconAdapter struct {
20- service *favicon.Service20+ service port.FaviconService
21 faviconDB port.FaviconDatabase21 faviconDB port.FaviconDatabase
22 textureCache map[string]*gdk.Texture22 textureCache map[string]*gdk.Texture
23 mu sync.RWMutex23 mu sync.RWMutex
@@ -29,7 +29,7 @@ type warningLogFunc func(log *zerolog.Logger, err error)
29 29
30 // NewFaviconAdapter creates a new FaviconAdapter.30 // NewFaviconAdapter creates a new FaviconAdapter.
31 // The faviconDB can be nil if a favicon database is not available.31 // The faviconDB can be nil if a favicon database is not available.
32-func NewFaviconAdapter(service *favicon.Service, faviconDB port.FaviconDatabase) *FaviconAdapter {32+func NewFaviconAdapter(service port.FaviconService, faviconDB port.FaviconDatabase) *FaviconAdapter {
33 return &FaviconAdapter{33 return &FaviconAdapter{
34 service: service,34 service: service,
35 faviconDB: faviconDB,35 faviconDB: faviconDB,
@@ -272,7 +272,7 @@ func (a *FaviconAdapter) PreloadFromCache(pageURL string) *gdk.Texture {
272 272
273 // Service returns the underlying favicon service.273 // Service returns the underlying favicon service.
274 // Used by CLI components that need disk paths.274 // Used by CLI components that need disk paths.
275-func (a *FaviconAdapter) Service() *favicon.Service {275+func (a *FaviconAdapter) Service() port.FaviconService {
276 return a.service276 return a.service
277 }277 }
278 278
M internal/ui/app.go +5 -6
@@ -17,7 +17,6 @@ import (
1717 "github.com/bnema/dumber/internal/infrastructure/config"
1818 "github.com/bnema/dumber/internal/infrastructure/desktop"
1919 "github.com/bnema/dumber/internal/infrastructure/filesystem"
20- "github.com/bnema/dumber/internal/infrastructure/filtering"
2120 "github.com/bnema/dumber/internal/infrastructure/snapshot"
2221 "github.com/bnema/dumber/internal/infrastructure/textinput"
2322
@@ -3116,7 +3115,7 @@ func (a *App) initFilteringAsync(ctx context.Context) {
31163115 return
31173116 }
31183117
3119- a.deps.FilterManager.SetStatusCallback(func(status filtering.FilterStatus) {
3118+ a.deps.FilterManager.SetStatusCallback(func(status port.FilterStatus) {
31203119 statusCopy := status // Capture for closure
31213120 cb := glib.SourceFunc(func(_ uintptr) bool {
31223121 a.showFilterStatus(ctx, statusCopy)
@@ -3128,15 +3127,15 @@ func (a *App) initFilteringAsync(ctx context.Context) {
31283127 }
31293128
31303129 // showFilterStatus displays toast notification for filter status.
3131-func (a *App) showFilterStatus(ctx context.Context, status filtering.FilterStatus) {
3130+func (a *App) showFilterStatus(ctx context.Context, status port.FilterStatus) {
31323131 log := logging.FromContext(ctx)
31333132
31343133 switch status.State {
3135- case filtering.StateLoading:
3134+ case port.FilterStateLoading:
31363135 if a.appToaster != nil {
31373136 a.appToaster.Show(ctx, status.Message, component.ToastInfo)
31383137 }
3139- case filtering.StateActive:
3138+ case port.FilterStateActive:
31403139 // Apply filters to existing webviews that were created before filters loaded
31413140 if a.contentCoord != nil && a.deps.FilterManager != nil {
31423141 a.contentCoord.ApplyFiltersToAll(ctx)
@@ -3145,7 +3144,7 @@ func (a *App) showFilterStatus(ctx context.Context, status filtering.FilterStatu
31453144 if a.appToaster != nil {
31463145 a.appToaster.Show(ctx, fmt.Sprintf("Ad blocker ready (%s)", status.Version), component.ToastInfo)
31473146 }
3148- case filtering.StateError:
3147+ case port.FilterStateError:
31493148 if a.appToaster != nil {
31503149 a.appToaster.Show(ctx, "Filter load failed: "+status.Message, component.ToastError)
31513150 }
@@ -17,7 +17,6 @@ import (
17 "github.com/bnema/dumber/internal/infrastructure/config"17 "github.com/bnema/dumber/internal/infrastructure/config"
18 "github.com/bnema/dumber/internal/infrastructure/desktop"18 "github.com/bnema/dumber/internal/infrastructure/desktop"
19 "github.com/bnema/dumber/internal/infrastructure/filesystem"19 "github.com/bnema/dumber/internal/infrastructure/filesystem"
20- "github.com/bnema/dumber/internal/infrastructure/filtering"
21 "github.com/bnema/dumber/internal/infrastructure/snapshot"20 "github.com/bnema/dumber/internal/infrastructure/snapshot"
22 "github.com/bnema/dumber/internal/infrastructure/textinput"21 "github.com/bnema/dumber/internal/infrastructure/textinput"
23 22
@@ -3116,7 +3115,7 @@ func (a *App) initFilteringAsync(ctx context.Context) {
3116 return3115 return
3117 }3116 }
3118 3117
3119- a.deps.FilterManager.SetStatusCallback(func(status filtering.FilterStatus) {3118+ a.deps.FilterManager.SetStatusCallback(func(status port.FilterStatus) {
3120 statusCopy := status // Capture for closure3119 statusCopy := status // Capture for closure
3121 cb := glib.SourceFunc(func(_ uintptr) bool {3120 cb := glib.SourceFunc(func(_ uintptr) bool {
3122 a.showFilterStatus(ctx, statusCopy)3121 a.showFilterStatus(ctx, statusCopy)
@@ -3128,15 +3127,15 @@ func (a *App) initFilteringAsync(ctx context.Context) {
3128 }3127 }
3129 3128
3130 // showFilterStatus displays toast notification for filter status.3129 // showFilterStatus displays toast notification for filter status.
3131-func (a *App) showFilterStatus(ctx context.Context, status filtering.FilterStatus) {3130+func (a *App) showFilterStatus(ctx context.Context, status port.FilterStatus) {
3132 log := logging.FromContext(ctx)3131 log := logging.FromContext(ctx)
3133 3132
3134 switch status.State {3133 switch status.State {
3135- case filtering.StateLoading:3134+ case port.FilterStateLoading:
3136 if a.appToaster != nil {3135 if a.appToaster != nil {
3137 a.appToaster.Show(ctx, status.Message, component.ToastInfo)3136 a.appToaster.Show(ctx, status.Message, component.ToastInfo)
3138 }3137 }
3139- case filtering.StateActive:3138+ case port.FilterStateActive:
3140 // Apply filters to existing webviews that were created before filters loaded3139 // Apply filters to existing webviews that were created before filters loaded
3141 if a.contentCoord != nil && a.deps.FilterManager != nil {3140 if a.contentCoord != nil && a.deps.FilterManager != nil {
3142 a.contentCoord.ApplyFiltersToAll(ctx)3141 a.contentCoord.ApplyFiltersToAll(ctx)
@@ -3145,7 +3144,7 @@ func (a *App) showFilterStatus(ctx context.Context, status filtering.FilterStatu
3145 if a.appToaster != nil {3144 if a.appToaster != nil {
3146 a.appToaster.Show(ctx, fmt.Sprintf("Ad blocker ready (%s)", status.Version), component.ToastInfo)3145 a.appToaster.Show(ctx, fmt.Sprintf("Ad blocker ready (%s)", status.Version), component.ToastInfo)
3147 }3146 }
3148- case filtering.StateError:3147+ case port.FilterStateError:
3149 if a.appToaster != nil {3148 if a.appToaster != nil {
3150 a.appToaster.Show(ctx, "Filter load failed: "+status.Message, component.ToastError)3149 a.appToaster.Show(ctx, "Filter load failed: "+status.Message, component.ToastError)
3151 }3150 }
M internal/ui/deps.go +6 -9
@@ -8,10 +8,7 @@ import (
88 "github.com/bnema/dumber/internal/application/usecase"
99 "github.com/bnema/dumber/internal/domain/entity"
1010 "github.com/bnema/dumber/internal/domain/repository"
11- "github.com/bnema/dumber/internal/infrastructure/colorscheme"
1211 "github.com/bnema/dumber/internal/infrastructure/config"
13- "github.com/bnema/dumber/internal/infrastructure/favicon"
14- "github.com/bnema/dumber/internal/infrastructure/filtering"
1512 "github.com/bnema/dumber/internal/ui/theme"
1613 )
1714
@@ -20,9 +17,9 @@ import (
2017 type Dependencies struct {
2118 // Core context and configuration
2219 Ctx context.Context
23- Config *config.Config
24- InitialURL string // URL to open on startup (optional)
25- RestoreSessionID string // Session ID to restore on startup (optional)
20+ Config *config.Config // TODO: replace with port interface in a later milestone
21+ InitialURL string // URL to open on startup (optional)
22+ RestoreSessionID string // Session ID to restore on startup (optional)
2623 StartupCrashReports []string
2724 OnFirstWebViewShown func(context.Context)
2825 OnSessionPersisted func() // Called by main after session is persisted to DB
@@ -31,7 +28,7 @@ type Dependencies struct {
3128 // Theme and color scheme management
3229 Theme *theme.Manager
3330 ColorResolver port.ColorSchemeResolver
34- AdwaitaDetector *colorscheme.AdwaitaDetector
31+ AdwaitaDetector port.ToolkitAvailabilityNotifier
3532
3633 // XDG paths
3734 XDG port.XDGPaths
@@ -58,8 +55,8 @@ type Dependencies struct {
5855
5956 // Infrastructure Adapters
6057 Clipboard port.Clipboard
61- FaviconService *favicon.Service
62- FilterManager *filtering.Manager
58+ FaviconService port.FaviconService
59+ FilterManager port.FilterManager
6360 IdleInhibitor port.IdleInhibitor
6461
6562 // Accent picker for dead keys support
@@ -8,10 +8,7 @@ import (
8 "github.com/bnema/dumber/internal/application/usecase"8 "github.com/bnema/dumber/internal/application/usecase"
9 "github.com/bnema/dumber/internal/domain/entity"9 "github.com/bnema/dumber/internal/domain/entity"
10 "github.com/bnema/dumber/internal/domain/repository"10 "github.com/bnema/dumber/internal/domain/repository"
11- "github.com/bnema/dumber/internal/infrastructure/colorscheme"
12 "github.com/bnema/dumber/internal/infrastructure/config"11 "github.com/bnema/dumber/internal/infrastructure/config"
13- "github.com/bnema/dumber/internal/infrastructure/favicon"
14- "github.com/bnema/dumber/internal/infrastructure/filtering"
15 "github.com/bnema/dumber/internal/ui/theme"12 "github.com/bnema/dumber/internal/ui/theme"
16 )13 )
17 14
@@ -20,9 +17,9 @@ import (
20 type Dependencies struct {17 type Dependencies struct {
21 // Core context and configuration18 // Core context and configuration
22 Ctx context.Context19 Ctx context.Context
23- Config *config.Config20+ Config *config.Config // TODO: replace with port interface in a later milestone
24- InitialURL string // URL to open on startup (optional)21+ InitialURL string // URL to open on startup (optional)
25- RestoreSessionID string // Session ID to restore on startup (optional)22+ RestoreSessionID string // Session ID to restore on startup (optional)
26 StartupCrashReports []string23 StartupCrashReports []string
27 OnFirstWebViewShown func(context.Context)24 OnFirstWebViewShown func(context.Context)
28 OnSessionPersisted func() // Called by main after session is persisted to DB25 OnSessionPersisted func() // Called by main after session is persisted to DB
@@ -31,7 +28,7 @@ type Dependencies struct {
31 // Theme and color scheme management28 // Theme and color scheme management
32 Theme *theme.Manager29 Theme *theme.Manager
33 ColorResolver port.ColorSchemeResolver30 ColorResolver port.ColorSchemeResolver
34- AdwaitaDetector *colorscheme.AdwaitaDetector31+ AdwaitaDetector port.ToolkitAvailabilityNotifier
35 32
36 // XDG paths33 // XDG paths
37 XDG port.XDGPaths34 XDG port.XDGPaths
@@ -58,8 +55,8 @@ type Dependencies struct {
58 55
59 // Infrastructure Adapters56 // Infrastructure Adapters
60 Clipboard port.Clipboard57 Clipboard port.Clipboard
61- FaviconService *favicon.Service58+ FaviconService port.FaviconService
62- FilterManager *filtering.Manager59+ FilterManager port.FilterManager
63 IdleInhibitor port.IdleInhibitor60 IdleInhibitor port.IdleInhibitor
64 61
65 // Accent picker for dead keys support62 // Accent picker for dead keys support

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close