detached 3b3765d

fix(cef): stabilize standalone omnibox startup

commit
3b3765d837e680e751252a3ece2d268483665b14
author
brice · · 2026-04-09 03:29 UTC
parent
f0280fa
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
10 files · +88 -15
M cmd/dumber/main.go +18 -5
@@ -147,13 +147,10 @@ func runGUI() int {
147147 }
148148 timer.MarkDuration("parallel_phase", initResult.Duration)
149149
150- if cfg.Engine.ResolveEngineType() == config.EngineTypeCEF {
150+ preInitializeAdwaitaForCEF(cfg, initResult, func() {
151151 logging.FromContext(ctx).Info().Msg("pre-initializing libadwaita before CEF multi-threaded loop")
152152 ui.EnsureAdwaitaInitialized()
153- if initResult.AdwaitaDetector != nil {
154- initResult.AdwaitaDetector.MarkAvailable()
155- }
156- }
153+ })
157154
158155 needsEagerDB := restoreSessionID != "" || cfg.Session.AutoRestore
159156
@@ -213,6 +210,8 @@ func runStandaloneOmnibox() int {
213210 return 1
214211 }
215212
213+ preInitializeAdwaitaForCEF(cfg, initResult, ui.EnsureAdwaitaInitialized)
214+
216215 engine, repos, dbCleanup, err := initStackAndRepos(ctx, cfg, initResult, false)
217216 if err != nil {
218217 logging.FromContext(ctx).Error().Err(err).Msg("failed to initialize standalone omnibox runtime")
@@ -245,6 +244,20 @@ func runStandaloneOmnibox() int {
245244 return ui.RunStandaloneOmnibox(ctx, runtimeCfg)
246245 }
247246
247+func preInitializeAdwaitaForCEF(cfg *config.Config, initResult *bootstrap.ParallelInitResult, initAdwaita func()) {
248+ if cfg == nil || initResult == nil || initAdwaita == nil {
249+ return
250+ }
251+ if cfg.Engine.ResolveEngineType() != config.EngineTypeCEF {
252+ return
253+ }
254+
255+ initAdwaita()
256+ if initResult.AdwaitaDetector != nil {
257+ initResult.AdwaitaDetector.MarkAvailable()
258+ }
259+}
260+
248261 func maybeReexecStandaloneOmniboxWithLayerShell() {
249262 env := bootstrap.CurrentEnvMap()
250263 if !bootstrap.ShouldPreloadLayerShell(env) {
@@ -147,13 +147,10 @@ func runGUI() int {
147 }147 }
148 timer.MarkDuration("parallel_phase", initResult.Duration)148 timer.MarkDuration("parallel_phase", initResult.Duration)
149 149
150- if cfg.Engine.ResolveEngineType() == config.EngineTypeCEF {150+ preInitializeAdwaitaForCEF(cfg, initResult, func() {
151 logging.FromContext(ctx).Info().Msg("pre-initializing libadwaita before CEF multi-threaded loop")151 logging.FromContext(ctx).Info().Msg("pre-initializing libadwaita before CEF multi-threaded loop")
152 ui.EnsureAdwaitaInitialized()152 ui.EnsureAdwaitaInitialized()
153- if initResult.AdwaitaDetector != nil {153+ })
154- initResult.AdwaitaDetector.MarkAvailable()
155- }
156- }
157 154
158 needsEagerDB := restoreSessionID != "" || cfg.Session.AutoRestore155 needsEagerDB := restoreSessionID != "" || cfg.Session.AutoRestore
159 156
@@ -213,6 +210,8 @@ func runStandaloneOmnibox() int {
213 return 1210 return 1
214 }211 }
215 212
213+ preInitializeAdwaitaForCEF(cfg, initResult, ui.EnsureAdwaitaInitialized)
214+
216 engine, repos, dbCleanup, err := initStackAndRepos(ctx, cfg, initResult, false)215 engine, repos, dbCleanup, err := initStackAndRepos(ctx, cfg, initResult, false)
217 if err != nil {216 if err != nil {
218 logging.FromContext(ctx).Error().Err(err).Msg("failed to initialize standalone omnibox runtime")217 logging.FromContext(ctx).Error().Err(err).Msg("failed to initialize standalone omnibox runtime")
@@ -245,6 +244,20 @@ func runStandaloneOmnibox() int {
245 return ui.RunStandaloneOmnibox(ctx, runtimeCfg)244 return ui.RunStandaloneOmnibox(ctx, runtimeCfg)
246 }245 }
247 246
247+func preInitializeAdwaitaForCEF(cfg *config.Config, initResult *bootstrap.ParallelInitResult, initAdwaita func()) {
248+ if cfg == nil || initResult == nil || initAdwaita == nil {
249+ return
250+ }
251+ if cfg.Engine.ResolveEngineType() != config.EngineTypeCEF {
252+ return
253+ }
254+
255+ initAdwaita()
256+ if initResult.AdwaitaDetector != nil {
257+ initResult.AdwaitaDetector.MarkAvailable()
258+ }
259+}
260+
248 func maybeReexecStandaloneOmniboxWithLayerShell() {261 func maybeReexecStandaloneOmniboxWithLayerShell() {
249 env := bootstrap.CurrentEnvMap()262 env := bootstrap.CurrentEnvMap()
250 if !bootstrap.ShouldPreloadLayerShell(env) {263 if !bootstrap.ShouldPreloadLayerShell(env) {
M cmd/dumber/main_test.go +44 -0
@@ -3,6 +3,10 @@ package main
33 import (
44 "errors"
55 "testing"
6+
7+ "github.com/bnema/dumber/internal/bootstrap"
8+ "github.com/bnema/dumber/internal/infrastructure/colorscheme"
9+ "github.com/bnema/dumber/internal/infrastructure/config"
610 )
711
812 func TestLaunchModeFromArgs_DetectsStandaloneOmnibox(t *testing.T) {
@@ -87,3 +91,43 @@ func TestResolveCurrentExecutable_PropagatesError(t *testing.T) {
8791 t.Fatalf("expected error %v, got %v", wantErr, err)
8892 }
8993 }
94+
95+func TestPreInitializeAdwaitaForCEF_InitializesAndMarksDetector(t *testing.T) {
96+ initResult := &bootstrap.ParallelInitResult{
97+ AdwaitaDetector: colorscheme.NewAdwaitaDetector(),
98+ }
99+ cfg := &config.Config{}
100+ cfg.Engine.Type = config.EngineTypeCEF
101+
102+ called := false
103+ preInitializeAdwaitaForCEF(cfg, initResult, func() {
104+ called = true
105+ })
106+
107+ if !called {
108+ t.Fatal("expected libadwaita initialization for CEF")
109+ }
110+ if !initResult.AdwaitaDetector.Available() {
111+ t.Fatal("expected adwaita detector to be marked available")
112+ }
113+}
114+
115+func TestPreInitializeAdwaitaForCEF_SkipsNonCEF(t *testing.T) {
116+ initResult := &bootstrap.ParallelInitResult{
117+ AdwaitaDetector: colorscheme.NewAdwaitaDetector(),
118+ }
119+ cfg := &config.Config{}
120+ cfg.Engine.Type = config.EngineTypeWebKit
121+
122+ called := false
123+ preInitializeAdwaitaForCEF(cfg, initResult, func() {
124+ called = true
125+ })
126+
127+ if called {
128+ t.Fatal("expected libadwaita initialization to be skipped for non-CEF")
129+ }
130+ if initResult.AdwaitaDetector.Available() {
131+ t.Fatal("expected adwaita detector to remain unavailable")
132+ }
133+}
@@ -3,6 +3,10 @@ package main
3 import (3 import (
4 "errors"4 "errors"
5 "testing"5 "testing"
6+
7+ "github.com/bnema/dumber/internal/bootstrap"
8+ "github.com/bnema/dumber/internal/infrastructure/colorscheme"
9+ "github.com/bnema/dumber/internal/infrastructure/config"
6 )10 )
7 11
8 func TestLaunchModeFromArgs_DetectsStandaloneOmnibox(t *testing.T) {12 func TestLaunchModeFromArgs_DetectsStandaloneOmnibox(t *testing.T) {
@@ -87,3 +91,43 @@ func TestResolveCurrentExecutable_PropagatesError(t *testing.T) {
87 t.Fatalf("expected error %v, got %v", wantErr, err)91 t.Fatalf("expected error %v, got %v", wantErr, err)
88 }92 }
89 }93 }
94+
95+func TestPreInitializeAdwaitaForCEF_InitializesAndMarksDetector(t *testing.T) {
96+ initResult := &bootstrap.ParallelInitResult{
97+ AdwaitaDetector: colorscheme.NewAdwaitaDetector(),
98+ }
99+ cfg := &config.Config{}
100+ cfg.Engine.Type = config.EngineTypeCEF
101+
102+ called := false
103+ preInitializeAdwaitaForCEF(cfg, initResult, func() {
104+ called = true
105+ })
106+
107+ if !called {
108+ t.Fatal("expected libadwaita initialization for CEF")
109+ }
110+ if !initResult.AdwaitaDetector.Available() {
111+ t.Fatal("expected adwaita detector to be marked available")
112+ }
113+}
114+
115+func TestPreInitializeAdwaitaForCEF_SkipsNonCEF(t *testing.T) {
116+ initResult := &bootstrap.ParallelInitResult{
117+ AdwaitaDetector: colorscheme.NewAdwaitaDetector(),
118+ }
119+ cfg := &config.Config{}
120+ cfg.Engine.Type = config.EngineTypeWebKit
121+
122+ called := false
123+ preInitializeAdwaitaForCEF(cfg, initResult, func() {
124+ called = true
125+ })
126+
127+ if called {
128+ t.Fatal("expected libadwaita initialization to be skipped for non-CEF")
129+ }
130+ if initResult.AdwaitaDetector.Available() {
131+ t.Fatal("expected adwaita detector to remain unavailable")
132+ }
133+}
M go.mod +1 -1
@@ -3,7 +3,7 @@ module github.com/bnema/dumber
33 go 1.26.1
44
55 require (
6- github.com/bnema/purego-cef v0.0.0-00010101000000-000000000000
6+ github.com/bnema/purego-cef v0.8.0
77 github.com/bnema/purego-ffmpeg v0.3.0
88 github.com/bnema/purego-sqlite v0.1.1
99 github.com/bnema/puregotk v0.3.0
@@ -3,7 +3,7 @@ module github.com/bnema/dumber
3 go 1.26.13 go 1.26.1
4 4
5 require (5 require (
6- github.com/bnema/purego-cef v0.0.0-00010101000000-0000000000006+ github.com/bnema/purego-cef v0.8.0
7 github.com/bnema/purego-ffmpeg v0.3.07 github.com/bnema/purego-ffmpeg v0.3.0
8 github.com/bnema/purego-sqlite v0.1.18 github.com/bnema/purego-sqlite v0.1.1
9 github.com/bnema/puregotk v0.3.09 github.com/bnema/puregotk v0.3.0
M go.sum +0 -2
@@ -8,8 +8,6 @@ github.com/bnema/purego v0.0.0-20260323095903-91aa177a2704 h1:0GaiO4102gOdp0es0M
88 github.com/bnema/purego v0.0.0-20260323095903-91aa177a2704/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
99 github.com/bnema/purego-sqlite v0.1.1 h1:lH/F6/xxaI+LwWNHv3zof83bDJWQnib9OlNnBo/JnII=
1010 github.com/bnema/purego-sqlite v0.1.1/go.mod h1:nRVrkBt8dFOlSGGOOFM/ynlk34TOU1RlOvH4T9KM91s=
11-github.com/bnema/puregotk v0.2.2 h1:lt9iOwOCHS0X9R4y+SK+zJsbQPGfsfPTyYTuBPWpBKM=
12-github.com/bnema/puregotk v0.2.2/go.mod h1:Sl24sbCl02M/HLCNUgaYVCckkYcGOi8yxMuGuQVWsL8=
1311 github.com/bnema/puregotk v0.3.0 h1:lKdRHkQBxUxE6lmiUJAYYdiUhV0uDq6UoQrVE7a562E=
1412 github.com/bnema/puregotk v0.3.0/go.mod h1:Sl24sbCl02M/HLCNUgaYVCckkYcGOi8yxMuGuQVWsL8=
1513 github.com/bnema/puregotk-webkit v0.0.16 h1:fYYtRLK3N4YgYmPkHpGZT6W+Mxu4EPu5I25H8nUv9oY=
@@ -8,8 +8,6 @@ github.com/bnema/purego v0.0.0-20260323095903-91aa177a2704 h1:0GaiO4102gOdp0es0M
8 github.com/bnema/purego v0.0.0-20260323095903-91aa177a2704/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=8 github.com/bnema/purego v0.0.0-20260323095903-91aa177a2704/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
9 github.com/bnema/purego-sqlite v0.1.1 h1:lH/F6/xxaI+LwWNHv3zof83bDJWQnib9OlNnBo/JnII=9 github.com/bnema/purego-sqlite v0.1.1 h1:lH/F6/xxaI+LwWNHv3zof83bDJWQnib9OlNnBo/JnII=
10 github.com/bnema/purego-sqlite v0.1.1/go.mod h1:nRVrkBt8dFOlSGGOOFM/ynlk34TOU1RlOvH4T9KM91s=10 github.com/bnema/purego-sqlite v0.1.1/go.mod h1:nRVrkBt8dFOlSGGOOFM/ynlk34TOU1RlOvH4T9KM91s=
11-github.com/bnema/puregotk v0.2.2 h1:lt9iOwOCHS0X9R4y+SK+zJsbQPGfsfPTyYTuBPWpBKM=
12-github.com/bnema/puregotk v0.2.2/go.mod h1:Sl24sbCl02M/HLCNUgaYVCckkYcGOi8yxMuGuQVWsL8=
13 github.com/bnema/puregotk v0.3.0 h1:lKdRHkQBxUxE6lmiUJAYYdiUhV0uDq6UoQrVE7a562E=11 github.com/bnema/puregotk v0.3.0 h1:lKdRHkQBxUxE6lmiUJAYYdiUhV0uDq6UoQrVE7a562E=
14 github.com/bnema/puregotk v0.3.0/go.mod h1:Sl24sbCl02M/HLCNUgaYVCckkYcGOi8yxMuGuQVWsL8=12 github.com/bnema/puregotk v0.3.0/go.mod h1:Sl24sbCl02M/HLCNUgaYVCckkYcGOi8yxMuGuQVWsL8=
15 github.com/bnema/puregotk-webkit v0.0.16 h1:fYYtRLK3N4YgYmPkHpGZT6W+Mxu4EPu5I25H8nUv9oY=13 github.com/bnema/puregotk-webkit v0.0.16 h1:fYYtRLK3N4YgYmPkHpGZT6W+Mxu4EPu5I25H8nUv9oY=
M internal/infrastructure/cef/engine_init.go +6 -2
@@ -63,6 +63,7 @@ func prepareCEFSettings(cfg config.CEFEngineConfig, logger *zerolog.Logger) pure
6363 settings := purecef.DefaultSettings()
6464 settings.MultiThreadedMessageLoop = true
6565 settings.ExternalMessagePump = false
66+ settings.RootCachePath = defaultCEFUserDataDir()
6667 if cfg.CEFDir != "" {
6768 settings.CEFDir = cfg.CEFDir
6869 }
@@ -92,6 +93,10 @@ func prepareCEFSettings(cfg config.CEFEngineConfig, logger *zerolog.Logger) pure
9293 return settings
9394 }
9495
96+func defaultCEFUserDataDir() string {
97+ return filepath.Clean(filepath.Join(os.Getenv("HOME"), ".config", "cef_user_data"))
98+}
99+
95100 // initializeCEF calls cef_initialize with the App to register custom schemes
96101 // and browser-process callbacks (OnBeforeCommandLineProcessing, etc.).
97102 func initializeCEF(eng *Engine, settings purecef.Settings, logger *zerolog.Logger) error {
@@ -299,8 +304,7 @@ func appendIfMissing(args []string, flag string) []string {
299304 // unclean shutdown (SIGKILL, SIGSEGV) and the next instance crashes trying
300305 // to connect to the dead process.
301306 func cleanStaleSingletonLocks(logger *zerolog.Logger) {
302- // CEF defaults to ~/.config/cef_user_data when no root_cache_path is set.
303- dir := filepath.Clean(filepath.Join(os.Getenv("HOME"), ".config", "cef_user_data"))
307+ dir := defaultCEFUserDataDir()
304308
305309 lockPath := filepath.Join(dir, "SingletonLock")
306310 target, err := os.Readlink(lockPath)
@@ -63,6 +63,7 @@ func prepareCEFSettings(cfg config.CEFEngineConfig, logger *zerolog.Logger) pure
63 settings := purecef.DefaultSettings()63 settings := purecef.DefaultSettings()
64 settings.MultiThreadedMessageLoop = true64 settings.MultiThreadedMessageLoop = true
65 settings.ExternalMessagePump = false65 settings.ExternalMessagePump = false
66+ settings.RootCachePath = defaultCEFUserDataDir()
66 if cfg.CEFDir != "" {67 if cfg.CEFDir != "" {
67 settings.CEFDir = cfg.CEFDir68 settings.CEFDir = cfg.CEFDir
68 }69 }
@@ -92,6 +93,10 @@ func prepareCEFSettings(cfg config.CEFEngineConfig, logger *zerolog.Logger) pure
92 return settings93 return settings
93 }94 }
94 95
96+func defaultCEFUserDataDir() string {
97+ return filepath.Clean(filepath.Join(os.Getenv("HOME"), ".config", "cef_user_data"))
98+}
99+
95 // initializeCEF calls cef_initialize with the App to register custom schemes100 // initializeCEF calls cef_initialize with the App to register custom schemes
96 // and browser-process callbacks (OnBeforeCommandLineProcessing, etc.).101 // and browser-process callbacks (OnBeforeCommandLineProcessing, etc.).
97 func initializeCEF(eng *Engine, settings purecef.Settings, logger *zerolog.Logger) error {102 func initializeCEF(eng *Engine, settings purecef.Settings, logger *zerolog.Logger) error {
@@ -299,8 +304,7 @@ func appendIfMissing(args []string, flag string) []string {
299 // unclean shutdown (SIGKILL, SIGSEGV) and the next instance crashes trying304 // unclean shutdown (SIGKILL, SIGSEGV) and the next instance crashes trying
300 // to connect to the dead process.305 // to connect to the dead process.
301 func cleanStaleSingletonLocks(logger *zerolog.Logger) {306 func cleanStaleSingletonLocks(logger *zerolog.Logger) {
302- // CEF defaults to ~/.config/cef_user_data when no root_cache_path is set.307+ dir := defaultCEFUserDataDir()
303- dir := filepath.Clean(filepath.Join(os.Getenv("HOME"), ".config", "cef_user_data"))
304 308
305 lockPath := filepath.Join(dir, "SingletonLock")309 lockPath := filepath.Join(dir, "SingletonLock")
306 target, err := os.Readlink(lockPath)310 target, err := os.Readlink(lockPath)
M internal/infrastructure/cef/engine_init_test.go +11 -0
@@ -5,6 +5,7 @@ import (
55 "testing"
66
77 "github.com/bnema/dumber/internal/infrastructure/config"
8+ "github.com/rs/zerolog"
89 "github.com/stretchr/testify/require"
910 )
1011
@@ -27,3 +28,13 @@ func TestPrepareCEFInitTraceFile_EnabledViaEnv(t *testing.T) {
2728 require.Equal(t, filepath.Join(filepath.Dir(cfg.LogFile), "cef_runtime.bootstrap.log"), path)
2829 require.FileExists(t, path)
2930 }
31+
32+func TestPrepareCEFSettings_SetsRootCachePath(t *testing.T) {
33+ homeDir := filepath.Join(t.TempDir(), "home")
34+ t.Setenv("HOME", homeDir)
35+
36+ logger := zerolog.Nop()
37+ settings := prepareCEFSettings(config.CEFEngineConfig{}, &logger)
38+
39+ require.Equal(t, filepath.Join(homeDir, ".config", "cef_user_data"), settings.RootCachePath)
40+}
@@ -5,6 +5,7 @@ import (
5 "testing"5 "testing"
6 6
7 "github.com/bnema/dumber/internal/infrastructure/config"7 "github.com/bnema/dumber/internal/infrastructure/config"
8+ "github.com/rs/zerolog"
8 "github.com/stretchr/testify/require"9 "github.com/stretchr/testify/require"
9 )10 )
10 11
@@ -27,3 +28,13 @@ func TestPrepareCEFInitTraceFile_EnabledViaEnv(t *testing.T) {
27 require.Equal(t, filepath.Join(filepath.Dir(cfg.LogFile), "cef_runtime.bootstrap.log"), path)28 require.Equal(t, filepath.Join(filepath.Dir(cfg.LogFile), "cef_runtime.bootstrap.log"), path)
28 require.FileExists(t, path)29 require.FileExists(t, path)
29 }30 }
31+
32+func TestPrepareCEFSettings_SetsRootCachePath(t *testing.T) {
33+ homeDir := filepath.Join(t.TempDir(), "home")
34+ t.Setenv("HOME", homeDir)
35+
36+ logger := zerolog.Nop()
37+ settings := prepareCEFSettings(config.CEFEngineConfig{}, &logger)
38+
39+ require.Equal(t, filepath.Join(homeDir, ".config", "cef_user_data"), settings.RootCachePath)
40+}
M internal/ui/standalone_omnibox.go +1 -2
@@ -9,7 +9,6 @@ import (
99 "github.com/bnema/dumber/internal/logging"
1010 "github.com/bnema/dumber/internal/ui/component"
1111 "github.com/bnema/dumber/internal/ui/layout"
12- "github.com/bnema/puregotk/v4/adw"
1312 "github.com/bnema/puregotk/v4/gdk"
1413 "github.com/bnema/puregotk/v4/gio"
1514 "github.com/bnema/puregotk/v4/glib"
@@ -345,7 +344,7 @@ func RunStandaloneOmnibox(ctx context.Context, runtimeCfg *StandaloneOmniboxRunt
345344 return 1
346345 }
347346
348- adw.Init()
347+ EnsureAdwaitaInitialized()
349348 windowCfg := DefaultStandaloneOmniboxWindowConfig()
350349 appID := windowCfg.Namespace
351350 gtkApp := gtk.NewApplication(&appID, gtkApplicationFlags())
@@ -9,7 +9,6 @@ import (
9 "github.com/bnema/dumber/internal/logging"9 "github.com/bnema/dumber/internal/logging"
10 "github.com/bnema/dumber/internal/ui/component"10 "github.com/bnema/dumber/internal/ui/component"
11 "github.com/bnema/dumber/internal/ui/layout"11 "github.com/bnema/dumber/internal/ui/layout"
12- "github.com/bnema/puregotk/v4/adw"
13 "github.com/bnema/puregotk/v4/gdk"12 "github.com/bnema/puregotk/v4/gdk"
14 "github.com/bnema/puregotk/v4/gio"13 "github.com/bnema/puregotk/v4/gio"
15 "github.com/bnema/puregotk/v4/glib"14 "github.com/bnema/puregotk/v4/glib"
@@ -345,7 +344,7 @@ func RunStandaloneOmnibox(ctx context.Context, runtimeCfg *StandaloneOmniboxRunt
345 return 1344 return 1
346 }345 }
347 346
348- adw.Init()347+ EnsureAdwaitaInitialized()
349 windowCfg := DefaultStandaloneOmniboxWindowConfig()348 windowCfg := DefaultStandaloneOmniboxWindowConfig()
350 appID := windowCfg.Namespace349 appID := windowCfg.Namespace
351 gtkApp := gtk.NewApplication(&appID, gtkApplicationFlags())350 gtkApp := gtk.NewApplication(&appID, gtkApplicationFlags())
M internal/ui/theme/css.go +0 -2
@@ -442,8 +442,6 @@ entry.omnibox-entry.omnibox-entry-bang-active:focus-visible > text {
442442 color: var(--muted);
443443 margin-top: -0.0625em; /* Nudge up 1px to align with entry text baseline */
444444 background-color: transparent;
445- /* Allow clicks to pass through to entry below */
446- pointer-events: none;
447445 }
448446 `
449447 }
@@ -442,8 +442,6 @@ entry.omnibox-entry.omnibox-entry-bang-active:focus-visible > text {
442 color: var(--muted);442 color: var(--muted);
443 margin-top: -0.0625em; /* Nudge up 1px to align with entry text baseline */443 margin-top: -0.0625em; /* Nudge up 1px to align with entry text baseline */
444 background-color: transparent;444 background-color: transparent;
445- /* Allow clicks to pass through to entry below */
446- pointer-events: none;
447 }445 }
448 `446 `
449 }447 }
M internal/ui/theme/css_test.go +7 -0
@@ -104,3 +104,10 @@ func TestGenerateCSSWithScale_UsesMediumWeightForOmniboxSuggestionTitle(t *testi
104104 assert.Contains(t, css, "font-weight: 500;")
105105 assert.NotContains(t, css, ".omnibox-suggestion-title {\n\tfont-size: 0.875em;\n\tcolor: var(--text);\n\tfont-weight: 400;")
106106 }
107+
108+func TestGenerateCSS_DoesNotEmitUnsupportedGTKProperties(t *testing.T) {
109+ css := GenerateCSS(DefaultDarkPalette())
110+
111+ assert.NotContains(t, css, "pointer-events:")
112+ assert.NotContains(t, css, "text-align:")
113+}
@@ -104,3 +104,10 @@ func TestGenerateCSSWithScale_UsesMediumWeightForOmniboxSuggestionTitle(t *testi
104 assert.Contains(t, css, "font-weight: 500;")104 assert.Contains(t, css, "font-weight: 500;")
105 assert.NotContains(t, css, ".omnibox-suggestion-title {\n\tfont-size: 0.875em;\n\tcolor: var(--text);\n\tfont-weight: 400;")105 assert.NotContains(t, css, ".omnibox-suggestion-title {\n\tfont-size: 0.875em;\n\tcolor: var(--text);\n\tfont-weight: 400;")
106 }106 }
107+
108+func TestGenerateCSS_DoesNotEmitUnsupportedGTKProperties(t *testing.T) {
109+ css := GenerateCSS(DefaultDarkPalette())
110+
111+ assert.NotContains(t, css, "pointer-events:")
112+ assert.NotContains(t, css, "text-align:")
113+}
M internal/ui/theme/webrtc_indicator_css.go +0 -1
@@ -76,7 +76,6 @@ func generateWebRTCPermissionIndicatorCSS(p Palette) string {
7676 background-color: alpha(var(--bg), 0.35);
7777 background-image: none;
7878 color: var(--muted);
79- text-align: left;
8079 transition: background-color 150ms ease-in-out,
8180 border-color 150ms ease-in-out,
8281 color 150ms ease-in-out;
@@ -76,7 +76,6 @@ func generateWebRTCPermissionIndicatorCSS(p Palette) string {
76 background-color: alpha(var(--bg), 0.35);76 background-color: alpha(var(--bg), 0.35);
77 background-image: none;77 background-image: none;
78 color: var(--muted);78 color: var(--muted);
79- text-align: left;
80 transition: background-color 150ms ease-in-out,79 transition: background-color 150ms ease-in-out,
81 border-color 150ms ease-in-out,80 border-color 150ms ease-in-out,
82 color 150ms ease-in-out;81 color 150ms ease-in-out;

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