detached 6b1adf4

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 VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
8 files · +75 -93
M cmd/dumber/main.go +12 -3
@@ -223,11 +223,14 @@ func buildAndConfigureApp(
223223 idleInhibitor port.IdleInhibitor,
224224 browserSession *bootstrap.BrowserSession,
225225 ) (*ui.App, error) {
226- uiDeps := buildUIDependencies(
226+ uiDeps, err := buildUIDependencies(
227227 ctx, cfg, initResult.ThemeManager,
228228 initResult.ColorResolver, initResult.AdwaitaDetector,
229229 engine, repos, useCases, idleInhibitor, browserSession.Session.ID, browserSession.CrashReports(),
230230 )
231+ if err != nil {
232+ return nil, err
233+ }
231234 configureDeferredInit(uiDeps, cfg, browserSession)
232235 return ui.New(uiDeps)
233236 }
@@ -548,12 +551,17 @@ func buildUIDependencies(
548551 idleInhibitor port.IdleInhibitor,
549552 currentSessionID entity.SessionID,
550553 startupCrashReports []string,
551-) *ui.Dependencies {
554+) (*ui.Dependencies, error) {
552555 var filterManager port.FilterManager
553556 if fmp, ok := engine.(port.FilterManagerProvider); ok {
554557 filterManager = fmp.InternalFilterManager()
555558 }
556559
560+ handlerDeps, err := bootstrap.BuildHandlerDeps(ctx)
561+ if err != nil {
562+ return nil, fmt.Errorf("failed to build handler deps: %w", err)
563+ }
564+
557565 focusProvider := textinput.NewFocusProvider()
558566
559567 uiDeps := &ui.Dependencies{
@@ -620,7 +628,8 @@ func buildUIDependencies(
620628 },
621629 LaunchExternalURL: desktop.LaunchExternalURL,
622630 ConfigMigrator: config.NewMigrator(),
631+ HandlerDeps: *handlerDeps,
623632 }
624633
625- return uiDeps
634+ return uiDeps, nil
626635 }
@@ -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.FilterManager555 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 uiDeps634+ return uiDeps, nil
626 }635 }
M internal/application/port/handler_deps.go +11 -0
@@ -30,3 +30,14 @@ type DownloadPrepareOutput struct {
3030 type AutoCopyConfig interface {
3131 IsAutoCopyEnabled() bool
3232 }
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() bool31 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 (
55 "fmt"
66
77 "github.com/bnema/dumber/internal/application/port"
8- "github.com/bnema/dumber/internal/application/usecase"
98 "github.com/bnema/dumber/internal/infrastructure/cef"
109 "github.com/bnema/dumber/internal/infrastructure/config"
11- "github.com/bnema/dumber/internal/infrastructure/handlers"
1210 "github.com/bnema/dumber/internal/infrastructure/webkit"
1311 "github.com/bnema/dumber/internal/ui/theme"
1412 "github.com/rs/zerolog"
@@ -38,33 +36,9 @@ func BuildEngine(input EngineInput) (port.Engine, error) {
3836 }
3937 wkCfg := webkit.EngineConfigFromConfig(cfg.Engine.WebKit)
4038
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-
5339 return webkit.NewEngine(
5440 input.Ctx, cfg, opts, wkCfg,
5541 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- },
6842 )
6943 case "cef":
7044 cefCfg := cfg.Engine.CEF
@@ -73,40 +47,3 @@ func BuildEngine(input EngineInput) (port.Engine, error) {
7347 return nil, fmt.Errorf("unknown engine type: %q", engineType)
7448 }
7549 }
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.CEF44 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 (
77 "github.com/bnema/dumber/internal/application/port"
88 "github.com/bnema/dumber/internal/infrastructure/config"
99 "github.com/bnema/dumber/internal/infrastructure/filtering"
10+ "github.com/bnema/dumber/internal/infrastructure/handlers"
1011 "github.com/rs/zerolog"
1112 )
1213
@@ -23,10 +24,6 @@ type Engine struct {
2324 schemeHandler *DumbSchemeHandler
2425 schemePath string
2526 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
3027 }
3128
3229 // Compile-time check that Engine implements port.Engine.
@@ -95,10 +92,7 @@ func (e *Engine) RegisterHandlers(ctx context.Context, deps port.HandlerDependen
9592 if e.messageRouter == nil {
9693 return fmt.Errorf("message router not initialized")
9794 }
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)
10296 }
10397
10498 // RegisterAccentHandlers registers accent/dead-key input handlers.
@@ -106,10 +100,7 @@ func (e *Engine) RegisterAccentHandlers(ctx context.Context, handler port.Accent
106100 if e.messageRouter == nil {
107101 return fmt.Errorf("message router not initialized")
108102 }
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)
113104 }
114105
115106 // 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 *DumbSchemeHandler24 schemeHandler *DumbSchemeHandler
24 schemePath string25 schemePath string
25 logger zerolog.Logger26 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(
2626 themeManager *theme.Manager,
2727 colorResolver port.ColorSchemeResolver,
2828 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,
3129 ) (*Engine, error) {
3230 // --- Hardware survey and performance profile resolution ---
3331 perfSettings := engineSurveyHardwareAndResolveProfile(ctx, cfg, logger)
@@ -106,19 +104,17 @@ func NewEngine(
106104
107105 // --- Assemble Engine ---
108106 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,
122118 }
123119
124120 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, nil120 return engine, nil
M internal/ui/app.go +5 -0
@@ -204,6 +204,11 @@ func New(deps *Dependencies) (*App, error) {
204204 })
205205 glib.IdleAdd(&cb, 0)
206206 },
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,
207212 }); err != nil {
208213 return nil, err
209214 }
@@ -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, err213 return nil, err
209 }214 }
M internal/ui/deps.go +3 -0
@@ -38,6 +38,9 @@ type Dependencies struct {
3838 // Engine (replaces individual webkit fields)
3939 Engine port.Engine
4040
41+ // Handler dependencies built by bootstrap (keybindings, config save).
42+ HandlerDeps port.HandlerDeps
43+
4144 // Repositories
4245 HistoryRepo repository.HistoryRepository
4346 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.Engine39 Engine port.Engine
40 40
41+ // Handler dependencies built by bootstrap (keybindings, config save).
42+ HandlerDeps port.HandlerDeps
43+
41 // Repositories44 // Repositories
42 HistoryRepo repository.HistoryRepository45 HistoryRepo repository.HistoryRepository
43 FavoriteRepo repository.FavoriteRepository46 FavoriteRepo repository.FavoriteRepository

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