detached c55079e

chore(arch): clean up low-risk architecture backlog items

commit
c55079e5c0a3331c838d9394c287f3ac03b38068
author
brice · · 2026-06-25 09:06 UTC
parent
d3cb888
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
21 files · +193 -54
M README.md +5 -3
@@ -109,7 +109,7 @@ The floating pane is a temporary browser pane that can be toggled without changi
109109 - `Alt+F` toggles the floating pane and preserves its state while hidden.
110110 - `Ctrl+W` closes the active pane; if the floating pane is active, it is fully released for a fresh next open.
111111 - Profile shortcuts such as `Alt+G` are optional and configured under `workspace.floating_pane.profiles`.
112-- Some `Alt+<key>` bindings may conflict with WebKit shortcuts or desktop-level handlers.
112+- Some `Alt+<key>` bindings may conflict with browser-engine shortcuts or desktop-level handlers.
113113
114114 See the [floating pane reference](https://dumber.bnema.dev/docs/reference/floating-pane) for setup and behavior details.
115115
@@ -200,7 +200,7 @@ Bug reports and reproducible Wayland/backend issues are welcome.
200200
201201 ## Development
202202
203-Dumber uses pure-Go bindings and requires GTK4/WebKitGTK runtime libraries for the GUI.
203+Dumber uses pure-Go bindings. The GUI uses GTK4, runs on CEF by default, and can use WebKitGTK as a fallback backend.
204204
205205 Set `ENV=dev` to use `.dev/dumber/` for config and data instead of XDG paths.
206206
@@ -209,7 +209,9 @@ Set `ENV=dev` to use `.dev/dumber/` for config and data instead of XDG paths.
209209 **Prerequisites:**
210210
211211 - Go 1.26+
212-- WebKitGTK 6.0 and GTK4 development packages
212+- GTK4 development packages
213+- CEF runtime (default backend)
214+- WebKitGTK 6.0 development/runtime packages (fallback backend and runtime checks)
213215 - Brotli for compressed systemviews assets
214216
215217 Systemviews assets are generated with `go tool templ` and Go's `js/wasm` toolchain; no root Node toolchain is required.
@@ -109,7 +109,7 @@ The floating pane is a temporary browser pane that can be toggled without changi
109 - `Alt+F` toggles the floating pane and preserves its state while hidden.109 - `Alt+F` toggles the floating pane and preserves its state while hidden.
110 - `Ctrl+W` closes the active pane; if the floating pane is active, it is fully released for a fresh next open.110 - `Ctrl+W` closes the active pane; if the floating pane is active, it is fully released for a fresh next open.
111 - Profile shortcuts such as `Alt+G` are optional and configured under `workspace.floating_pane.profiles`.111 - Profile shortcuts such as `Alt+G` are optional and configured under `workspace.floating_pane.profiles`.
112-- Some `Alt+<key>` bindings may conflict with WebKit shortcuts or desktop-level handlers.112+- Some `Alt+<key>` bindings may conflict with browser-engine shortcuts or desktop-level handlers.
113 113
114 See the [floating pane reference](https://dumber.bnema.dev/docs/reference/floating-pane) for setup and behavior details.114 See the [floating pane reference](https://dumber.bnema.dev/docs/reference/floating-pane) for setup and behavior details.
115 115
@@ -200,7 +200,7 @@ Bug reports and reproducible Wayland/backend issues are welcome.
200 200
201 ## Development201 ## Development
202 202
203-Dumber uses pure-Go bindings and requires GTK4/WebKitGTK runtime libraries for the GUI.203+Dumber uses pure-Go bindings. The GUI uses GTK4, runs on CEF by default, and can use WebKitGTK as a fallback backend.
204 204
205 Set `ENV=dev` to use `.dev/dumber/` for config and data instead of XDG paths.205 Set `ENV=dev` to use `.dev/dumber/` for config and data instead of XDG paths.
206 206
@@ -209,7 +209,9 @@ Set `ENV=dev` to use `.dev/dumber/` for config and data instead of XDG paths.
209 **Prerequisites:**209 **Prerequisites:**
210 210
211 - Go 1.26+211 - Go 1.26+
212-- WebKitGTK 6.0 and GTK4 development packages212+- GTK4 development packages
213+- CEF runtime (default backend)
214+- WebKitGTK 6.0 development/runtime packages (fallback backend and runtime checks)
213 - Brotli for compressed systemviews assets215 - Brotli for compressed systemviews assets
214 216
215 Systemviews assets are generated with `go tool templ` and Go's `js/wasm` toolchain; no root Node toolchain is required.217 Systemviews assets are generated with `go tool templ` and Go's `js/wasm` toolchain; no root Node toolchain is required.
M docs/cli/index.md +1 -1
@@ -141,7 +141,7 @@ dumber doctor [flags]
141141
142142 | Flag | Description |
143143 |------|-------------|
144-| `--runtime` | Only run runtime checks (GTK4/WebKitGTK) |
144+| `--runtime` | Only run runtime checks for GTK and WebKit fallback libraries |
145145 | `--media` | Only run media checks (GStreamer/VA-API) |
146146
147147 ### setup
@@ -141,7 +141,7 @@ dumber doctor [flags]
141 141
142 | Flag | Description |142 | Flag | Description |
143 |------|-------------|143 |------|-------------|
144-| `--runtime` | Only run runtime checks (GTK4/WebKitGTK) |144+| `--runtime` | Only run runtime checks for GTK and WebKit fallback libraries |
145 | `--media` | Only run media checks (GStreamer/VA-API) |145 | `--media` | Only run media checks (GStreamer/VA-API) |
146 146
147 ### setup147 ### setup
M docs/getting-started.md +2 -2
@@ -39,7 +39,7 @@ Press `Ctrl+L` to open the omnibox for:
3939
4040 - Press `Alt+F` to toggle the floating pane.
4141 - Press `Ctrl+W` to close the active pane; if the floating pane is active, this fully releases it so the next open starts fresh.
42-- Some `Alt+<key>` bindings can conflict with default WebKit shortcuts or desktop-level handlers.
42+- Some `Alt+<key>` bindings can conflict with browser-engine shortcuts or desktop-level handlers.
4343
4444 See [Floating Pane](./reference/floating-pane.md) for profile shortcuts and configuration.
4545
@@ -70,7 +70,7 @@ Dumber includes a built-in permissions system for camera, microphone, and screen
7070 - **Privacy-focused** - All permissions stored locally
7171 - **Camera & Microphone** - Fully working on Wayland/PipeWire
7272
73-> ⚠️ **Note**: Screen sharing does not currently work on Wayland with WebKitGTK 6.0. This is a known WebKitGTK limitation.
73+> ⚠️ **WebKit fallback note**: Screen sharing does not currently work on Wayland with WebKitGTK 6.0. This is a known WebKitGTK limitation and does not describe the default CEF backend.
7474
7575 When a website requests camera or microphone access, you'll see a permission dialog with options to allow once, always allow, deny, or always deny.
7676
@@ -39,7 +39,7 @@ Press `Ctrl+L` to open the omnibox for:
39 39
40 - Press `Alt+F` to toggle the floating pane.40 - Press `Alt+F` to toggle the floating pane.
41 - Press `Ctrl+W` to close the active pane; if the floating pane is active, this fully releases it so the next open starts fresh.41 - Press `Ctrl+W` to close the active pane; if the floating pane is active, this fully releases it so the next open starts fresh.
42-- Some `Alt+<key>` bindings can conflict with default WebKit shortcuts or desktop-level handlers.42+- Some `Alt+<key>` bindings can conflict with browser-engine shortcuts or desktop-level handlers.
43 43
44 See [Floating Pane](./reference/floating-pane.md) for profile shortcuts and configuration.44 See [Floating Pane](./reference/floating-pane.md) for profile shortcuts and configuration.
45 45
@@ -70,7 +70,7 @@ Dumber includes a built-in permissions system for camera, microphone, and screen
70 - **Privacy-focused** - All permissions stored locally70 - **Privacy-focused** - All permissions stored locally
71 - **Camera & Microphone** - Fully working on Wayland/PipeWire71 - **Camera & Microphone** - Fully working on Wayland/PipeWire
72 72
73-> ⚠️ **Note**: Screen sharing does not currently work on Wayland with WebKitGTK 6.0. This is a known WebKitGTK limitation.73+> ⚠️ **WebKit fallback note**: Screen sharing does not currently work on Wayland with WebKitGTK 6.0. This is a known WebKitGTK limitation and does not describe the default CEF backend.
74 74
75 When a website requests camera or microphone access, you'll see a permission dialog with options to allow once, always allow, deny, or always deny.75 When a website requests camera or microphone access, you'll see a permission dialog with options to allow once, always allow, deny, or always deny.
76 76
M docs/index.md +3 -3
@@ -1,6 +1,6 @@
11 # Dumber Documentation
22
3-Dumber is a keyboard-driven browser for tiling window managers, built with GTK4 and WebKitGTK.
3+Dumber is a keyboard-driven browser for tiling window managers, built with GTK4. It uses CEF by default, with WebKitGTK available as a fallback backend.
44
55 ## What is Dumber?
66
@@ -41,5 +41,5 @@ A browser designed for Wayland compositors that works like your favorite termina
4141 ## Requirements
4242
4343 - Linux with Wayland (Sway, Hyprland, River, Niri, etc.)
44-- GTK4 and WebKitGTK 2.42+
45-- GStreamer (for media playback)
44+- GTK4, CEF for the default backend, and WebKitGTK for the fallback backend
45+- GStreamer (for WebKit media playback and diagnostics)
@@ -1,6 +1,6 @@
1 # Dumber Documentation1 # Dumber Documentation
2 2
3-Dumber is a keyboard-driven browser for tiling window managers, built with GTK4 and WebKitGTK.3+Dumber is a keyboard-driven browser for tiling window managers, built with GTK4. It uses CEF by default, with WebKitGTK available as a fallback backend.
4 4
5 ## What is Dumber?5 ## What is Dumber?
6 6
@@ -41,5 +41,5 @@ A browser designed for Wayland compositors that works like your favorite termina
41 ## Requirements41 ## Requirements
42 42
43 - Linux with Wayland (Sway, Hyprland, River, Niri, etc.)43 - Linux with Wayland (Sway, Hyprland, River, Niri, etc.)
44-- GTK4 and WebKitGTK 2.42+44+- GTK4, CEF for the default backend, and WebKitGTK for the fallback backend
45-- GStreamer (for media playback)45+- GStreamer (for WebKit media playback and diagnostics)
M docs/installation.md +3 -2
@@ -51,8 +51,9 @@ make build
5151
5252 - Go 1.26+
5353 - GTK4 development libraries
54-- WebKitGTK 6.0 development libraries
55-- GStreamer development libraries
54+- CEF runtime for the default backend
55+- WebKitGTK 6.0 development/runtime libraries for the fallback backend and runtime checks
56+- GStreamer development libraries for WebKit media playback and diagnostics
5657 - Brotli for compressed systemviews assets
5758
5859 Systemviews assets are generated with `go tool templ` and Go's `js/wasm` toolchain; no root Node toolchain is required.
@@ -51,8 +51,9 @@ make build
51 51
52 - Go 1.26+52 - Go 1.26+
53 - GTK4 development libraries53 - GTK4 development libraries
54-- WebKitGTK 6.0 development libraries54+- CEF runtime for the default backend
55-- GStreamer development libraries55+- WebKitGTK 6.0 development/runtime libraries for the fallback backend and runtime checks
56+- GStreamer development libraries for WebKit media playback and diagnostics
56 - Brotli for compressed systemviews assets57 - Brotli for compressed systemviews assets
57 58
58 Systemviews assets are generated with `go tool templ` and Go's `js/wasm` toolchain; no root Node toolchain is required.59 Systemviews assets are generated with `go tool templ` and Go's `js/wasm` toolchain; no root Node toolchain is required.
M docs/reference/dependencies.md +3 -2
@@ -61,7 +61,7 @@ sudo pacman -S vulkan-radeon
6161 sudo pacman -S nvidia-utils # includes Vulkan
6262 ```
6363
64-### GStreamer Plugins (for WebKit Media Playback)
64+### GStreamer Plugins (for WebKit fallback media playback)
6565
6666 ```bash
6767 sudo pacman -S gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugins-ugly gst-libav
@@ -79,10 +79,11 @@ CEF is Dumber's default engine. It uses the GPU-first Wayland stack: GDK DMABUF
7979
8080 ## Debian/Ubuntu
8181
82-### Core GTK4 and WebKit
82+### Core GTK4, CEF, and WebKit fallback
8383
8484 ```bash
8585 sudo apt install libgtk-4-1 libwebkitgtk-6.0-4
86+# Also provide a CEF runtime for the default backend (package name varies by distribution).
8687 ```
8788
8889 ### VA-API
@@ -61,7 +61,7 @@ sudo pacman -S vulkan-radeon
61 sudo pacman -S nvidia-utils # includes Vulkan61 sudo pacman -S nvidia-utils # includes Vulkan
62 ```62 ```
63 63
64-### GStreamer Plugins (for WebKit Media Playback)64+### GStreamer Plugins (for WebKit fallback media playback)
65 65
66 ```bash66 ```bash
67 sudo pacman -S gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugins-ugly gst-libav67 sudo pacman -S gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugins-ugly gst-libav
@@ -79,10 +79,11 @@ CEF is Dumber's default engine. It uses the GPU-first Wayland stack: GDK DMABUF
79 79
80 ## Debian/Ubuntu80 ## Debian/Ubuntu
81 81
82-### Core GTK4 and WebKit82+### Core GTK4, CEF, and WebKit fallback
83 83
84 ```bash84 ```bash
85 sudo apt install libgtk-4-1 libwebkitgtk-6.0-485 sudo apt install libgtk-4-1 libwebkitgtk-6.0-4
86+# Also provide a CEF runtime for the default backend (package name varies by distribution).
86 ```87 ```
87 88
88 ### VA-API89 ### VA-API
M docs/reference/floating-pane.md +1 -1
@@ -53,5 +53,5 @@ desc = "Open Google in floating pane"
5353 - The default install only binds `Alt+F` for floating pane toggle.
5454 - Profile shortcuts are opt-in and empty by default.
5555 - Profile shortcuts support multi-modifier combos using `ctrl`, `shift`, and `alt` (for example `ctrl+shift+y` or `ctrl+alt+m`).
56-- Some `Alt+<key>` shortcuts can conflict with default WebKit shortcuts, website handlers, or your desktop environment.
56+- Some `Alt+<key>` shortcuts can conflict with browser-engine shortcuts, website handlers, or your desktop environment.
5757 - If a shortcut does not fire in Dumber, bind a different key.
@@ -53,5 +53,5 @@ desc = "Open Google in floating pane"
53 - The default install only binds `Alt+F` for floating pane toggle.53 - The default install only binds `Alt+F` for floating pane toggle.
54 - Profile shortcuts are opt-in and empty by default.54 - Profile shortcuts are opt-in and empty by default.
55 - Profile shortcuts support multi-modifier combos using `ctrl`, `shift`, and `alt` (for example `ctrl+shift+y` or `ctrl+alt+m`).55 - Profile shortcuts support multi-modifier combos using `ctrl`, `shift`, and `alt` (for example `ctrl+shift+y` or `ctrl+alt+m`).
56-- Some `Alt+<key>` shortcuts can conflict with default WebKit shortcuts, website handlers, or your desktop environment.56+- Some `Alt+<key>` shortcuts can conflict with browser-engine shortcuts, website handlers, or your desktop environment.
57 - If a shortcut does not fire in Dumber, bind a different key.57 - If a shortcut does not fire in Dumber, bind a different key.
M docs/reference/keybindings.md +1 -1
@@ -101,7 +101,7 @@ These work outside modal modes:
101101 - Any URL shortcut (for example `Alt+G`) must be defined explicitly in `workspace.floating_pane.profiles`.
102102 - Floating profile shortcuts support modifier combos with `ctrl`, `shift`, and `alt` (for example `ctrl+shift+y` or `ctrl+alt+m`).
103103
104-Warning: some `Alt+<key>` combinations may conflict with default WebKit shortcuts, website handlers, or your desktop environment.
104+Warning: some `Alt+<key>` combinations may conflict with browser-engine shortcuts, website handlers, or your desktop environment.
105105 If a shortcut does not trigger in Dumber, choose a different keybinding.
106106
107107 For details, see [Floating Pane](./floating-pane.md).
@@ -101,7 +101,7 @@ These work outside modal modes:
101 - Any URL shortcut (for example `Alt+G`) must be defined explicitly in `workspace.floating_pane.profiles`.101 - Any URL shortcut (for example `Alt+G`) must be defined explicitly in `workspace.floating_pane.profiles`.
102 - Floating profile shortcuts support modifier combos with `ctrl`, `shift`, and `alt` (for example `ctrl+shift+y` or `ctrl+alt+m`).102 - Floating profile shortcuts support modifier combos with `ctrl`, `shift`, and `alt` (for example `ctrl+shift+y` or `ctrl+alt+m`).
103 103
104-Warning: some `Alt+<key>` combinations may conflict with default WebKit shortcuts, website handlers, or your desktop environment.104+Warning: some `Alt+<key>` combinations may conflict with browser-engine shortcuts, website handlers, or your desktop environment.
105 If a shortcut does not trigger in Dumber, choose a different keybinding.105 If a shortcut does not trigger in Dumber, choose a different keybinding.
106 106
107 For details, see [Floating Pane](./floating-pane.md).107 For details, see [Floating Pane](./floating-pane.md).
M docs/reference/permissions.md +1 -1
@@ -10,7 +10,7 @@ These permissions are automatically granted without user interaction:
1010
1111 - **Display Capture** (screen sharing) - The XDG Desktop Portal handles the UI
1212
13- > ⚠️ **Known Issue**: Screen sharing does **not work** on Wayland with WebKitGTK 2.50.x due to a GStreamer caps negotiation failure ([WebKit bug #306440](https://bugs.webkit.org/show_bug.cgi?id=306440)). This was fixed on January 29, 2026 and backported to the 2.52 stable branch. **Workaround**: Upgrade to WebKitGTK 2.52+ when available from your distribution. Camera and microphone permissions work correctly.
13+ > ⚠️ **WebKit fallback known issue**: Screen sharing does **not work** on Wayland with WebKitGTK 2.50.x due to a GStreamer caps negotiation failure ([WebKit bug #306440](https://bugs.webkit.org/show_bug.cgi?id=306440)). This was fixed on January 29, 2026 and backported to the 2.52 stable branch. **Workaround for the WebKit backend**: Upgrade to WebKitGTK 2.52+ when available from your distribution. Camera and microphone permissions work correctly.
1414
1515 - **Device Enumeration** (listing microphones/cameras) - Low security risk
1616
@@ -10,7 +10,7 @@ These permissions are automatically granted without user interaction:
10 10
11 - **Display Capture** (screen sharing) - The XDG Desktop Portal handles the UI11 - **Display Capture** (screen sharing) - The XDG Desktop Portal handles the UI
12 12
13- > ⚠️ **Known Issue**: Screen sharing does **not work** on Wayland with WebKitGTK 2.50.x due to a GStreamer caps negotiation failure ([WebKit bug #306440](https://bugs.webkit.org/show_bug.cgi?id=306440)). This was fixed on January 29, 2026 and backported to the 2.52 stable branch. **Workaround**: Upgrade to WebKitGTK 2.52+ when available from your distribution. Camera and microphone permissions work correctly.13+ > ⚠️ **WebKit fallback known issue**: Screen sharing does **not work** on Wayland with WebKitGTK 2.50.x due to a GStreamer caps negotiation failure ([WebKit bug #306440](https://bugs.webkit.org/show_bug.cgi?id=306440)). This was fixed on January 29, 2026 and backported to the 2.52 stable branch. **Workaround for the WebKit backend**: Upgrade to WebKitGTK 2.52+ when available from your distribution. Camera and microphone permissions work correctly.
14 14
15 - **Device Enumeration** (listing microphones/cameras) - Low security risk15 - **Device Enumeration** (listing microphones/cameras) - Low security risk
16 16
M docs/reference/troubleshooting.md +7 -7
@@ -6,7 +6,7 @@ Run the doctor command to check your system:
66
77 ```bash
88 dumber doctor # Full check
9-dumber doctor --runtime # GTK4/WebKitGTK only
9+dumber doctor --runtime # GTK and WebKit fallback runtime checks only
1010 dumber doctor --media # GStreamer/VA-API only
1111 ```
1212
@@ -14,14 +14,14 @@ dumber doctor --media # GStreamer/VA-API only
1414
1515 ### Browser won't start
1616
17-**Symptoms:** Error about missing libraries or GTK/WebKitGTK version
17+**Symptoms:** Error about missing GUI runtime libraries or GTK/WebKitGTK fallback version
1818
1919 **Solution:**
2020 1. Run `dumber doctor --runtime`
2121 2. Install missing dependencies:
22- - Arch: `pacman -S webkitgtk-6.0 gtk4`
23- - Fedora: `dnf install webkitgtk6.0 gtk4`
24- - Ubuntu: `apt install libwebkitgtk-6.0-4 libgtk-4-1`
22+ - Arch: `pacman -S cef webkitgtk-6.0 gtk4`
23+ - Fedora: install GTK4 and WebKitGTK packages from your distribution and a CEF runtime/package if available
24+ - Ubuntu: `apt install libwebkitgtk-6.0-4 libgtk-4-1` and provide a CEF runtime for the default backend
2525
2626 ### Video playback issues
2727
@@ -58,9 +58,9 @@ dumber doctor --media # GStreamer/VA-API only
5858 gstreamer_debug_level = 0
5959 ```
6060
61-### Screen flickering on Wayland
61+### Screen flickering on Wayland with the WebKit fallback
6262
63-**Symptoms:** Flicker when scrolling or rendering
63+**Symptoms:** Flicker when scrolling or rendering while using `engine.type = "webkit"`
6464
6565 **Solution:**
6666 ```toml
@@ -6,7 +6,7 @@ Run the doctor command to check your system:
6 6
7 ```bash7 ```bash
8 dumber doctor # Full check8 dumber doctor # Full check
9-dumber doctor --runtime # GTK4/WebKitGTK only9+dumber doctor --runtime # GTK and WebKit fallback runtime checks only
10 dumber doctor --media # GStreamer/VA-API only10 dumber doctor --media # GStreamer/VA-API only
11 ```11 ```
12 12
@@ -14,14 +14,14 @@ dumber doctor --media # GStreamer/VA-API only
14 14
15 ### Browser won't start15 ### Browser won't start
16 16
17-**Symptoms:** Error about missing libraries or GTK/WebKitGTK version17+**Symptoms:** Error about missing GUI runtime libraries or GTK/WebKitGTK fallback version
18 18
19 **Solution:**19 **Solution:**
20 1. Run `dumber doctor --runtime`20 1. Run `dumber doctor --runtime`
21 2. Install missing dependencies:21 2. Install missing dependencies:
22- - Arch: `pacman -S webkitgtk-6.0 gtk4`22+ - Arch: `pacman -S cef webkitgtk-6.0 gtk4`
23- - Fedora: `dnf install webkitgtk6.0 gtk4`23+ - Fedora: install GTK4 and WebKitGTK packages from your distribution and a CEF runtime/package if available
24- - Ubuntu: `apt install libwebkitgtk-6.0-4 libgtk-4-1`24+ - Ubuntu: `apt install libwebkitgtk-6.0-4 libgtk-4-1` and provide a CEF runtime for the default backend
25 25
26 ### Video playback issues26 ### Video playback issues
27 27
@@ -58,9 +58,9 @@ dumber doctor --media # GStreamer/VA-API only
58 gstreamer_debug_level = 058 gstreamer_debug_level = 0
59 ```59 ```
60 60
61-### Screen flickering on Wayland61+### Screen flickering on Wayland with the WebKit fallback
62 62
63-**Symptoms:** Flicker when scrolling or rendering63+**Symptoms:** Flicker when scrolling or rendering while using `engine.type = "webkit"`
64 64
65 **Solution:**65 **Solution:**
66 ```toml66 ```toml
M internal/bootstrap/gui.go +1 -1
@@ -227,7 +227,7 @@ func RunDeferredInit(input DeferredInitInput) DeferredInitResult {
227227 }
228228 }
229229
230-// CheckRuntimeRequirements verifies WebKitGTK and other runtime dependencies.
230+// CheckRuntimeRequirements verifies GUI runtime dependencies currently covered by runtime checks.
231231 // Returns error if requirements are not met; caller should log details and exit.
232232 // Note: When running in a Flatpak sandbox, runtime checks are skipped because
233233 // the Flatpak runtime provides all required libraries.
@@ -227,7 +227,7 @@ func RunDeferredInit(input DeferredInitInput) DeferredInitResult {
227 }227 }
228 }228 }
229 229
230-// CheckRuntimeRequirements verifies WebKitGTK and other runtime dependencies.230+// CheckRuntimeRequirements verifies GUI runtime dependencies currently covered by runtime checks.
231 // Returns error if requirements are not met; caller should log details and exit.231 // Returns error if requirements are not met; caller should log details and exit.
232 // Note: When running in a Flatpak sandbox, runtime checks are skipped because232 // Note: When running in a Flatpak sandbox, runtime checks are skipped because
233 // the Flatpak runtime provides all required libraries.233 // the Flatpak runtime provides all required libraries.
M internal/cli/cmd/doctor.go +2 -2
@@ -22,7 +22,7 @@ var doctorCmd = &cobra.Command{
2222 Long: `Doctor checks system prerequisites for running the GUI browser.
2323
2424 By default it runs both:
25-- Runtime checks (GTK4 + WebKitGTK 6.0)
25+- Runtime checks for GTK and WebKit fallback libraries
2626 - Media checks (GStreamer/VA-API)
2727
2828 Use flags to run only one category.
@@ -36,7 +36,7 @@ Examples:
3636
3737 func init() {
3838 rootCmd.AddCommand(doctorCmd)
39- doctorCmd.Flags().BoolVar(&doctorOnlyRuntime, "runtime", false, "Only run runtime checks (GTK4/WebKitGTK)")
39+ doctorCmd.Flags().BoolVar(&doctorOnlyRuntime, "runtime", false, "Only run runtime checks for GTK and WebKit fallback libraries")
4040 doctorCmd.Flags().BoolVar(&doctorOnlyMedia, "media", false, "Only run media checks (GStreamer/VA-API)")
4141 }
4242 func runDoctor(_ *cobra.Command, _ []string) error {
@@ -22,7 +22,7 @@ var doctorCmd = &cobra.Command{
22 Long: `Doctor checks system prerequisites for running the GUI browser.22 Long: `Doctor checks system prerequisites for running the GUI browser.
23 23
24 By default it runs both:24 By default it runs both:
25-- Runtime checks (GTK4 + WebKitGTK 6.0)25+- Runtime checks for GTK and WebKit fallback libraries
26 - Media checks (GStreamer/VA-API)26 - Media checks (GStreamer/VA-API)
27 27
28 Use flags to run only one category.28 Use flags to run only one category.
@@ -36,7 +36,7 @@ Examples:
36 36
37 func init() {37 func init() {
38 rootCmd.AddCommand(doctorCmd)38 rootCmd.AddCommand(doctorCmd)
39- doctorCmd.Flags().BoolVar(&doctorOnlyRuntime, "runtime", false, "Only run runtime checks (GTK4/WebKitGTK)")39+ doctorCmd.Flags().BoolVar(&doctorOnlyRuntime, "runtime", false, "Only run runtime checks for GTK and WebKit fallback libraries")
40 doctorCmd.Flags().BoolVar(&doctorOnlyMedia, "media", false, "Only run media checks (GStreamer/VA-API)")40 doctorCmd.Flags().BoolVar(&doctorOnlyMedia, "media", false, "Only run media checks (GStreamer/VA-API)")
41 }41 }
42 func runDoctor(_ *cobra.Command, _ []string) error {42 func runDoctor(_ *cobra.Command, _ []string) error {
M internal/cli/cmd/root.go +1 -1
@@ -22,7 +22,7 @@ var (
2222 SilenceUsage: true,
2323 Long: `Dumber - a dumb browser that works like your favorite terminal multiplexer.
2424
25-A fully unfeatured unbloated browser for tiling WMs, built with GTK4 and WebKitGTK.
25+A fully unfeatured unbloated browser for tiling WMs, built with GTK4. CEF is the default backend; WebKitGTK is available as a fallback.
2626
2727 Features:
2828 - Wayland native (Sway, Hyprland, River, Niri, etc.)
@@ -22,7 +22,7 @@ var (
22 SilenceUsage: true,22 SilenceUsage: true,
23 Long: `Dumber - a dumb browser that works like your favorite terminal multiplexer.23 Long: `Dumber - a dumb browser that works like your favorite terminal multiplexer.
24 24
25-A fully unfeatured unbloated browser for tiling WMs, built with GTK4 and WebKitGTK.25+A fully unfeatured unbloated browser for tiling WMs, built with GTK4. CEF is the default backend; WebKitGTK is available as a fallback.
26 26
27 Features:27 Features:
28 - Wayland native (Sway, Hyprland, River, Niri, etc.)28 - Wayland native (Sway, Hyprland, River, Niri, etc.)
M internal/infrastructure/gtkutil/scale.go +1 -13
@@ -1,10 +1,6 @@
11 package gtkutil
22
3-import (
4- "math"
5-
6- "github.com/bnema/puregotk/v4/gtk"
7-)
3+import "math"
84
95 // NormalizeScale clamps invalid GTK/GDK scale values to 1.
106 func NormalizeScale(scale float64) float64 {
@@ -14,14 +10,6 @@ func NormalizeScale(scale float64) float64 {
1410 return scale
1511 }
1612
17-// WidgetFromNativePointer wraps a native GTK widget pointer.
18-func WidgetFromNativePointer(ptr uintptr) *gtk.Widget {
19- if ptr == 0 {
20- return nil
21- }
22- return gtk.WidgetNewFromInternalPtr(ptr)
23-}
24-
2513 // DeviceToLogical converts a device-pixel coordinate into a logical coordinate.
2614 func DeviceToLogical(value int32, scale float64) int32 {
2715 return int32(math.Floor(float64(value) / NormalizeScale(scale)))
@@ -1,10 +1,6 @@
1 package gtkutil1 package gtkutil
2 2
3-import (3+import "math"
4- "math"
5-
6- "github.com/bnema/puregotk/v4/gtk"
7-)
8 4
9 // NormalizeScale clamps invalid GTK/GDK scale values to 1.5 // NormalizeScale clamps invalid GTK/GDK scale values to 1.
10 func NormalizeScale(scale float64) float64 {6 func NormalizeScale(scale float64) float64 {
@@ -14,14 +10,6 @@ func NormalizeScale(scale float64) float64 {
14 return scale10 return scale
15 }11 }
16 12
17-// WidgetFromNativePointer wraps a native GTK widget pointer.
18-func WidgetFromNativePointer(ptr uintptr) *gtk.Widget {
19- if ptr == 0 {
20- return nil
21- }
22- return gtk.WidgetNewFromInternalPtr(ptr)
23-}
24-
25 // DeviceToLogical converts a device-pixel coordinate into a logical coordinate.13 // DeviceToLogical converts a device-pixel coordinate into a logical coordinate.
26 func DeviceToLogical(value int32, scale float64) int32 {14 func DeviceToLogical(value int32, scale float64) int32 {
27 return int32(math.Floor(float64(value) / NormalizeScale(scale)))15 return int32(math.Floor(float64(value) / NormalizeScale(scale)))
M internal/infrastructure/updater/github.go +2 -8
@@ -275,14 +275,8 @@ func isRetryableRequestError(err error) bool {
275275 }
276276
277277 var netErr net.Error
278- if errors.As(err, &netErr) {
279- if netErr.Timeout() {
280- return true
281- }
282- //nolint:staticcheck // net.Error.Temporary is deprecated but still useful for transient detection
283- if netErr.Temporary() {
284- return true
285- }
278+ if errors.As(err, &netErr) && netErr.Timeout() {
279+ return true
286280 }
287281
288282 var opErr *net.OpError
@@ -275,14 +275,8 @@ func isRetryableRequestError(err error) bool {
275 }275 }
276 276
277 var netErr net.Error277 var netErr net.Error
278- if errors.As(err, &netErr) {278+ if errors.As(err, &netErr) && netErr.Timeout() {
279- if netErr.Timeout() {279+ return true
280- return true
281- }
282- //nolint:staticcheck // net.Error.Temporary is deprecated but still useful for transient detection
283- if netErr.Temporary() {
284- return true
285- }
286 }280 }
287 281
288 var opErr *net.OpError282 var opErr *net.OpError
M internal/infrastructure/updater/github_test.go +27 -0
@@ -6,11 +6,13 @@ import (
66 "encoding/hex"
77 "errors"
88 "io"
9+ "net"
910 "net/http"
1011 "os"
1112 "path/filepath"
1213 "strings"
1314 "sync/atomic"
15+ "syscall"
1416 "testing"
1517 "time"
1618
@@ -393,6 +395,31 @@ func TestRetryDelayForAttemptAddsJitter(t *testing.T) {
393395 }
394396 }
395397
398+func TestIsRetryableRequestError(t *testing.T) {
399+ tests := []struct {
400+ name string
401+ err error
402+ want bool
403+ }{
404+ {name: "nil", err: nil, want: false},
405+ {name: "context canceled", err: context.Canceled, want: false},
406+ {name: "context deadline exceeded", err: context.DeadlineExceeded, want: true},
407+ {name: "timeout net error", err: &net.DNSError{IsTimeout: true}, want: true},
408+ {name: "non-timeout net error", err: &net.DNSError{Err: "lookup failed"}, want: false},
409+ {name: "transient errno through net op error", err: &net.OpError{Op: "read", Net: "tcp", Err: syscall.ECONNRESET}, want: true},
410+ {name: "non-transient errno", err: syscall.EINVAL, want: false},
411+ {name: "ordinary error", err: errors.New("ordinary"), want: false},
412+ }
413+
414+ for _, tt := range tests {
415+ t.Run(tt.name, func(t *testing.T) {
416+ if got := isRetryableRequestError(tt.err); got != tt.want {
417+ t.Fatalf("isRetryableRequestError(%v) = %v, want %v", tt.err, got, tt.want)
418+ }
419+ })
420+ }
421+}
422+
396423 func TestGitHubCheckerDoRequestWithRetry_RetryableStatus(t *testing.T) {
397424 var calls int32
398425 tr := roundTripFunc(func(_ *http.Request) (*http.Response, error) {
@@ -6,11 +6,13 @@ import (
6 "encoding/hex"6 "encoding/hex"
7 "errors"7 "errors"
8 "io"8 "io"
9+ "net"
9 "net/http"10 "net/http"
10 "os"11 "os"
11 "path/filepath"12 "path/filepath"
12 "strings"13 "strings"
13 "sync/atomic"14 "sync/atomic"
15+ "syscall"
14 "testing"16 "testing"
15 "time"17 "time"
16 18
@@ -393,6 +395,31 @@ func TestRetryDelayForAttemptAddsJitter(t *testing.T) {
393 }395 }
394 }396 }
395 397
398+func TestIsRetryableRequestError(t *testing.T) {
399+ tests := []struct {
400+ name string
401+ err error
402+ want bool
403+ }{
404+ {name: "nil", err: nil, want: false},
405+ {name: "context canceled", err: context.Canceled, want: false},
406+ {name: "context deadline exceeded", err: context.DeadlineExceeded, want: true},
407+ {name: "timeout net error", err: &net.DNSError{IsTimeout: true}, want: true},
408+ {name: "non-timeout net error", err: &net.DNSError{Err: "lookup failed"}, want: false},
409+ {name: "transient errno through net op error", err: &net.OpError{Op: "read", Net: "tcp", Err: syscall.ECONNRESET}, want: true},
410+ {name: "non-transient errno", err: syscall.EINVAL, want: false},
411+ {name: "ordinary error", err: errors.New("ordinary"), want: false},
412+ }
413+
414+ for _, tt := range tests {
415+ t.Run(tt.name, func(t *testing.T) {
416+ if got := isRetryableRequestError(tt.err); got != tt.want {
417+ t.Fatalf("isRetryableRequestError(%v) = %v, want %v", tt.err, got, tt.want)
418+ }
419+ })
420+ }
421+}
422+
396 func TestGitHubCheckerDoRequestWithRetry_RetryableStatus(t *testing.T) {423 func TestGitHubCheckerDoRequestWithRetry_RetryableStatus(t *testing.T) {
397 var calls int32424 var calls int32
398 tr := roundTripFunc(func(_ *http.Request) (*http.Response, error) {425 tr := roundTripFunc(func(_ *http.Request) (*http.Response, error) {
M internal/ui/coordinator/content/lifecycle.go +1 -6
@@ -6,7 +6,6 @@ import (
66
77 "github.com/bnema/dumber/internal/application/port"
88 "github.com/bnema/dumber/internal/domain/entity"
9- "github.com/bnema/dumber/internal/infrastructure/gtkutil"
109 "github.com/bnema/dumber/internal/logging"
1110 "github.com/bnema/dumber/internal/ui/component"
1211 "github.com/bnema/dumber/internal/ui/input"
@@ -167,11 +166,7 @@ func (c *Coordinator) WrapWidget(ctx context.Context, wv port.WebView) layout.Wi
167166 return nil
168167 }
169168
170- gtkWidget := gtkutil.WidgetFromNativePointer(ptr)
171- if gtkWidget == nil {
172- return nil
173- }
174- widget := c.widgetFactory.WrapWidget(gtkWidget)
169+ widget := c.widgetFactory.WrapNativeWidget(ptr)
175170
176171 // Attach gesture handler for mouse button 8/9 navigation
177172 if widget != nil {
@@ -6,7 +6,6 @@ import (
6 6
7 "github.com/bnema/dumber/internal/application/port"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/gtkutil"
10 "github.com/bnema/dumber/internal/logging"9 "github.com/bnema/dumber/internal/logging"
11 "github.com/bnema/dumber/internal/ui/component"10 "github.com/bnema/dumber/internal/ui/component"
12 "github.com/bnema/dumber/internal/ui/input"11 "github.com/bnema/dumber/internal/ui/input"
@@ -167,11 +166,7 @@ func (c *Coordinator) WrapWidget(ctx context.Context, wv port.WebView) layout.Wi
167 return nil166 return nil
168 }167 }
169 168
170- gtkWidget := gtkutil.WidgetFromNativePointer(ptr)169+ widget := c.widgetFactory.WrapNativeWidget(ptr)
171- if gtkWidget == nil {
172- return nil
173- }
174- widget := c.widgetFactory.WrapWidget(gtkWidget)
175 170
176 // Attach gesture handler for mouse button 8/9 navigation171 // Attach gesture handler for mouse button 8/9 navigation
177 if widget != nil {172 if widget != nil {
M internal/ui/coordinator/content/lifecycle_test.go +70 -0
@@ -348,6 +348,76 @@ func TestLifecycle_RegisterPopupWebView_IgnoresNil(t *testing.T) {
348348 assert.Nil(t, c.GetWebView("popup-1"))
349349 }
350350
351+type nativeWebViewStub struct {
352+ port.WebView
353+ ptr uintptr
354+}
355+
356+func (s *nativeWebViewStub) NativeWidget() uintptr {
357+ return s.ptr
358+}
359+
360+func TestLifecycle_WrapWidget_ReturnsNilForNilWebView(t *testing.T) {
361+ t.Parallel()
362+
363+ factory := layoutmocks.NewMockWidgetFactory(t)
364+ c := newMinimalCoordinator()
365+ c.widgetFactory = factory
366+
367+ assert.Nil(t, c.WrapWidget(context.Background(), nil))
368+}
369+
370+func TestLifecycle_WrapWidget_ReturnsNilForMissingNativeProvider(t *testing.T) {
371+ t.Parallel()
372+
373+ wv := mocks.NewMockWebView(t)
374+ factory := layoutmocks.NewMockWidgetFactory(t)
375+ c := newMinimalCoordinator()
376+ c.widgetFactory = factory
377+
378+ assert.Nil(t, c.WrapWidget(context.Background(), wv))
379+}
380+
381+func TestLifecycle_WrapWidget_ReturnsNilForZeroNativePointer(t *testing.T) {
382+ t.Parallel()
383+
384+ base := mocks.NewMockWebView(t)
385+ wv := &nativeWebViewStub{WebView: base}
386+ factory := layoutmocks.NewMockWidgetFactory(t)
387+ c := newMinimalCoordinator()
388+ c.widgetFactory = factory
389+
390+ assert.Nil(t, c.WrapWidget(context.Background(), wv))
391+}
392+
393+func TestLifecycle_WrapWidget_ReturnsNilForNilWrappedWidget(t *testing.T) {
394+ t.Parallel()
395+
396+ base := mocks.NewMockWebView(t)
397+ wv := &nativeWebViewStub{WebView: base, ptr: 123}
398+ factory := layoutmocks.NewMockWidgetFactory(t)
399+ factory.EXPECT().WrapNativeWidget(uintptr(123)).Return(nil).Once()
400+ c := newMinimalCoordinator()
401+ c.widgetFactory = factory
402+
403+ assert.Nil(t, c.WrapWidget(context.Background(), wv))
404+}
405+
406+func TestLifecycle_WrapWidget_RequestsGtkWidgetForGestureSetup(t *testing.T) {
407+ t.Parallel()
408+
409+ base := mocks.NewMockWebView(t)
410+ wv := &nativeWebViewStub{WebView: base, ptr: 456}
411+ factory := layoutmocks.NewMockWidgetFactory(t)
412+ widget := layoutmocks.NewMockWidget(t)
413+ factory.EXPECT().WrapNativeWidget(uintptr(456)).Return(widget).Once()
414+ widget.EXPECT().GtkWidget().Return(nil).Once()
415+ c := newMinimalCoordinator()
416+ c.widgetFactory = factory
417+
418+ assert.Same(t, widget, c.WrapWidget(context.Background(), wv))
419+}
420+
351421 type syncViewportContextKey struct{}
352422
353423 type syncViewportCapableStub struct {
@@ -348,6 +348,76 @@ func TestLifecycle_RegisterPopupWebView_IgnoresNil(t *testing.T) {
348 assert.Nil(t, c.GetWebView("popup-1"))348 assert.Nil(t, c.GetWebView("popup-1"))
349 }349 }
350 350
351+type nativeWebViewStub struct {
352+ port.WebView
353+ ptr uintptr
354+}
355+
356+func (s *nativeWebViewStub) NativeWidget() uintptr {
357+ return s.ptr
358+}
359+
360+func TestLifecycle_WrapWidget_ReturnsNilForNilWebView(t *testing.T) {
361+ t.Parallel()
362+
363+ factory := layoutmocks.NewMockWidgetFactory(t)
364+ c := newMinimalCoordinator()
365+ c.widgetFactory = factory
366+
367+ assert.Nil(t, c.WrapWidget(context.Background(), nil))
368+}
369+
370+func TestLifecycle_WrapWidget_ReturnsNilForMissingNativeProvider(t *testing.T) {
371+ t.Parallel()
372+
373+ wv := mocks.NewMockWebView(t)
374+ factory := layoutmocks.NewMockWidgetFactory(t)
375+ c := newMinimalCoordinator()
376+ c.widgetFactory = factory
377+
378+ assert.Nil(t, c.WrapWidget(context.Background(), wv))
379+}
380+
381+func TestLifecycle_WrapWidget_ReturnsNilForZeroNativePointer(t *testing.T) {
382+ t.Parallel()
383+
384+ base := mocks.NewMockWebView(t)
385+ wv := &nativeWebViewStub{WebView: base}
386+ factory := layoutmocks.NewMockWidgetFactory(t)
387+ c := newMinimalCoordinator()
388+ c.widgetFactory = factory
389+
390+ assert.Nil(t, c.WrapWidget(context.Background(), wv))
391+}
392+
393+func TestLifecycle_WrapWidget_ReturnsNilForNilWrappedWidget(t *testing.T) {
394+ t.Parallel()
395+
396+ base := mocks.NewMockWebView(t)
397+ wv := &nativeWebViewStub{WebView: base, ptr: 123}
398+ factory := layoutmocks.NewMockWidgetFactory(t)
399+ factory.EXPECT().WrapNativeWidget(uintptr(123)).Return(nil).Once()
400+ c := newMinimalCoordinator()
401+ c.widgetFactory = factory
402+
403+ assert.Nil(t, c.WrapWidget(context.Background(), wv))
404+}
405+
406+func TestLifecycle_WrapWidget_RequestsGtkWidgetForGestureSetup(t *testing.T) {
407+ t.Parallel()
408+
409+ base := mocks.NewMockWebView(t)
410+ wv := &nativeWebViewStub{WebView: base, ptr: 456}
411+ factory := layoutmocks.NewMockWidgetFactory(t)
412+ widget := layoutmocks.NewMockWidget(t)
413+ factory.EXPECT().WrapNativeWidget(uintptr(456)).Return(widget).Once()
414+ widget.EXPECT().GtkWidget().Return(nil).Once()
415+ c := newMinimalCoordinator()
416+ c.widgetFactory = factory
417+
418+ assert.Same(t, widget, c.WrapWidget(context.Background(), wv))
419+}
420+
351 type syncViewportContextKey struct{}421 type syncViewportContextKey struct{}
352 422
353 type syncViewportCapableStub struct {423 type syncViewportCapableStub struct {
M internal/ui/layout/gtk_widgets.go +7 -0
@@ -827,3 +827,10 @@ func (f *GtkWidgetFactory) WrapWidget(w *gtk.Widget) Widget {
827827 }
828828 return &gtkWidget{inner: w}
829829 }
830+
831+func (f *GtkWidgetFactory) WrapNativeWidget(ptr uintptr) Widget {
832+ if ptr == 0 {
833+ return nil
834+ }
835+ return f.WrapWidget(gtk.WidgetNewFromInternalPtr(ptr))
836+}
@@ -827,3 +827,10 @@ func (f *GtkWidgetFactory) WrapWidget(w *gtk.Widget) Widget {
827 }827 }
828 return &gtkWidget{inner: w}828 return &gtkWidget{inner: w}
829 }829 }
830+
831+func (f *GtkWidgetFactory) WrapNativeWidget(ptr uintptr) Widget {
832+ if ptr == 0 {
833+ return nil
834+ }
835+ return f.WrapWidget(gtk.WidgetNewFromInternalPtr(ptr))
836+}
M internal/ui/layout/mocks/widgets.go +53 -0
@@ -13898,6 +13898,59 @@ func (_c *MockWidgetFactory_NewSpinner_Call) RunAndReturn(run func() layout.Spin
1389813898 return _c
1389913899 }
1390013900
13901+// WrapNativeWidget provides a mock function for the type MockWidgetFactory
13902+func (_mock *MockWidgetFactory) WrapNativeWidget(ptr uintptr) layout.Widget {
13903+ ret := _mock.Called(ptr)
13904+
13905+ if len(ret) == 0 {
13906+ panic("no return value specified for WrapNativeWidget")
13907+ }
13908+
13909+ var r0 layout.Widget
13910+ if returnFunc, ok := ret.Get(0).(func(uintptr) layout.Widget); ok {
13911+ r0 = returnFunc(ptr)
13912+ } else {
13913+ if ret.Get(0) != nil {
13914+ r0 = ret.Get(0).(layout.Widget)
13915+ }
13916+ }
13917+ return r0
13918+}
13919+
13920+// MockWidgetFactory_WrapNativeWidget_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WrapNativeWidget'
13921+type MockWidgetFactory_WrapNativeWidget_Call struct {
13922+ *mock.Call
13923+}
13924+
13925+// WrapNativeWidget is a helper method to define mock.On call
13926+// - ptr uintptr
13927+func (_e *MockWidgetFactory_Expecter) WrapNativeWidget(ptr any) *MockWidgetFactory_WrapNativeWidget_Call {
13928+ return &MockWidgetFactory_WrapNativeWidget_Call{Call: _e.mock.On("WrapNativeWidget", ptr)}
13929+}
13930+
13931+func (_c *MockWidgetFactory_WrapNativeWidget_Call) Run(run func(ptr uintptr)) *MockWidgetFactory_WrapNativeWidget_Call {
13932+ _c.Call.Run(func(args mock.Arguments) {
13933+ var arg0 uintptr
13934+ if args[0] != nil {
13935+ arg0 = args[0].(uintptr)
13936+ }
13937+ run(
13938+ arg0,
13939+ )
13940+ })
13941+ return _c
13942+}
13943+
13944+func (_c *MockWidgetFactory_WrapNativeWidget_Call) Return(widget layout.Widget) *MockWidgetFactory_WrapNativeWidget_Call {
13945+ _c.Call.Return(widget)
13946+ return _c
13947+}
13948+
13949+func (_c *MockWidgetFactory_WrapNativeWidget_Call) RunAndReturn(run func(ptr uintptr) layout.Widget) *MockWidgetFactory_WrapNativeWidget_Call {
13950+ _c.Call.Return(run)
13951+ return _c
13952+}
13953+
1390113954 // WrapWidget provides a mock function for the type MockWidgetFactory
1390213955 func (_mock *MockWidgetFactory) WrapWidget(w *gtk.Widget) layout.Widget {
1390313956 ret := _mock.Called(w)
@@ -13898,6 +13898,59 @@ func (_c *MockWidgetFactory_NewSpinner_Call) RunAndReturn(run func() layout.Spin
13898 return _c13898 return _c
13899 }13899 }
13900 13900
13901+// WrapNativeWidget provides a mock function for the type MockWidgetFactory
13902+func (_mock *MockWidgetFactory) WrapNativeWidget(ptr uintptr) layout.Widget {
13903+ ret := _mock.Called(ptr)
13904+
13905+ if len(ret) == 0 {
13906+ panic("no return value specified for WrapNativeWidget")
13907+ }
13908+
13909+ var r0 layout.Widget
13910+ if returnFunc, ok := ret.Get(0).(func(uintptr) layout.Widget); ok {
13911+ r0 = returnFunc(ptr)
13912+ } else {
13913+ if ret.Get(0) != nil {
13914+ r0 = ret.Get(0).(layout.Widget)
13915+ }
13916+ }
13917+ return r0
13918+}
13919+
13920+// MockWidgetFactory_WrapNativeWidget_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WrapNativeWidget'
13921+type MockWidgetFactory_WrapNativeWidget_Call struct {
13922+ *mock.Call
13923+}
13924+
13925+// WrapNativeWidget is a helper method to define mock.On call
13926+// - ptr uintptr
13927+func (_e *MockWidgetFactory_Expecter) WrapNativeWidget(ptr any) *MockWidgetFactory_WrapNativeWidget_Call {
13928+ return &MockWidgetFactory_WrapNativeWidget_Call{Call: _e.mock.On("WrapNativeWidget", ptr)}
13929+}
13930+
13931+func (_c *MockWidgetFactory_WrapNativeWidget_Call) Run(run func(ptr uintptr)) *MockWidgetFactory_WrapNativeWidget_Call {
13932+ _c.Call.Run(func(args mock.Arguments) {
13933+ var arg0 uintptr
13934+ if args[0] != nil {
13935+ arg0 = args[0].(uintptr)
13936+ }
13937+ run(
13938+ arg0,
13939+ )
13940+ })
13941+ return _c
13942+}
13943+
13944+func (_c *MockWidgetFactory_WrapNativeWidget_Call) Return(widget layout.Widget) *MockWidgetFactory_WrapNativeWidget_Call {
13945+ _c.Call.Return(widget)
13946+ return _c
13947+}
13948+
13949+func (_c *MockWidgetFactory_WrapNativeWidget_Call) RunAndReturn(run func(ptr uintptr) layout.Widget) *MockWidgetFactory_WrapNativeWidget_Call {
13950+ _c.Call.Return(run)
13951+ return _c
13952+}
13953+
13901 // WrapWidget provides a mock function for the type MockWidgetFactory13954 // WrapWidget provides a mock function for the type MockWidgetFactory
13902 func (_mock *MockWidgetFactory) WrapWidget(w *gtk.Widget) layout.Widget {13955 func (_mock *MockWidgetFactory) WrapWidget(w *gtk.Widget) layout.Widget {
13903 ret := _mock.Called(w)13956 ret := _mock.Called(w)
M internal/ui/layout/widgets.go +1 -0
@@ -241,6 +241,7 @@ type WidgetFactory interface {
241241
242242 // Wrap existing GTK widget
243243 WrapWidget(w *gtk.Widget) Widget
244+ WrapNativeWidget(ptr uintptr) Widget
244245 }
245246
246247 // PaneRenderer creates pane view widgets from domain entities.
@@ -241,6 +241,7 @@ type WidgetFactory interface {
241 241
242 // Wrap existing GTK widget242 // Wrap existing GTK widget
243 WrapWidget(w *gtk.Widget) Widget243 WrapWidget(w *gtk.Widget) Widget
244+ WrapNativeWidget(ptr uintptr) Widget
244 }245 }
245 246
246 // PaneRenderer creates pane view widgets from domain entities.247 // PaneRenderer creates pane view widgets from domain entities.

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