detached e23b0ff

fix(zoom): prevent visual glitch during page navigation

Zoom level for the next page was being applied before navigation completed,
causing a visible zoom change on the current page.

Solution: Apply zoom on load-committed event instead of URI change.
- Added RegisterLoadCommittedHandler to WebView
- handleURIChange now only tracks URL without applying zoom
- handleLoadCommitted applies zoom when page load actually starts

This ensures zoom is applied at the right time, eliminating the visual glitch.
commit
e23b0ff634f1f426eb9402f53f6a77aaf8f9a138
author
bnema · · 2025-11-16 19:00 UTC
committer
bnema · · 2025-11-16 22:17 UTC
parent
a2289aa
signature
unsigned
2 files · +25 -1
M internal/app/control/zoom.go +8 -1
@@ -26,6 +26,7 @@ type WebViewInterface interface {
2626 InjectScript(script string) error
2727 RegisterZoomChangedHandler(handler func(float64))
2828 RegisterURIChangedHandler(handler func(string))
29+ RegisterLoadCommittedHandler(handler func(string))
2930 }
3031
3132 // NewZoomController creates a new zoom controller
@@ -41,11 +42,17 @@ func NewZoomController(browserService *services.BrowserService, webView *webkit.
4142 func (z *ZoomController) RegisterHandlers() {
4243 z.webView.RegisterZoomChangedHandler(z.handleZoomChange)
4344 z.webView.RegisterURIChangedHandler(z.handleURIChange)
45+ z.webView.RegisterLoadCommittedHandler(z.handleLoadCommitted)
4446 }
4547
46-// handleURIChange responds to URL changes and applies saved zoom levels
48+// handleURIChange tracks URL changes but doesn't apply zoom (wait for load-committed)
4749 func (z *ZoomController) handleURIChange(url string) {
4850 z.currentURL = url
51+}
52+
53+// handleLoadCommitted responds to load committed events and applies saved zoom levels
54+// This fires when the page actually starts loading content, preventing visual glitches
55+func (z *ZoomController) handleLoadCommitted(url string) {
4956 if url == "" {
5057 return
5158 }
@@ -26,6 +26,7 @@ type WebViewInterface interface {
26 InjectScript(script string) error26 InjectScript(script string) error
27 RegisterZoomChangedHandler(handler func(float64))27 RegisterZoomChangedHandler(handler func(float64))
28 RegisterURIChangedHandler(handler func(string))28 RegisterURIChangedHandler(handler func(string))
29+ RegisterLoadCommittedHandler(handler func(string))
29 }30 }
30 31
31 // NewZoomController creates a new zoom controller32 // NewZoomController creates a new zoom controller
@@ -41,11 +42,17 @@ func NewZoomController(browserService *services.BrowserService, webView *webkit.
41 func (z *ZoomController) RegisterHandlers() {42 func (z *ZoomController) RegisterHandlers() {
42 z.webView.RegisterZoomChangedHandler(z.handleZoomChange)43 z.webView.RegisterZoomChangedHandler(z.handleZoomChange)
43 z.webView.RegisterURIChangedHandler(z.handleURIChange)44 z.webView.RegisterURIChangedHandler(z.handleURIChange)
45+ z.webView.RegisterLoadCommittedHandler(z.handleLoadCommitted)
44 }46 }
45 47
46-// handleURIChange responds to URL changes and applies saved zoom levels48+// handleURIChange tracks URL changes but doesn't apply zoom (wait for load-committed)
47 func (z *ZoomController) handleURIChange(url string) {49 func (z *ZoomController) handleURIChange(url string) {
48 z.currentURL = url50 z.currentURL = url
51+}
52+
53+// handleLoadCommitted responds to load committed events and applies saved zoom levels
54+// This fires when the page actually starts loading content, preventing visual glitches
55+func (z *ZoomController) handleLoadCommitted(url string) {
49 if url == "" {56 if url == "" {
50 return57 return
51 }58 }
M pkg/webkit/webview.go +17 -0
@@ -38,6 +38,7 @@ type WebView struct {
3838 onFaviconChanged func([]byte)
3939 onFaviconURIChanged func(pageURI, faviconURI string)
4040 onZoomChanged func(float64)
41+ onLoadCommitted func(string) // Called when page load is committed (safe to apply zoom)
4142 onPopupCreate func(*webkit.NavigationAction) *WebView // New WebKit create signal handler
4243 onReadyToShow func() // WebKit ready-to-show signal handler
4344 onClose func()
@@ -234,6 +235,14 @@ func (w *WebView) setupEventHandlers() {
234235 }
235236 })
236237
238+ // Load committed - connect to load-changed signal for WEBKIT_LOAD_COMMITTED
239+ w.view.ConnectLoadChanged(func(loadEvent webkit.LoadEvent) {
240+ if loadEvent == webkit.LoadCommitted && w.onLoadCommitted != nil {
241+ uri := w.view.URI()
242+ w.onLoadCommitted(uri)
243+ }
244+ })
245+
237246 // Favicon changed - connect to FaviconDatabase
238247 w.setupFaviconHandlers()
239248
@@ -605,6 +614,14 @@ func (w *WebView) RegisterZoomChangedHandler(handler func(float64)) {
605614 w.onZoomChanged = handler
606615 }
607616
617+// RegisterLoadCommittedHandler registers a handler for load committed events
618+// This fires when the page actually starts loading new content (after URI change)
619+func (w *WebView) RegisterLoadCommittedHandler(handler func(string)) {
620+ w.mu.Lock()
621+ defer w.mu.Unlock()
622+ w.onLoadCommitted = handler
623+}
624+
608625 // RegisterCloseHandler registers a handler for close requests
609626 func (w *WebView) RegisterCloseHandler(handler func()) {
610627 w.mu.Lock()
@@ -38,6 +38,7 @@ type WebView struct {
38 onFaviconChanged func([]byte)38 onFaviconChanged func([]byte)
39 onFaviconURIChanged func(pageURI, faviconURI string)39 onFaviconURIChanged func(pageURI, faviconURI string)
40 onZoomChanged func(float64)40 onZoomChanged func(float64)
41+ onLoadCommitted func(string) // Called when page load is committed (safe to apply zoom)
41 onPopupCreate func(*webkit.NavigationAction) *WebView // New WebKit create signal handler42 onPopupCreate func(*webkit.NavigationAction) *WebView // New WebKit create signal handler
42 onReadyToShow func() // WebKit ready-to-show signal handler43 onReadyToShow func() // WebKit ready-to-show signal handler
43 onClose func()44 onClose func()
@@ -234,6 +235,14 @@ func (w *WebView) setupEventHandlers() {
234 }235 }
235 })236 })
236 237
238+ // Load committed - connect to load-changed signal for WEBKIT_LOAD_COMMITTED
239+ w.view.ConnectLoadChanged(func(loadEvent webkit.LoadEvent) {
240+ if loadEvent == webkit.LoadCommitted && w.onLoadCommitted != nil {
241+ uri := w.view.URI()
242+ w.onLoadCommitted(uri)
243+ }
244+ })
245+
237 // Favicon changed - connect to FaviconDatabase246 // Favicon changed - connect to FaviconDatabase
238 w.setupFaviconHandlers()247 w.setupFaviconHandlers()
239 248
@@ -605,6 +614,14 @@ func (w *WebView) RegisterZoomChangedHandler(handler func(float64)) {
605 w.onZoomChanged = handler614 w.onZoomChanged = handler
606 }615 }
607 616
617+// RegisterLoadCommittedHandler registers a handler for load committed events
618+// This fires when the page actually starts loading new content (after URI change)
619+func (w *WebView) RegisterLoadCommittedHandler(handler func(string)) {
620+ w.mu.Lock()
621+ defer w.mu.Unlock()
622+ w.onLoadCommitted = handler
623+}
624+
608 // RegisterCloseHandler registers a handler for close requests625 // RegisterCloseHandler registers a handler for close requests
609 func (w *WebView) RegisterCloseHandler(handler func()) {626 func (w *WebView) RegisterCloseHandler(handler func()) {
610 w.mu.Lock()627 w.mu.Lock()

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