fix(cef): extract handler deps to port layer, fix KeybindingsGetter crash
CEF engine crashed with "KeybindingsGetter is required" when .dev/dumber/ was deleted because handler deps (keybindings, saveConfig) were only injected via webkit's callback pattern, which CEF didn't replicate. Move HandlerDeps struct to port layer and build deps in a single bootstrap helper. Both engines now call handlers.RegisterAll directly — no callbacks needed. Removes the webkit callback pattern that caused the asymmetry.
- commit
- 6b1adf41a3e32d5cbd9e3413089007ccb309f077
- author
- brice · · 2026-03-22 04:43 UTC
- committer
- brice · · 2026-03-28 04:38 UTC
- parent
- f36f1f3
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
8 files · +75 -93
M cmd/dumber/main.go +12 -3
| @@ -223,11 +223,14 @@ func buildAndConfigureApp( | |||
| 223 | 223 | idleInhibitor port.IdleInhibitor, | |
| 224 | 224 | browserSession *bootstrap.BrowserSession, | |
| 225 | 225 | ) (*ui.App, error) { | |
| 226 | - | uiDeps := buildUIDependencies( | |
| 226 | + | uiDeps, err := buildUIDependencies( | |
| 227 | 227 | ctx, cfg, initResult.ThemeManager, | |
| 228 | 228 | initResult.ColorResolver, initResult.AdwaitaDetector, | |
| 229 | 229 | engine, repos, useCases, idleInhibitor, browserSession.Session.ID, browserSession.CrashReports(), | |
| 230 | 230 | ) | |
| 231 | + | if err != nil { | |
| 232 | + | return nil, err | |
| 233 | + | } | |
| 231 | 234 | configureDeferredInit(uiDeps, cfg, browserSession) | |
| 232 | 235 | return ui.New(uiDeps) | |
| 233 | 236 | } | |
| @@ -548,12 +551,17 @@ func buildUIDependencies( | |||
| 548 | 551 | idleInhibitor port.IdleInhibitor, | |
| 549 | 552 | currentSessionID entity.SessionID, | |
| 550 | 553 | startupCrashReports []string, | |
| 551 | - | ) *ui.Dependencies { | |
| 554 | + | ) (*ui.Dependencies, error) { | |
| 552 | 555 | var filterManager port.FilterManager | |
| 553 | 556 | if fmp, ok := engine.(port.FilterManagerProvider); ok { | |
| 554 | 557 | filterManager = fmp.InternalFilterManager() | |
| 555 | 558 | } | |
| 556 | 559 | ||
| 560 | + | handlerDeps, err := bootstrap.BuildHandlerDeps(ctx) | |
| 561 | + | if err != nil { | |
| 562 | + | return nil, fmt.Errorf("failed to build handler deps: %w", err) | |
| 563 | + | } | |
| 564 | + | ||
| 557 | 565 | focusProvider := textinput.NewFocusProvider() | |
| 558 | 566 | ||
| 559 | 567 | uiDeps := &ui.Dependencies{ | |
| @@ -620,7 +628,8 @@ func buildUIDependencies( | |||
| 620 | 628 | }, | |
| 621 | 629 | LaunchExternalURL: desktop.LaunchExternalURL, | |
| 622 | 630 | ConfigMigrator: config.NewMigrator(), | |
| 631 | + | HandlerDeps: *handlerDeps, | |
| 623 | 632 | } | |
| 624 | 633 | ||
| 625 | - | return uiDeps | |
| 634 | + | return uiDeps, nil | |
| 626 | 635 | } | |
| @@ -223,11 +223,14 @@ func buildAndConfigureApp( | |||||
| 223 | idleInhibitor port.IdleInhibitor, | 223 | idleInhibitor port.IdleInhibitor, | ||
| 224 | browserSession *bootstrap.BrowserSession, | 224 | browserSession *bootstrap.BrowserSession, | ||
| 225 | ) (*ui.App, error) { | 225 | ) (*ui.App, error) { | ||
| 226 | - | uiDeps := buildUIDependencies( | 226 | + | uiDeps, err := buildUIDependencies( |
| 227 | ctx, cfg, initResult.ThemeManager, | 227 | ctx, cfg, initResult.ThemeManager, | ||
| 228 | initResult.ColorResolver, initResult.AdwaitaDetector, | 228 | initResult.ColorResolver, initResult.AdwaitaDetector, | ||
| 229 | engine, repos, useCases, idleInhibitor, browserSession.Session.ID, browserSession.CrashReports(), | 229 | engine, repos, useCases, idleInhibitor, browserSession.Session.ID, browserSession.CrashReports(), | ||
| 230 | ) | 230 | ) | ||
| 231 | + | if err != nil { | |||
| 232 | + | return nil, err | |||
| 233 | + | } | |||
| 231 | configureDeferredInit(uiDeps, cfg, browserSession) | 234 | configureDeferredInit(uiDeps, cfg, browserSession) | ||
| 232 | return ui.New(uiDeps) | 235 | return ui.New(uiDeps) | ||
| 233 | } | 236 | } | ||
| @@ -548,12 +551,17 @@ func buildUIDependencies( | |||||
| 548 | idleInhibitor port.IdleInhibitor, | 551 | idleInhibitor port.IdleInhibitor, | ||
| 549 | currentSessionID entity.SessionID, | 552 | currentSessionID entity.SessionID, | ||
| 550 | startupCrashReports []string, | 553 | startupCrashReports []string, | ||
| 551 | - | ) *ui.Dependencies { | 554 | + | ) (*ui.Dependencies, error) { |
| 552 | var filterManager port.FilterManager | 555 | var filterManager port.FilterManager | ||
| 553 | if fmp, ok := engine.(port.FilterManagerProvider); ok { | 556 | if fmp, ok := engine.(port.FilterManagerProvider); ok { | ||
| 554 | filterManager = fmp.InternalFilterManager() | 557 | filterManager = fmp.InternalFilterManager() | ||
| 555 | } | 558 | } | ||
| 556 | 559 | ||||
| 560 | + | handlerDeps, err := bootstrap.BuildHandlerDeps(ctx) | |||
| 561 | + | if err != nil { | |||
| 562 | + | return nil, fmt.Errorf("failed to build handler deps: %w", err) | |||
| 563 | + | } | |||
| 564 | + | ||||
| 557 | focusProvider := textinput.NewFocusProvider() | 565 | focusProvider := textinput.NewFocusProvider() | ||
| 558 | 566 | ||||
| 559 | uiDeps := &ui.Dependencies{ | 567 | uiDeps := &ui.Dependencies{ | ||
| @@ -620,7 +628,8 @@ func buildUIDependencies( | |||||
| 620 | }, | 628 | }, | ||
| 621 | LaunchExternalURL: desktop.LaunchExternalURL, | 629 | LaunchExternalURL: desktop.LaunchExternalURL, | ||
| 622 | ConfigMigrator: config.NewMigrator(), | 630 | ConfigMigrator: config.NewMigrator(), | ||
| 631 | + | HandlerDeps: *handlerDeps, | |||
| 623 | } | 632 | } | ||
| 624 | 633 | ||||
| 625 | - | return uiDeps | 634 | + | return uiDeps, nil |
| 626 | } | 635 | } | ||
M internal/application/port/handler_deps.go +11 -0
| @@ -30,3 +30,14 @@ type DownloadPrepareOutput struct { | |||
| 30 | 30 | type AutoCopyConfig interface { | |
| 31 | 31 | IsAutoCopyEnabled() bool | |
| 32 | 32 | } | |
| 33 | + | ||
| 34 | + | // HandlerDeps holds handler dependencies built at bootstrap time. | |
| 35 | + | // Lives in the port layer so both bootstrap and ui can depend on it | |
| 36 | + | // without creating a reverse dependency. | |
| 37 | + | type HandlerDeps struct { | |
| 38 | + | SaveConfig func(context.Context, WebUIConfig) error | |
| 39 | + | KeybindingsGetter KeybindingsGetter | |
| 40 | + | KeybindingSetter KeybindingSetter | |
| 41 | + | KeybindingResetter KeybindingResetter | |
| 42 | + | AllKeybindingsResetter AllKeybindingsResetter | |
| 43 | + | } | |
| @@ -30,3 +30,14 @@ type DownloadPrepareOutput struct { | |||||
| 30 | type AutoCopyConfig interface { | 30 | type AutoCopyConfig interface { | ||
| 31 | IsAutoCopyEnabled() bool | 31 | IsAutoCopyEnabled() bool | ||
| 32 | } | 32 | } | ||
| 33 | + | ||||
| 34 | + | // HandlerDeps holds handler dependencies built at bootstrap time. | |||
| 35 | + | // Lives in the port layer so both bootstrap and ui can depend on it | |||
| 36 | + | // without creating a reverse dependency. | |||
| 37 | + | type HandlerDeps struct { | |||
| 38 | + | SaveConfig func(context.Context, WebUIConfig) error | |||
| 39 | + | KeybindingsGetter KeybindingsGetter | |||
| 40 | + | KeybindingSetter KeybindingSetter | |||
| 41 | + | KeybindingResetter KeybindingResetter | |||
| 42 | + | AllKeybindingsResetter AllKeybindingsResetter | |||
| 43 | + | } | |||
M internal/bootstrap/engine.go +0 -63
| @@ -5,10 +5,8 @@ import ( | |||
| 5 | 5 | "fmt" | |
| 6 | 6 | ||
| 7 | 7 | "github.com/bnema/dumber/internal/application/port" | |
| 8 | - | "github.com/bnema/dumber/internal/application/usecase" | |
| 9 | 8 | "github.com/bnema/dumber/internal/infrastructure/cef" | |
| 10 | 9 | "github.com/bnema/dumber/internal/infrastructure/config" | |
| 11 | - | "github.com/bnema/dumber/internal/infrastructure/handlers" | |
| 12 | 10 | "github.com/bnema/dumber/internal/infrastructure/webkit" | |
| 13 | 11 | "github.com/bnema/dumber/internal/ui/theme" | |
| 14 | 12 | "github.com/rs/zerolog" | |
| @@ -38,33 +36,9 @@ func BuildEngine(input EngineInput) (port.Engine, error) { | |||
| 38 | 36 | } | |
| 39 | 37 | wkCfg := webkit.EngineConfigFromConfig(cfg.Engine.WebKit) | |
| 40 | 38 | ||
| 41 | - | // Pre-build keybinding use cases for handler registration. | |
| 42 | - | keybindingDeps, err := buildKeybindingDeps() | |
| 43 | - | if err != nil { | |
| 44 | - | return nil, fmt.Errorf("failed to build keybinding deps: %w", err) | |
| 45 | - | } | |
| 46 | - | ||
| 47 | - | // Pre-build config save function for handler registration. | |
| 48 | - | saveConfigFunc, err := buildSaveConfigFunc() | |
| 49 | - | if err != nil { | |
| 50 | - | return nil, fmt.Errorf("failed to build save config func: %w", err) | |
| 51 | - | } | |
| 52 | - | ||
| 53 | 39 | return webkit.NewEngine( | |
| 54 | 40 | input.Ctx, cfg, opts, wkCfg, | |
| 55 | 41 | input.ThemeManager, input.ColorResolver, input.Logger, | |
| 56 | - | func(ctx context.Context, router port.WebUIHandlerRouter, deps port.HandlerDependencies) error { | |
| 57 | - | // Inject pre-built dependencies that come from bootstrap, not from the engine. | |
| 58 | - | deps.SaveConfig = saveConfigFunc | |
| 59 | - | deps.KeybindingsGetter = keybindingDeps.KeybindingsGetter | |
| 60 | - | deps.KeybindingSetter = keybindingDeps.KeybindingSetter | |
| 61 | - | deps.KeybindingResetter = keybindingDeps.KeybindingResetter | |
| 62 | - | deps.AllKeybindingsResetter = keybindingDeps.AllKeybindingsResetter | |
| 63 | - | return handlers.RegisterAll(ctx, router, deps) | |
| 64 | - | }, | |
| 65 | - | func(ctx context.Context, router port.WebUIHandlerRouter, handler port.AccentKeyHandler) error { | |
| 66 | - | return handlers.RegisterAccentHandlers(ctx, router, handler) | |
| 67 | - | }, | |
| 68 | 42 | ) | |
| 69 | 43 | case "cef": | |
| 70 | 44 | cefCfg := cfg.Engine.CEF | |
| @@ -73,40 +47,3 @@ func BuildEngine(input EngineInput) (port.Engine, error) { | |||
| 73 | 47 | return nil, fmt.Errorf("unknown engine type: %q", engineType) | |
| 74 | 48 | } | |
| 75 | 49 | } | |
| 76 | - | ||
| 77 | - | // keybindingDeps holds keybinding use cases built at bootstrap time. | |
| 78 | - | type keybindingDeps struct { | |
| 79 | - | KeybindingsGetter port.KeybindingsGetter | |
| 80 | - | KeybindingSetter port.KeybindingSetter | |
| 81 | - | KeybindingResetter port.KeybindingResetter | |
| 82 | - | AllKeybindingsResetter port.AllKeybindingsResetter | |
| 83 | - | } | |
| 84 | - | ||
| 85 | - | // buildKeybindingDeps constructs keybinding use cases using the config manager. | |
| 86 | - | func buildKeybindingDeps() (*keybindingDeps, error) { | |
| 87 | - | mgr := config.GetManager() | |
| 88 | - | if mgr == nil { | |
| 89 | - | return nil, fmt.Errorf("config manager not initialized") | |
| 90 | - | } | |
| 91 | - | ||
| 92 | - | gateway := config.NewKeybindingsGateway(mgr) | |
| 93 | - | ||
| 94 | - | return &keybindingDeps{ | |
| 95 | - | KeybindingsGetter: usecase.NewGetKeybindingsUseCase(gateway), | |
| 96 | - | KeybindingSetter: usecase.NewSetKeybindingUseCase(gateway, gateway), | |
| 97 | - | KeybindingResetter: usecase.NewResetKeybindingUseCase(gateway), | |
| 98 | - | AllKeybindingsResetter: usecase.NewResetAllKeybindingsUseCase(gateway), | |
| 99 | - | }, nil | |
| 100 | - | } | |
| 101 | - | ||
| 102 | - | // buildSaveConfigFunc constructs the config save function using the config manager. | |
| 103 | - | // It returns an error immediately if the config manager is not initialized, consistent | |
| 104 | - | // with buildKeybindingsHandler. | |
| 105 | - | func buildSaveConfigFunc() (func(context.Context, port.WebUIConfig) error, error) { | |
| 106 | - | mgr := config.GetManager() | |
| 107 | - | if mgr == nil { | |
| 108 | - | return nil, fmt.Errorf("config manager not initialized") | |
| 109 | - | } | |
| 110 | - | uc := usecase.NewSaveWebUIConfigUseCase(config.NewWebUIConfigGateway(mgr)) | |
| 111 | - | return uc.Execute, nil | |
| 112 | - | } | |
| @@ -5,10 +5,8 @@ import ( | |||||
| 5 | "fmt" | 5 | "fmt" | ||
| 6 | 6 | ||||
| 7 | "github.com/bnema/dumber/internal/application/port" | 7 | "github.com/bnema/dumber/internal/application/port" | ||
| 8 | - | "github.com/bnema/dumber/internal/application/usecase" | |||
| 9 | "github.com/bnema/dumber/internal/infrastructure/cef" | 8 | "github.com/bnema/dumber/internal/infrastructure/cef" | ||
| 10 | "github.com/bnema/dumber/internal/infrastructure/config" | 9 | "github.com/bnema/dumber/internal/infrastructure/config" | ||
| 11 | - | "github.com/bnema/dumber/internal/infrastructure/handlers" | |||
| 12 | "github.com/bnema/dumber/internal/infrastructure/webkit" | 10 | "github.com/bnema/dumber/internal/infrastructure/webkit" | ||
| 13 | "github.com/bnema/dumber/internal/ui/theme" | 11 | "github.com/bnema/dumber/internal/ui/theme" | ||
| 14 | "github.com/rs/zerolog" | 12 | "github.com/rs/zerolog" | ||
| @@ -38,33 +36,9 @@ func BuildEngine(input EngineInput) (port.Engine, error) { | |||||
| 38 | } | 36 | } | ||
| 39 | wkCfg := webkit.EngineConfigFromConfig(cfg.Engine.WebKit) | 37 | wkCfg := webkit.EngineConfigFromConfig(cfg.Engine.WebKit) | ||
| 40 | 38 | ||||
| 41 | - | // Pre-build keybinding use cases for handler registration. | |||
| 42 | - | keybindingDeps, err := buildKeybindingDeps() | |||
| 43 | - | if err != nil { | |||
| 44 | - | return nil, fmt.Errorf("failed to build keybinding deps: %w", err) | |||
| 45 | - | } | |||
| 46 | - | ||||
| 47 | - | // Pre-build config save function for handler registration. | |||
| 48 | - | saveConfigFunc, err := buildSaveConfigFunc() | |||
| 49 | - | if err != nil { | |||
| 50 | - | return nil, fmt.Errorf("failed to build save config func: %w", err) | |||
| 51 | - | } | |||
| 52 | - | ||||
| 53 | return webkit.NewEngine( | 39 | return webkit.NewEngine( | ||
| 54 | input.Ctx, cfg, opts, wkCfg, | 40 | input.Ctx, cfg, opts, wkCfg, | ||
| 55 | input.ThemeManager, input.ColorResolver, input.Logger, | 41 | input.ThemeManager, input.ColorResolver, input.Logger, | ||
| 56 | - | func(ctx context.Context, router port.WebUIHandlerRouter, deps port.HandlerDependencies) error { | |||
| 57 | - | // Inject pre-built dependencies that come from bootstrap, not from the engine. | |||
| 58 | - | deps.SaveConfig = saveConfigFunc | |||
| 59 | - | deps.KeybindingsGetter = keybindingDeps.KeybindingsGetter | |||
| 60 | - | deps.KeybindingSetter = keybindingDeps.KeybindingSetter | |||
| 61 | - | deps.KeybindingResetter = keybindingDeps.KeybindingResetter | |||
| 62 | - | deps.AllKeybindingsResetter = keybindingDeps.AllKeybindingsResetter | |||
| 63 | - | return handlers.RegisterAll(ctx, router, deps) | |||
| 64 | - | }, | |||
| 65 | - | func(ctx context.Context, router port.WebUIHandlerRouter, handler port.AccentKeyHandler) error { | |||
| 66 | - | return handlers.RegisterAccentHandlers(ctx, router, handler) | |||
| 67 | - | }, | |||
| 68 | ) | 42 | ) | ||
| 69 | case "cef": | 43 | case "cef": | ||
| 70 | cefCfg := cfg.Engine.CEF | 44 | cefCfg := cfg.Engine.CEF | ||
| @@ -73,40 +47,3 @@ func BuildEngine(input EngineInput) (port.Engine, error) { | |||||
| 73 | return nil, fmt.Errorf("unknown engine type: %q", engineType) | 47 | return nil, fmt.Errorf("unknown engine type: %q", engineType) | ||
| 74 | } | 48 | } | ||
| 75 | } | 49 | } | ||
| 76 | - | ||||
| 77 | - | // keybindingDeps holds keybinding use cases built at bootstrap time. | |||
| 78 | - | type keybindingDeps struct { | |||
| 79 | - | KeybindingsGetter port.KeybindingsGetter | |||
| 80 | - | KeybindingSetter port.KeybindingSetter | |||
| 81 | - | KeybindingResetter port.KeybindingResetter | |||
| 82 | - | AllKeybindingsResetter port.AllKeybindingsResetter | |||
| 83 | - | } | |||
| 84 | - | ||||
| 85 | - | // buildKeybindingDeps constructs keybinding use cases using the config manager. | |||
| 86 | - | func buildKeybindingDeps() (*keybindingDeps, error) { | |||
| 87 | - | mgr := config.GetManager() | |||
| 88 | - | if mgr == nil { | |||
| 89 | - | return nil, fmt.Errorf("config manager not initialized") | |||
| 90 | - | } | |||
| 91 | - | ||||
| 92 | - | gateway := config.NewKeybindingsGateway(mgr) | |||
| 93 | - | ||||
| 94 | - | return &keybindingDeps{ | |||
| 95 | - | KeybindingsGetter: usecase.NewGetKeybindingsUseCase(gateway), | |||
| 96 | - | KeybindingSetter: usecase.NewSetKeybindingUseCase(gateway, gateway), | |||
| 97 | - | KeybindingResetter: usecase.NewResetKeybindingUseCase(gateway), | |||
| 98 | - | AllKeybindingsResetter: usecase.NewResetAllKeybindingsUseCase(gateway), | |||
| 99 | - | }, nil | |||
| 100 | - | } | |||
| 101 | - | ||||
| 102 | - | // buildSaveConfigFunc constructs the config save function using the config manager. | |||
| 103 | - | // It returns an error immediately if the config manager is not initialized, consistent | |||
| 104 | - | // with buildKeybindingsHandler. | |||
| 105 | - | func buildSaveConfigFunc() (func(context.Context, port.WebUIConfig) error, error) { | |||
| 106 | - | mgr := config.GetManager() | |||
| 107 | - | if mgr == nil { | |||
| 108 | - | return nil, fmt.Errorf("config manager not initialized") | |||
| 109 | - | } | |||
| 110 | - | uc := usecase.NewSaveWebUIConfigUseCase(config.NewWebUIConfigGateway(mgr)) | |||
| 111 | - | return uc.Execute, nil | |||
| 112 | - | } | |||
A internal/bootstrap/handler_deps.go +30 -0
| @@ -0,0 +1,30 @@ | |||
| 1 | + | package bootstrap | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "context" | |
| 5 | + | "fmt" | |
| 6 | + | ||
| 7 | + | "github.com/bnema/dumber/internal/application/port" | |
| 8 | + | "github.com/bnema/dumber/internal/application/usecase" | |
| 9 | + | "github.com/bnema/dumber/internal/infrastructure/config" | |
| 10 | + | ) | |
| 11 | + | ||
| 12 | + | // BuildHandlerDeps constructs handler dependencies from the config manager. | |
| 13 | + | // It combines keybinding use cases and config save into a single struct. | |
| 14 | + | func BuildHandlerDeps(_ context.Context) (*port.HandlerDeps, error) { | |
| 15 | + | mgr := config.GetManager() | |
| 16 | + | if mgr == nil { | |
| 17 | + | return nil, fmt.Errorf("config manager not initialized") | |
| 18 | + | } | |
| 19 | + | ||
| 20 | + | gateway := config.NewKeybindingsGateway(mgr) | |
| 21 | + | saveUC := usecase.NewSaveWebUIConfigUseCase(config.NewWebUIConfigGateway(mgr)) | |
| 22 | + | ||
| 23 | + | return &port.HandlerDeps{ | |
| 24 | + | SaveConfig: saveUC.Execute, | |
| 25 | + | KeybindingsGetter: usecase.NewGetKeybindingsUseCase(gateway), | |
| 26 | + | KeybindingSetter: usecase.NewSetKeybindingUseCase(gateway, gateway), | |
| 27 | + | KeybindingResetter: usecase.NewResetKeybindingUseCase(gateway), | |
| 28 | + | AllKeybindingsResetter: usecase.NewResetAllKeybindingsUseCase(gateway), | |
| 29 | + | }, nil | |
| 30 | + | } | |
| @@ -0,0 +1,30 @@ | |||||
| 1 | + | package bootstrap | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "context" | |||
| 5 | + | "fmt" | |||
| 6 | + | ||||
| 7 | + | "github.com/bnema/dumber/internal/application/port" | |||
| 8 | + | "github.com/bnema/dumber/internal/application/usecase" | |||
| 9 | + | "github.com/bnema/dumber/internal/infrastructure/config" | |||
| 10 | + | ) | |||
| 11 | + | ||||
| 12 | + | // BuildHandlerDeps constructs handler dependencies from the config manager. | |||
| 13 | + | // It combines keybinding use cases and config save into a single struct. | |||
| 14 | + | func BuildHandlerDeps(_ context.Context) (*port.HandlerDeps, error) { | |||
| 15 | + | mgr := config.GetManager() | |||
| 16 | + | if mgr == nil { | |||
| 17 | + | return nil, fmt.Errorf("config manager not initialized") | |||
| 18 | + | } | |||
| 19 | + | ||||
| 20 | + | gateway := config.NewKeybindingsGateway(mgr) | |||
| 21 | + | saveUC := usecase.NewSaveWebUIConfigUseCase(config.NewWebUIConfigGateway(mgr)) | |||
| 22 | + | ||||
| 23 | + | return &port.HandlerDeps{ | |||
| 24 | + | SaveConfig: saveUC.Execute, | |||
| 25 | + | KeybindingsGetter: usecase.NewGetKeybindingsUseCase(gateway), | |||
| 26 | + | KeybindingSetter: usecase.NewSetKeybindingUseCase(gateway, gateway), | |||
| 27 | + | KeybindingResetter: usecase.NewResetKeybindingUseCase(gateway), | |||
| 28 | + | AllKeybindingsResetter: usecase.NewResetAllKeybindingsUseCase(gateway), | |||
| 29 | + | }, nil | |||
| 30 | + | } | |||
M internal/infrastructure/webkit/engine.go +3 -12
| @@ -7,6 +7,7 @@ import ( | |||
| 7 | 7 | "github.com/bnema/dumber/internal/application/port" | |
| 8 | 8 | "github.com/bnema/dumber/internal/infrastructure/config" | |
| 9 | 9 | "github.com/bnema/dumber/internal/infrastructure/filtering" | |
| 10 | + | "github.com/bnema/dumber/internal/infrastructure/handlers" | |
| 10 | 11 | "github.com/rs/zerolog" | |
| 11 | 12 | ) | |
| 12 | 13 | ||
| @@ -23,10 +24,6 @@ type Engine struct { | |||
| 23 | 24 | schemeHandler *DumbSchemeHandler | |
| 24 | 25 | schemePath string | |
| 25 | 26 | logger zerolog.Logger | |
| 26 | - | ||
| 27 | - | // Handler registrars - injected at construction to avoid import cycles. | |
| 28 | - | handlerRegistrar func(ctx context.Context, router port.WebUIHandlerRouter, deps port.HandlerDependencies) error | |
| 29 | - | accentHandlerRegistrar func(ctx context.Context, router port.WebUIHandlerRouter, handler port.AccentKeyHandler) error | |
| 30 | 27 | } | |
| 31 | 28 | ||
| 32 | 29 | // Compile-time check that Engine implements port.Engine. | |
| @@ -95,10 +92,7 @@ func (e *Engine) RegisterHandlers(ctx context.Context, deps port.HandlerDependen | |||
| 95 | 92 | if e.messageRouter == nil { | |
| 96 | 93 | return fmt.Errorf("message router not initialized") | |
| 97 | 94 | } | |
| 98 | - | if e.handlerRegistrar == nil { | |
| 99 | - | return fmt.Errorf("handler registrar not configured") | |
| 100 | - | } | |
| 101 | - | return e.handlerRegistrar(ctx, e.messageRouter, deps) | |
| 95 | + | return handlers.RegisterAll(ctx, e.messageRouter, deps) | |
| 102 | 96 | } | |
| 103 | 97 | ||
| 104 | 98 | // RegisterAccentHandlers registers accent/dead-key input handlers. | |
| @@ -106,10 +100,7 @@ func (e *Engine) RegisterAccentHandlers(ctx context.Context, handler port.Accent | |||
| 106 | 100 | if e.messageRouter == nil { | |
| 107 | 101 | return fmt.Errorf("message router not initialized") | |
| 108 | 102 | } | |
| 109 | - | if e.accentHandlerRegistrar == nil { | |
| 110 | - | return fmt.Errorf("accent handler registrar not configured") | |
| 111 | - | } | |
| 112 | - | return e.accentHandlerRegistrar(ctx, e.messageRouter, handler) | |
| 103 | + | return handlers.RegisterAccentHandlers(ctx, e.messageRouter, handler) | |
| 113 | 104 | } | |
| 114 | 105 | ||
| 115 | 106 | // ConfigureDownloads sets up download handling. | |
| @@ -7,6 +7,7 @@ import ( | |||||
| 7 | "github.com/bnema/dumber/internal/application/port" | 7 | "github.com/bnema/dumber/internal/application/port" | ||
| 8 | "github.com/bnema/dumber/internal/infrastructure/config" | 8 | "github.com/bnema/dumber/internal/infrastructure/config" | ||
| 9 | "github.com/bnema/dumber/internal/infrastructure/filtering" | 9 | "github.com/bnema/dumber/internal/infrastructure/filtering" | ||
| 10 | + | "github.com/bnema/dumber/internal/infrastructure/handlers" | |||
| 10 | "github.com/rs/zerolog" | 11 | "github.com/rs/zerolog" | ||
| 11 | ) | 12 | ) | ||
| 12 | 13 | ||||
| @@ -23,10 +24,6 @@ type Engine struct { | |||||
| 23 | schemeHandler *DumbSchemeHandler | 24 | schemeHandler *DumbSchemeHandler | ||
| 24 | schemePath string | 25 | schemePath string | ||
| 25 | logger zerolog.Logger | 26 | logger zerolog.Logger | ||
| 26 | - | ||||
| 27 | - | // Handler registrars - injected at construction to avoid import cycles. | |||
| 28 | - | handlerRegistrar func(ctx context.Context, router port.WebUIHandlerRouter, deps port.HandlerDependencies) error | |||
| 29 | - | accentHandlerRegistrar func(ctx context.Context, router port.WebUIHandlerRouter, handler port.AccentKeyHandler) error | |||
| 30 | } | 27 | } | ||
| 31 | 28 | ||||
| 32 | // Compile-time check that Engine implements port.Engine. | 29 | // Compile-time check that Engine implements port.Engine. | ||
| @@ -95,10 +92,7 @@ func (e *Engine) RegisterHandlers(ctx context.Context, deps port.HandlerDependen | |||||
| 95 | if e.messageRouter == nil { | 92 | if e.messageRouter == nil { | ||
| 96 | return fmt.Errorf("message router not initialized") | 93 | return fmt.Errorf("message router not initialized") | ||
| 97 | } | 94 | } | ||
| 98 | - | if e.handlerRegistrar == nil { | 95 | + | return handlers.RegisterAll(ctx, e.messageRouter, deps) |
| 99 | - | return fmt.Errorf("handler registrar not configured") | |||
| 100 | - | } | |||
| 101 | - | return e.handlerRegistrar(ctx, e.messageRouter, deps) | |||
| 102 | } | 96 | } | ||
| 103 | 97 | ||||
| 104 | // RegisterAccentHandlers registers accent/dead-key input handlers. | 98 | // RegisterAccentHandlers registers accent/dead-key input handlers. | ||
| @@ -106,10 +100,7 @@ func (e *Engine) RegisterAccentHandlers(ctx context.Context, handler port.Accent | |||||
| 106 | if e.messageRouter == nil { | 100 | if e.messageRouter == nil { | ||
| 107 | return fmt.Errorf("message router not initialized") | 101 | return fmt.Errorf("message router not initialized") | ||
| 108 | } | 102 | } | ||
| 109 | - | if e.accentHandlerRegistrar == nil { | 103 | + | return handlers.RegisterAccentHandlers(ctx, e.messageRouter, handler) |
| 110 | - | return fmt.Errorf("accent handler registrar not configured") | |||
| 111 | - | } | |||
| 112 | - | return e.accentHandlerRegistrar(ctx, e.messageRouter, handler) | |||
| 113 | } | 104 | } | ||
| 114 | 105 | ||||
| 115 | // ConfigureDownloads sets up download handling. | 106 | // ConfigureDownloads sets up download handling. | ||
M internal/infrastructure/webkit/engine_init.go +11 -15
| @@ -26,8 +26,6 @@ func NewEngine( | |||
| 26 | 26 | themeManager *theme.Manager, | |
| 27 | 27 | colorResolver port.ColorSchemeResolver, | |
| 28 | 28 | logger zerolog.Logger, | |
| 29 | - | handlerRegistrar func(ctx context.Context, router port.WebUIHandlerRouter, deps port.HandlerDependencies) error, | |
| 30 | - | accentHandlerRegistrar func(ctx context.Context, router port.WebUIHandlerRouter, handler port.AccentKeyHandler) error, | |
| 31 | 29 | ) (*Engine, error) { | |
| 32 | 30 | // --- Hardware survey and performance profile resolution --- | |
| 33 | 31 | perfSettings := engineSurveyHardwareAndResolveProfile(ctx, cfg, logger) | |
| @@ -106,19 +104,17 @@ func NewEngine( | |||
| 106 | 104 | ||
| 107 | 105 | // --- Assemble Engine --- | |
| 108 | 106 | engine := &Engine{ | |
| 109 | - | ctx: ctx, | |
| 110 | - | wkCtx: wkCtx, | |
| 111 | - | settings: settings, | |
| 112 | - | injector: injector, | |
| 113 | - | messageRouter: messageRouter, | |
| 114 | - | pool: pool, | |
| 115 | - | factory: factory, | |
| 116 | - | filterManager: filterManager, | |
| 117 | - | schemeHandler: schemeHandler, | |
| 118 | - | schemePath: "dumb://", | |
| 119 | - | logger: logger, | |
| 120 | - | handlerRegistrar: handlerRegistrar, | |
| 121 | - | accentHandlerRegistrar: accentHandlerRegistrar, | |
| 107 | + | ctx: ctx, | |
| 108 | + | wkCtx: wkCtx, | |
| 109 | + | settings: settings, | |
| 110 | + | injector: injector, | |
| 111 | + | messageRouter: messageRouter, | |
| 112 | + | pool: pool, | |
| 113 | + | factory: factory, | |
| 114 | + | filterManager: filterManager, | |
| 115 | + | schemeHandler: schemeHandler, | |
| 116 | + | schemePath: "dumb://", | |
| 117 | + | logger: logger, | |
| 122 | 118 | } | |
| 123 | 119 | ||
| 124 | 120 | return engine, nil | |
| @@ -26,8 +26,6 @@ func NewEngine( | |||||
| 26 | themeManager *theme.Manager, | 26 | themeManager *theme.Manager, | ||
| 27 | colorResolver port.ColorSchemeResolver, | 27 | colorResolver port.ColorSchemeResolver, | ||
| 28 | logger zerolog.Logger, | 28 | logger zerolog.Logger, | ||
| 29 | - | handlerRegistrar func(ctx context.Context, router port.WebUIHandlerRouter, deps port.HandlerDependencies) error, | |||
| 30 | - | accentHandlerRegistrar func(ctx context.Context, router port.WebUIHandlerRouter, handler port.AccentKeyHandler) error, | |||
| 31 | ) (*Engine, error) { | 29 | ) (*Engine, error) { | ||
| 32 | // --- Hardware survey and performance profile resolution --- | 30 | // --- Hardware survey and performance profile resolution --- | ||
| 33 | perfSettings := engineSurveyHardwareAndResolveProfile(ctx, cfg, logger) | 31 | perfSettings := engineSurveyHardwareAndResolveProfile(ctx, cfg, logger) | ||
| @@ -106,19 +104,17 @@ func NewEngine( | |||||
| 106 | 104 | ||||
| 107 | // --- Assemble Engine --- | 105 | // --- Assemble Engine --- | ||
| 108 | engine := &Engine{ | 106 | engine := &Engine{ | ||
| 109 | - | ctx: ctx, | 107 | + | ctx: ctx, |
| 110 | - | wkCtx: wkCtx, | 108 | + | wkCtx: wkCtx, |
| 111 | - | settings: settings, | 109 | + | settings: settings, |
| 112 | - | injector: injector, | 110 | + | injector: injector, |
| 113 | - | messageRouter: messageRouter, | 111 | + | messageRouter: messageRouter, |
| 114 | - | pool: pool, | 112 | + | pool: pool, |
| 115 | - | factory: factory, | 113 | + | factory: factory, |
| 116 | - | filterManager: filterManager, | 114 | + | filterManager: filterManager, |
| 117 | - | schemeHandler: schemeHandler, | 115 | + | schemeHandler: schemeHandler, |
| 118 | - | schemePath: "dumb://", | 116 | + | schemePath: "dumb://", |
| 119 | - | logger: logger, | 117 | + | logger: logger, |
| 120 | - | handlerRegistrar: handlerRegistrar, | |||
| 121 | - | accentHandlerRegistrar: accentHandlerRegistrar, | |||
| 122 | } | 118 | } | ||
| 123 | 119 | ||||
| 124 | return engine, nil | 120 | return engine, nil | ||
M internal/ui/app.go +5 -0
| @@ -204,6 +204,11 @@ func New(deps *Dependencies) (*App, error) { | |||
| 204 | 204 | }) | |
| 205 | 205 | glib.IdleAdd(&cb, 0) | |
| 206 | 206 | }, | |
| 207 | + | SaveConfig: deps.HandlerDeps.SaveConfig, | |
| 208 | + | KeybindingsGetter: deps.HandlerDeps.KeybindingsGetter, | |
| 209 | + | KeybindingSetter: deps.HandlerDeps.KeybindingSetter, | |
| 210 | + | KeybindingResetter: deps.HandlerDeps.KeybindingResetter, | |
| 211 | + | AllKeybindingsResetter: deps.HandlerDeps.AllKeybindingsResetter, | |
| 207 | 212 | }); err != nil { | |
| 208 | 213 | return nil, err | |
| 209 | 214 | } | |
| @@ -204,6 +204,11 @@ func New(deps *Dependencies) (*App, error) { | |||||
| 204 | }) | 204 | }) | ||
| 205 | glib.IdleAdd(&cb, 0) | 205 | glib.IdleAdd(&cb, 0) | ||
| 206 | }, | 206 | }, | ||
| 207 | + | SaveConfig: deps.HandlerDeps.SaveConfig, | |||
| 208 | + | KeybindingsGetter: deps.HandlerDeps.KeybindingsGetter, | |||
| 209 | + | KeybindingSetter: deps.HandlerDeps.KeybindingSetter, | |||
| 210 | + | KeybindingResetter: deps.HandlerDeps.KeybindingResetter, | |||
| 211 | + | AllKeybindingsResetter: deps.HandlerDeps.AllKeybindingsResetter, | |||
| 207 | }); err != nil { | 212 | }); err != nil { | ||
| 208 | return nil, err | 213 | return nil, err | ||
| 209 | } | 214 | } | ||
M internal/ui/deps.go +3 -0
| @@ -38,6 +38,9 @@ type Dependencies struct { | |||
| 38 | 38 | // Engine (replaces individual webkit fields) | |
| 39 | 39 | Engine port.Engine | |
| 40 | 40 | ||
| 41 | + | // Handler dependencies built by bootstrap (keybindings, config save). | |
| 42 | + | HandlerDeps port.HandlerDeps | |
| 43 | + | ||
| 41 | 44 | // Repositories | |
| 42 | 45 | HistoryRepo repository.HistoryRepository | |
| 43 | 46 | FavoriteRepo repository.FavoriteRepository | |
| @@ -38,6 +38,9 @@ type Dependencies struct { | |||||
| 38 | // Engine (replaces individual webkit fields) | 38 | // Engine (replaces individual webkit fields) | ||
| 39 | Engine port.Engine | 39 | Engine port.Engine | ||
| 40 | 40 | ||||
| 41 | + | // Handler dependencies built by bootstrap (keybindings, config save). | |||
| 42 | + | HandlerDeps port.HandlerDeps | |||
| 43 | + | ||||
| 41 | // Repositories | 44 | // Repositories | ||
| 42 | HistoryRepo repository.HistoryRepository | 45 | HistoryRepo repository.HistoryRepository | ||
| 43 | FavoriteRepo repository.FavoriteRepository | 46 | FavoriteRepo repository.FavoriteRepository | ||