detached e8eb767

fix(config): cap Skia CPU threads to 8, add null checks in webui

commit
e8eb7675a447c687f76c3cfc2a014d188433c08f
author
brice · · 2025-12-28 19:37 UTC
committer
brice · · 2025-12-29 17:28 UTC
parent
39cf1a7
signature
unsigned
3 files · +22 -3
M internal/infrastructure/config/profile.go +10 -1
@@ -2,6 +2,12 @@ package config
22
33 import "runtime"
44
5+const (
6+ // maxSkiaCPUThreads is the maximum number of Skia CPU painting threads
7+ // supported by WebKitGTK (0-8 range).
8+ maxSkiaCPUThreads = 8
9+)
10+
511 // ResolvedPerformanceSettings contains the computed performance settings
612 // after profile resolution. These are the actual values to apply.
713 type ResolvedPerformanceSettings struct {
@@ -93,11 +99,14 @@ func resolveLiteProfile() ResolvedPerformanceSettings {
9399
94100 // resolveMaxProfile returns settings optimized for maximum responsiveness.
95101 func resolveMaxProfile() ResolvedPerformanceSettings {
96- // Use half of available CPUs, minimum 4
102+ // Use half of available CPUs, minimum 4, maximum 8 (WebKitGTK Skia limit)
97103 cpuThreads := runtime.NumCPU() / 2
98104 if cpuThreads < 4 {
99105 cpuThreads = 4
100106 }
107+ if cpuThreads > maxSkiaCPUThreads {
108+ cpuThreads = maxSkiaCPUThreads
109+ }
101110
102111 return ResolvedPerformanceSettings{
103112 SkiaCPUPaintingThreads: cpuThreads,
@@ -2,6 +2,12 @@ package config
2 2
3 import "runtime"3 import "runtime"
4 4
5+const (
6+ // maxSkiaCPUThreads is the maximum number of Skia CPU painting threads
7+ // supported by WebKitGTK (0-8 range).
8+ maxSkiaCPUThreads = 8
9+)
10+
5 // ResolvedPerformanceSettings contains the computed performance settings11 // ResolvedPerformanceSettings contains the computed performance settings
6 // after profile resolution. These are the actual values to apply.12 // after profile resolution. These are the actual values to apply.
7 type ResolvedPerformanceSettings struct {13 type ResolvedPerformanceSettings struct {
@@ -93,11 +99,14 @@ func resolveLiteProfile() ResolvedPerformanceSettings {
93 99
94 // resolveMaxProfile returns settings optimized for maximum responsiveness.100 // resolveMaxProfile returns settings optimized for maximum responsiveness.
95 func resolveMaxProfile() ResolvedPerformanceSettings {101 func resolveMaxProfile() ResolvedPerformanceSettings {
96- // Use half of available CPUs, minimum 4102+ // Use half of available CPUs, minimum 4, maximum 8 (WebKitGTK Skia limit)
97 cpuThreads := runtime.NumCPU() / 2103 cpuThreads := runtime.NumCPU() / 2
98 if cpuThreads < 4 {104 if cpuThreads < 4 {
99 cpuThreads = 4105 cpuThreads = 4
100 }106 }
107+ if cpuThreads > maxSkiaCPUThreads {
108+ cpuThreads = maxSkiaCPUThreads
109+ }
101 110
102 return ResolvedPerformanceSettings{111 return ResolvedPerformanceSettings{
103 SkiaCPUPaintingThreads: cpuThreads,112 SkiaCPUPaintingThreads: cpuThreads,
M internal/infrastructure/config/profile_test.go +4 -0
@@ -75,10 +75,14 @@ func TestResolvePerformanceProfile_Max(t *testing.T) {
7575 result := ResolvePerformanceProfile(cfg)
7676
7777 // Max should have high resource allocation
78+ // CPU threads: NumCPU/2, clamped to [4, 8] range (WebKitGTK Skia limit)
7879 expectedCPUThreads := runtime.NumCPU() / 2
7980 if expectedCPUThreads < 4 {
8081 expectedCPUThreads = 4
8182 }
83+ if expectedCPUThreads > 8 {
84+ expectedCPUThreads = 8
85+ }
8286 if result.SkiaCPUPaintingThreads != expectedCPUThreads {
8387 t.Errorf("expected SkiaCPUPaintingThreads=%d, got %d", expectedCPUThreads, result.SkiaCPUPaintingThreads)
8488 }
@@ -75,10 +75,14 @@ func TestResolvePerformanceProfile_Max(t *testing.T) {
75 result := ResolvePerformanceProfile(cfg)75 result := ResolvePerformanceProfile(cfg)
76 76
77 // Max should have high resource allocation77 // Max should have high resource allocation
78+ // CPU threads: NumCPU/2, clamped to [4, 8] range (WebKitGTK Skia limit)
78 expectedCPUThreads := runtime.NumCPU() / 279 expectedCPUThreads := runtime.NumCPU() / 2
79 if expectedCPUThreads < 4 {80 if expectedCPUThreads < 4 {
80 expectedCPUThreads = 481 expectedCPUThreads = 4
81 }82 }
83+ if expectedCPUThreads > 8 {
84+ expectedCPUThreads = 8
85+ }
82 if result.SkiaCPUPaintingThreads != expectedCPUThreads {86 if result.SkiaCPUPaintingThreads != expectedCPUThreads {
83 t.Errorf("expected SkiaCPUPaintingThreads=%d, got %d", expectedCPUThreads, result.SkiaCPUPaintingThreads)87 t.Errorf("expected SkiaCPUPaintingThreads=%d, got %d", expectedCPUThreads, result.SkiaCPUPaintingThreads)
84 }88 }
M webui/src/pages/ConfigPage.svelte +8 -2
@@ -427,6 +427,7 @@
427427 </div>
428428
429429 <!-- Profile selector -->
430+ {#if config.performance}
430431 <div class="space-y-4">
431432 {#each PERFORMANCE_PROFILES as profile (profile.value)}
432433 <label
@@ -447,9 +448,14 @@
447448 </label>
448449 {/each}
449450 </div>
451+ {:else}
452+ <div class="text-sm text-muted-foreground">
453+ Performance settings not available. This may be due to a configuration error.
454+ </div>
455+ {/if}
450456
451457 <!-- Custom profile note -->
452- {#if config.performance.profile === "custom"}
458+ {#if config.performance?.profile === "custom"}
453459 <div class="rounded-md border border-border bg-muted/30 px-4 py-3 text-sm text-muted-foreground">
454460 <p>
455461 Custom profile settings are configured in your <code class="rounded bg-muted px-1 py-0.5">config.toml</code> file.
@@ -459,7 +465,7 @@
459465 {/if}
460466
461467 <!-- Resolved values display -->
462- {#if config.performance.resolved}
468+ {#if config.performance?.resolved}
463469 <div class="space-y-3">
464470 <div class="text-sm font-semibold uppercase tracking-[0.2em] text-muted-foreground">
465471 Resolved Settings (applied on restart)
@@ -427,6 +427,7 @@
427 </div>427 </div>
428 428
429 <!-- Profile selector -->429 <!-- Profile selector -->
430+ {#if config.performance}
430 <div class="space-y-4">431 <div class="space-y-4">
431 {#each PERFORMANCE_PROFILES as profile (profile.value)}432 {#each PERFORMANCE_PROFILES as profile (profile.value)}
432 <label433 <label
@@ -447,9 +448,14 @@
447 </label>448 </label>
448 {/each}449 {/each}
449 </div>450 </div>
451+ {:else}
452+ <div class="text-sm text-muted-foreground">
453+ Performance settings not available. This may be due to a configuration error.
454+ </div>
455+ {/if}
450 456
451 <!-- Custom profile note -->457 <!-- Custom profile note -->
452- {#if config.performance.profile === "custom"}458+ {#if config.performance?.profile === "custom"}
453 <div class="rounded-md border border-border bg-muted/30 px-4 py-3 text-sm text-muted-foreground">459 <div class="rounded-md border border-border bg-muted/30 px-4 py-3 text-sm text-muted-foreground">
454 <p>460 <p>
455 Custom profile settings are configured in your <code class="rounded bg-muted px-1 py-0.5">config.toml</code> file.461 Custom profile settings are configured in your <code class="rounded bg-muted px-1 py-0.5">config.toml</code> file.
@@ -459,7 +465,7 @@
459 {/if}465 {/if}
460 466
461 <!-- Resolved values display -->467 <!-- Resolved values display -->
462- {#if config.performance.resolved}468+ {#if config.performance?.resolved}
463 <div class="space-y-3">469 <div class="space-y-3">
464 <div class="text-sm font-semibold uppercase tracking-[0.2em] text-muted-foreground">470 <div class="text-sm font-semibold uppercase tracking-[0.2em] text-muted-foreground">
465 Resolved Settings (applied on restart)471 Resolved Settings (applied on restart)

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