detached 54ea5e7

feat(engine): wire SettingsApplier and FilterApplier into content coordinator

commit
54ea5e7712409963163e1fa047df323f3e49597b
author
brice · · 2026-03-18 05:41 UTC
parent
25cf728
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +55 -12
M internal/ui/app.go +8 -0
@@ -1544,6 +1544,14 @@ func (a *App) initCoordinators(ctx context.Context) {
15441544 a.contentCoord.SetIdleInhibitor(a.deps.IdleInhibitor)
15451545 }
15461546
1547+ // Wire engine settings and filter appliers for hot-reload and late-binding filters
1548+ if sa := a.deps.Engine.SettingsApplier(); sa != nil {
1549+ a.contentCoord.SetSettingsApplier(sa)
1550+ }
1551+ if fa := a.deps.Engine.FilterApplier(); fa != nil {
1552+ a.contentCoord.SetFilterApplier(fa)
1553+ }
1554+
15471555 // Wire external URL launcher (e.g. xdg-open for vscode://, spotify://)
15481556 if a.deps.LaunchExternalURL != nil {
15491557 a.contentCoord.SetOnLaunchExternalURL(a.deps.LaunchExternalURL)
@@ -1544,6 +1544,14 @@ func (a *App) initCoordinators(ctx context.Context) {
1544 a.contentCoord.SetIdleInhibitor(a.deps.IdleInhibitor)1544 a.contentCoord.SetIdleInhibitor(a.deps.IdleInhibitor)
1545 }1545 }
1546 1546
1547+ // Wire engine settings and filter appliers for hot-reload and late-binding filters
1548+ if sa := a.deps.Engine.SettingsApplier(); sa != nil {
1549+ a.contentCoord.SetSettingsApplier(sa)
1550+ }
1551+ if fa := a.deps.Engine.FilterApplier(); fa != nil {
1552+ a.contentCoord.SetFilterApplier(fa)
1553+ }
1554+
1547 // Wire external URL launcher (e.g. xdg-open for vscode://, spotify://)1555 // Wire external URL launcher (e.g. xdg-open for vscode://, spotify://)
1548 if a.deps.LaunchExternalURL != nil {1556 if a.deps.LaunchExternalURL != nil {
1549 a.contentCoord.SetOnLaunchExternalURL(a.deps.LaunchExternalURL)1557 a.contentCoord.SetOnLaunchExternalURL(a.deps.LaunchExternalURL)
M internal/ui/coordinator/content/bulk.go +29 -6
@@ -10,17 +10,41 @@ import (
1010
1111 // ApplyFiltersToAll applies content filters to all active webviews.
1212 // Called when filters become available after webviews were already created.
13-// TODO: Accept a port.FilterApplier once engine abstraction is wired.
1413 func (c *Coordinator) ApplyFiltersToAll(ctx context.Context) {
1514 log := logging.FromContext(ctx)
16- log.Debug().Msg("ApplyFiltersToAll: not yet implemented with engine abstraction")
15+ if c.filterApplier == nil {
16+ log.Debug().Msg("ApplyFiltersToAll: no filter applier configured")
17+ return
18+ }
19+
20+ c.webViewsMu.RLock()
21+ snapshot := make([]port.WebView, 0, len(c.webViews))
22+ for _, wv := range c.webViews {
23+ snapshot = append(snapshot, wv)
24+ }
25+ c.webViewsMu.RUnlock()
26+
27+ c.filterApplier.ApplyToAll(ctx, snapshot)
28+ log.Debug().Int("count", len(snapshot)).Msg("applied filters to all webviews")
1729 }
1830
19-// ApplySettingsToAll reapplies WebKit settings to all active WebViews.
20-// TODO: Accept engine-level settings once engine abstraction is wired.
31+// ApplySettingsToAll reapplies engine settings to all active WebViews.
2132 func (c *Coordinator) ApplySettingsToAll(ctx context.Context) {
2233 log := logging.FromContext(ctx)
23- log.Debug().Msg("ApplySettingsToAll: not yet implemented with engine abstraction")
34+ if c.settingsApplier == nil {
35+ log.Debug().Msg("ApplySettingsToAll: no settings applier configured")
36+ return
37+ }
38+
39+ c.webViewsMu.RLock()
40+ snapshot := make([]port.WebView, 0, len(c.webViews))
41+ for _, wv := range c.webViews {
42+ snapshot = append(snapshot, wv)
43+ }
44+ c.webViewsMu.RUnlock()
45+
46+ c.settingsApplier.ApplyToAll(ctx, snapshot)
47+ log.Debug().Int("count", len(snapshot)).Msg("applied settings to all webviews")
2448 }
2549
2650 // RefreshInjectedScriptsToAll clears and re-injects user scripts into all active WebViews.
@@ -30,7 +54,6 @@ func (c *Coordinator) ApplySettingsToAll(ctx context.Context) {
3054 // the scripts so future navigations pick up the latest values.
3155 // Script refresh is deferred for any WebView that is currently loading to avoid
3256 // removing scripts mid-navigation.
33-// TODO: Accept engine-level injector once engine abstraction is wired.
3457 func (c *Coordinator) RefreshInjectedScriptsToAll(ctx context.Context) {
3558 log := logging.FromContext(ctx)
3659 if c.injector == nil {
@@ -10,17 +10,41 @@ import (
10 10
11 // ApplyFiltersToAll applies content filters to all active webviews.11 // ApplyFiltersToAll applies content filters to all active webviews.
12 // Called when filters become available after webviews were already created.12 // Called when filters become available after webviews were already created.
13-// TODO: Accept a port.FilterApplier once engine abstraction is wired.
14 func (c *Coordinator) ApplyFiltersToAll(ctx context.Context) {13 func (c *Coordinator) ApplyFiltersToAll(ctx context.Context) {
15 log := logging.FromContext(ctx)14 log := logging.FromContext(ctx)
16- log.Debug().Msg("ApplyFiltersToAll: not yet implemented with engine abstraction")15+ if c.filterApplier == nil {
16+ log.Debug().Msg("ApplyFiltersToAll: no filter applier configured")
17+ return
18+ }
19+
20+ c.webViewsMu.RLock()
21+ snapshot := make([]port.WebView, 0, len(c.webViews))
22+ for _, wv := range c.webViews {
23+ snapshot = append(snapshot, wv)
24+ }
25+ c.webViewsMu.RUnlock()
26+
27+ c.filterApplier.ApplyToAll(ctx, snapshot)
28+ log.Debug().Int("count", len(snapshot)).Msg("applied filters to all webviews")
17 }29 }
18 30
19-// ApplySettingsToAll reapplies WebKit settings to all active WebViews.31+// ApplySettingsToAll reapplies engine settings to all active WebViews.
20-// TODO: Accept engine-level settings once engine abstraction is wired.
21 func (c *Coordinator) ApplySettingsToAll(ctx context.Context) {32 func (c *Coordinator) ApplySettingsToAll(ctx context.Context) {
22 log := logging.FromContext(ctx)33 log := logging.FromContext(ctx)
23- log.Debug().Msg("ApplySettingsToAll: not yet implemented with engine abstraction")34+ if c.settingsApplier == nil {
35+ log.Debug().Msg("ApplySettingsToAll: no settings applier configured")
36+ return
37+ }
38+
39+ c.webViewsMu.RLock()
40+ snapshot := make([]port.WebView, 0, len(c.webViews))
41+ for _, wv := range c.webViews {
42+ snapshot = append(snapshot, wv)
43+ }
44+ c.webViewsMu.RUnlock()
45+
46+ c.settingsApplier.ApplyToAll(ctx, snapshot)
47+ log.Debug().Int("count", len(snapshot)).Msg("applied settings to all webviews")
24 }48 }
25 49
26 // RefreshInjectedScriptsToAll clears and re-injects user scripts into all active WebViews.50 // RefreshInjectedScriptsToAll clears and re-injects user scripts into all active WebViews.
@@ -30,7 +54,6 @@ func (c *Coordinator) ApplySettingsToAll(ctx context.Context) {
30 // the scripts so future navigations pick up the latest values.54 // the scripts so future navigations pick up the latest values.
31 // Script refresh is deferred for any WebView that is currently loading to avoid55 // Script refresh is deferred for any WebView that is currently loading to avoid
32 // removing scripts mid-navigation.56 // removing scripts mid-navigation.
33-// TODO: Accept engine-level injector once engine abstraction is wired.
34 func (c *Coordinator) RefreshInjectedScriptsToAll(ctx context.Context) {57 func (c *Coordinator) RefreshInjectedScriptsToAll(ctx context.Context) {
35 log := logging.FromContext(ctx)58 log := logging.FromContext(ctx)
36 if c.injector == nil {59 if c.injector == nil {
M internal/ui/coordinator/content/coordinator.go +18 -6
@@ -18,12 +18,14 @@ import (
1818
1919 // Coordinator manages WebView lifecycle, title tracking, and content attachment.
2020 type Coordinator struct {
21- pool port.WebViewPool
22- widgetFactory layout.WidgetFactory
23- faviconAdapter *adapter.FaviconAdapter
24- zoomUC *usecase.ManageZoomUseCase
25- permissionUC *usecase.HandlePermissionUseCase
26- injector port.ContentInjector
21+ pool port.WebViewPool
22+ widgetFactory layout.WidgetFactory
23+ faviconAdapter *adapter.FaviconAdapter
24+ zoomUC *usecase.ManageZoomUseCase
25+ permissionUC *usecase.HandlePermissionUseCase
26+ injector port.ContentInjector
27+ settingsApplier port.SettingsApplier // optional: nil if engine doesn't support
28+ filterApplier port.FilterApplier // optional: nil if engine doesn't support
2729
2830 webViews map[entity.PaneID]port.WebView
2931 webViewsMu sync.RWMutex
@@ -238,6 +240,16 @@ func (c *Coordinator) SetOnLaunchExternalURL(fn func(uri string)) {
238240 c.onLaunchExternalURL = fn
239241 }
240242
243+// SetSettingsApplier sets the engine settings applier for config hot-reload.
244+func (c *Coordinator) SetSettingsApplier(sa port.SettingsApplier) {
245+ c.settingsApplier = sa
246+}
247+
248+// SetFilterApplier sets the content filter applier for late-binding filters.
249+func (c *Coordinator) SetFilterApplier(fa port.FilterApplier) {
250+ c.filterApplier = fa
251+}
252+
241253 // ActivePaneID returns the currently active pane ID used by navigation.
242254 func (c *Coordinator) ActivePaneID(ctx context.Context) entity.PaneID {
243255 if paneID, ok := c.activePaneOverrideID(); ok {
@@ -18,12 +18,14 @@ import (
18 18
19 // Coordinator manages WebView lifecycle, title tracking, and content attachment.19 // Coordinator manages WebView lifecycle, title tracking, and content attachment.
20 type Coordinator struct {20 type Coordinator struct {
21- pool port.WebViewPool21+ pool port.WebViewPool
22- widgetFactory layout.WidgetFactory22+ widgetFactory layout.WidgetFactory
23- faviconAdapter *adapter.FaviconAdapter23+ faviconAdapter *adapter.FaviconAdapter
24- zoomUC *usecase.ManageZoomUseCase24+ zoomUC *usecase.ManageZoomUseCase
25- permissionUC *usecase.HandlePermissionUseCase25+ permissionUC *usecase.HandlePermissionUseCase
26- injector port.ContentInjector26+ injector port.ContentInjector
27+ settingsApplier port.SettingsApplier // optional: nil if engine doesn't support
28+ filterApplier port.FilterApplier // optional: nil if engine doesn't support
27 29
28 webViews map[entity.PaneID]port.WebView30 webViews map[entity.PaneID]port.WebView
29 webViewsMu sync.RWMutex31 webViewsMu sync.RWMutex
@@ -238,6 +240,16 @@ func (c *Coordinator) SetOnLaunchExternalURL(fn func(uri string)) {
238 c.onLaunchExternalURL = fn240 c.onLaunchExternalURL = fn
239 }241 }
240 242
243+// SetSettingsApplier sets the engine settings applier for config hot-reload.
244+func (c *Coordinator) SetSettingsApplier(sa port.SettingsApplier) {
245+ c.settingsApplier = sa
246+}
247+
248+// SetFilterApplier sets the content filter applier for late-binding filters.
249+func (c *Coordinator) SetFilterApplier(fa port.FilterApplier) {
250+ c.filterApplier = fa
251+}
252+
241 // ActivePaneID returns the currently active pane ID used by navigation.253 // ActivePaneID returns the currently active pane ID used by navigation.
242 func (c *Coordinator) ActivePaneID(ctx context.Context) entity.PaneID {254 func (c *Coordinator) ActivePaneID(ctx context.Context) entity.PaneID {
243 if paneID, ok := c.activePaneOverrideID(); ok {255 if paneID, ok := c.activePaneOverrideID(); ok {

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