detached 9da5cc3

feat(systemviews): add config editing controls

commit
9da5cc35e9393221aa73b3271ec292afe16b890b
author
brice · · 2026-04-24 03:02 UTC
parent
18449b5
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
8 files · +1790 -106
M assets/systemviews/systemviews.css +37 -3
@@ -411,7 +411,8 @@ body {
411411 }
412412
413413 .sv-card-form label,
414-.sv-inline-form label {
414+.sv-inline-form label,
415+.sv-fieldset label {
415416 display: grid;
416417 gap: 0.25rem;
417418 min-width: min(14rem, 100%);
@@ -423,7 +424,9 @@ body {
423424 .sv-card-form input,
424425 .sv-card-form select,
425426 .sv-inline-form input,
426-.sv-inline-form select {
427+.sv-inline-form select,
428+.sv-fieldset input,
429+.sv-fieldset select {
427430 border: 1px solid var(--sv-border, #2a313d);
428431 border-radius: 0.55rem;
429432 padding: 0.5rem 0.6rem;
@@ -432,6 +435,35 @@ body {
432435 font: inherit;
433436 }
434437
438+.sv-config-form,
439+.sv-fieldset {
440+ display: flex;
441+ flex-wrap: wrap;
442+ gap: 0.65rem;
443+ align-items: end;
444+}
445+
446+.sv-fieldset {
447+ width: 100%;
448+ margin: 0;
449+ padding: 0.75rem;
450+ border: 1px solid var(--sv-border, #2a313d);
451+ border-radius: 0.65rem;
452+}
453+
454+.sv-fieldset legend {
455+ color: var(--sv-muted, #9aa3b2);
456+ font-weight: 700;
457+ padding: 0 0.35rem;
458+}
459+
460+.sv-keybinding-row {
461+ display: grid;
462+ grid-template-columns: minmax(0, 1fr) minmax(18rem, auto);
463+ gap: 1rem;
464+ align-items: start;
465+}
466+
435467 .sv-filter-panel {
436468 display: grid;
437469 gap: 0.6rem;
@@ -478,7 +510,9 @@ body {
478510 }
479511
480512 @media (max-width: 720px) {
481- .sv-favorite-header {
513+ .sv-favorite-header,
514+ .sv-keybinding-row {
482515 display: grid;
516+ grid-template-columns: 1fr;
483517 }
484518 }
@@ -411,7 +411,8 @@ body {
411 }411 }
412 412
413 .sv-card-form label,413 .sv-card-form label,
414-.sv-inline-form label {414+.sv-inline-form label,
415+.sv-fieldset label {
415 display: grid;416 display: grid;
416 gap: 0.25rem;417 gap: 0.25rem;
417 min-width: min(14rem, 100%);418 min-width: min(14rem, 100%);
@@ -423,7 +424,9 @@ body {
423 .sv-card-form input,424 .sv-card-form input,
424 .sv-card-form select,425 .sv-card-form select,
425 .sv-inline-form input,426 .sv-inline-form input,
426-.sv-inline-form select {427+.sv-inline-form select,
428+.sv-fieldset input,
429+.sv-fieldset select {
427 border: 1px solid var(--sv-border, #2a313d);430 border: 1px solid var(--sv-border, #2a313d);
428 border-radius: 0.55rem;431 border-radius: 0.55rem;
429 padding: 0.5rem 0.6rem;432 padding: 0.5rem 0.6rem;
@@ -432,6 +435,35 @@ body {
432 font: inherit;435 font: inherit;
433 }436 }
434 437
438+.sv-config-form,
439+.sv-fieldset {
440+ display: flex;
441+ flex-wrap: wrap;
442+ gap: 0.65rem;
443+ align-items: end;
444+}
445+
446+.sv-fieldset {
447+ width: 100%;
448+ margin: 0;
449+ padding: 0.75rem;
450+ border: 1px solid var(--sv-border, #2a313d);
451+ border-radius: 0.65rem;
452+}
453+
454+.sv-fieldset legend {
455+ color: var(--sv-muted, #9aa3b2);
456+ font-weight: 700;
457+ padding: 0 0.35rem;
458+}
459+
460+.sv-keybinding-row {
461+ display: grid;
462+ grid-template-columns: minmax(0, 1fr) minmax(18rem, auto);
463+ gap: 1rem;
464+ align-items: start;
465+}
466+
435 .sv-filter-panel {467 .sv-filter-panel {
436 display: grid;468 display: grid;
437 gap: 0.6rem;469 gap: 0.6rem;
@@ -478,7 +510,9 @@ body {
478 }510 }
479 511
480 @media (max-width: 720px) {512 @media (max-width: 720px) {
481- .sv-favorite-header {513+ .sv-favorite-header,
514+ .sv-keybinding-row {
482 display: grid;515 display: grid;
516+ grid-template-columns: 1fr;
483 }517 }
484 }518 }
M internal/ui/systemviews/app.go +10 -1
@@ -38,6 +38,8 @@ type App struct {
3838 favoritesError string
3939 config *port.SystemviewConfigPayload
4040 keybindings any
41+ configNotice string
42+ configError string
4143 renderedHTML string
4244 }
4345
@@ -261,7 +263,12 @@ func (a *App) loadConfigRoute(ctx context.Context) error {
261263 a.renderedHTML = renderAppFrame(renderedPage{
262264 route: RouteConfig,
263265 subtitle: "Browser settings",
264- body: configHTML(config, keybindings),
266+ body: configHTML(configRenderData{
267+ Config: config,
268+ Keybindings: keybindings,
269+ Notice: a.configNotice,
270+ Error: a.configError,
271+ }),
265272 }, a.shellTheme)
266273 return nil
267274 }
@@ -300,6 +307,8 @@ func (a *App) resetRouteState() {
300307 a.favoritesError = ""
301308 a.config = nil
302309 a.keybindings = nil
310+ a.configNotice = ""
311+ a.configError = ""
303312 }
304313
305314 func (a *App) CurrentRoute() Route {
@@ -38,6 +38,8 @@ type App struct {
38 favoritesError string38 favoritesError string
39 config *port.SystemviewConfigPayload39 config *port.SystemviewConfigPayload
40 keybindings any40 keybindings any
41+ configNotice string
42+ configError string
41 renderedHTML string43 renderedHTML string
42 }44 }
43 45
@@ -261,7 +263,12 @@ func (a *App) loadConfigRoute(ctx context.Context) error {
261 a.renderedHTML = renderAppFrame(renderedPage{263 a.renderedHTML = renderAppFrame(renderedPage{
262 route: RouteConfig,264 route: RouteConfig,
263 subtitle: "Browser settings",265 subtitle: "Browser settings",
264- body: configHTML(config, keybindings),266+ body: configHTML(configRenderData{
267+ Config: config,
268+ Keybindings: keybindings,
269+ Notice: a.configNotice,
270+ Error: a.configError,
271+ }),
265 }, a.shellTheme)272 }, a.shellTheme)
266 return nil273 return nil
267 }274 }
@@ -300,6 +307,8 @@ func (a *App) resetRouteState() {
300 a.favoritesError = ""307 a.favoritesError = ""
301 a.config = nil308 a.config = nil
302 a.keybindings = nil309 a.keybindings = nil
310+ a.configNotice = ""
311+ a.configError = ""
303 }312 }
304 313
305 func (a *App) CurrentRoute() Route {314 func (a *App) CurrentRoute() Route {
M internal/ui/systemviews/app_test.go +161 -5
@@ -455,12 +455,149 @@ func TestAppLoadInitialConfigRouteRendersData(t *testing.T) {
455455 func TestConfigHTMLFallsBackForMalformedKeybindings(t *testing.T) {
456456 t.Parallel()
457457
458- html := configHTML(port.SystemviewConfigPayload{}, map[string]any{"groups": "oops"})
458+ html := configHTML(configRenderData{Config: port.SystemviewConfigPayload{}, Keybindings: map[string]any{"groups": "oops"}})
459459
460460 assert.Contains(t, html, "Keybindings unavailable")
461461 assert.NotContains(t, html, "groups[0].bindings[0].action")
462462 }
463463
464+func TestAppLoadInitialConfigRouteRendersEditControls(t *testing.T) {
465+ t.Parallel()
466+
467+ service := &fakeConfigService{
468+ current: testConfigPayload(),
469+ keybindings: port.KeybindingsConfig{Groups: []port.KeybindingGroup{{
470+ Mode: "default",
471+ DisplayName: "Default",
472+ Bindings: []port.KeybindingEntry{{
473+ Action: "toggle_history_systemview",
474+ Description: "Toggle history",
475+ Keys: []string{"ctrl+h"},
476+ DefaultKeys: []string{"ctrl+h"},
477+ }},
478+ }}},
479+ }
480+ app := NewApp(Dependencies{Config: service, LocationURI: "dumb://config"})
481+
482+ require.NoError(t, app.LoadInitial(context.Background()))
483+ assert.Contains(t, app.renderedHTML, `data-sv-action="config.appearance.save"`)
484+ assert.Contains(t, app.renderedHTML, `data-sv-action="config.appearance.reset"`)
485+ assert.Contains(t, app.renderedHTML, `data-sv-action="config.search.save"`)
486+ assert.Contains(t, app.renderedHTML, `data-sv-action="config.searchShortcut.create"`)
487+ assert.Contains(t, app.renderedHTML, `data-sv-action="config.searchShortcut.update"`)
488+ assert.Contains(t, app.renderedHTML, `data-sv-action="config.searchShortcut.delete"`)
489+ assert.Contains(t, app.renderedHTML, `data-sv-action="config.performance.save"`)
490+ assert.Contains(t, app.renderedHTML, `data-sv-action="config.keybinding.set"`)
491+ assert.Contains(t, app.renderedHTML, `data-sv-action="config.keybinding.reset"`)
492+ assert.Contains(t, app.renderedHTML, `data-sv-action="config.keybinding.resetAll"`)
493+ assert.Contains(t, app.renderedHTML, "may require restarting")
494+}
495+
496+func TestAppHandleConfigActionsRefreshesDOM(t *testing.T) {
497+ dom := &fakeDOM{}
498+ service := &fakeConfigService{
499+ current: testConfigPayload(),
500+ defaultCfg: testDefaultConfigPayload(),
501+ keybindings: port.KeybindingsConfig{Groups: []port.KeybindingGroup{{
502+ Mode: "default",
503+ DisplayName: "Default",
504+ Bindings: []port.KeybindingEntry{{
505+ Action: "toggle_history_systemview",
506+ Description: "Toggle history",
507+ Keys: []string{"ctrl+h"},
508+ DefaultKeys: []string{"ctrl+h"},
509+ }},
510+ }}},
511+ setResp: port.SetKeybindingResponse{},
512+ }
513+ app := NewApp(Dependencies{DOM: dom, Config: service, LocationURI: "dumb://config"})
514+
515+ require.NoError(t, app.HandleDOMAction(context.Background(), DOMAction{
516+ Action: configActionSaveAppearance,
517+ Data: map[string]string{
518+ "sans_font": "Inter", "serif_font": "Georgia", "monospace_font": "JetBrains Mono",
519+ "default_font_size": "18", "default_ui_scale": "1.25", "color_scheme": "prefer-light",
520+ "light_background": "#ffffff", "light_surface": "#fafafa", "light_surface_variant": "#eeeeee", "light_text": "#111111", "light_muted": "#666666", "light_accent": "#0055ff", "light_border": "#dddddd",
521+ "dark_background": "#111111", "dark_surface": "#1a1a1a", "dark_surface_variant": "#2a2a2a", "dark_text": "#f5f5f5", "dark_muted": "#a0a0a0", "dark_accent": "#66aaff", "dark_border": "#333333",
522+ },
523+ }))
524+ assert.True(t, service.calledSave)
525+ assert.Equal(t, 18, service.savedConfig.Appearance.DefaultFontSize)
526+ assert.Equal(t, 1.25, service.savedConfig.DefaultUIScale)
527+ assert.Contains(t, dom.html, "Saved appearance settings")
528+ assert.Contains(t, dom.html, `class="sv-app sv-light"`)
529+
530+ require.NoError(t, app.HandleDOMAction(context.Background(), DOMAction{
531+ Action: configActionCreateSearchShortcut,
532+ Data: map[string]string{"key": "g", "url": "https://google.com/search?q=%s", "description": "Google"},
533+ }))
534+ assert.Equal(t, "Google", service.savedConfig.SearchShortcuts["g"].Description)
535+ assert.Contains(t, dom.html, "Created search shortcut g")
536+
537+ require.NoError(t, app.HandleDOMAction(context.Background(), DOMAction{
538+ Action: configActionSavePerformance,
539+ Data: map[string]string{
540+ "profile": "custom", "skia_cpu_threads": "4", "skia_gpu_threads": "2",
541+ "web_process_memory_mb": "2048", "network_process_memory_mb": "512", "webview_pool_prewarm": "4",
542+ },
543+ }))
544+ assert.Equal(t, "custom", service.savedConfig.Performance.Profile)
545+ assert.Equal(t, 2048, service.savedConfig.Performance.Custom.WebProcessMemoryMB)
546+ assert.Contains(t, dom.html, "Saved performance settings")
547+
548+ require.NoError(t, app.HandleDOMAction(context.Background(), DOMAction{
549+ Action: configActionSetKeybinding,
550+ Data: map[string]string{"mode": "default", "action": "toggle_history_systemview", "keys": "ctrl+h, alt+h"},
551+ }))
552+ assert.True(t, service.calledSet)
553+ assert.Equal(t, []string{"ctrl+h", "alt+h"}, service.setReq.Keys)
554+ assert.Contains(t, dom.html, "Saved keybinding toggle_history_systemview")
555+
556+ require.NoError(t, app.HandleDOMAction(context.Background(), DOMAction{
557+ Action: configActionResetKeybinding,
558+ Data: map[string]string{"mode": "default", "action": "toggle_history_systemview"},
559+ }))
560+ assert.True(t, service.calledReset)
561+ assert.Equal(t, "toggle_history_systemview", service.resetReq.Action)
562+ assert.Contains(t, dom.html, "Reset keybinding toggle_history_systemview")
563+
564+ require.NoError(t, app.HandleDOMAction(context.Background(), DOMAction{Action: configActionResetAllKeybindings}))
565+ assert.True(t, service.calledResetAll)
566+ assert.Contains(t, dom.html, "Reset all keybindings to defaults")
567+}
568+
569+func testConfigPayload() port.SystemviewConfigPayload {
570+ return port.SystemviewConfigPayload{
571+ EngineType: "webkit",
572+ Appearance: port.WebUIAppearanceConfig{
573+ ColorScheme: "prefer-dark",
574+ SansFont: "Inter",
575+ SerifFont: "Georgia",
576+ MonospaceFont: "JetBrains Mono",
577+ DefaultFontSize: 16,
578+ LightPalette: port.ColorPalette{Background: "#ffffff", Surface: "#fafafa", SurfaceVariant: "#eeeeee", Text: "#111111", Muted: "#666666", Accent: "#0055ff", Border: "#dddddd"},
579+ DarkPalette: port.ColorPalette{Background: "#111111", Surface: "#1a1a1a", SurfaceVariant: "#2a2a2a", Text: "#f5f5f5", Muted: "#a0a0a0", Accent: "#66aaff", Border: "#333333"},
580+ },
581+ DefaultUIScale: 1,
582+ DefaultSearchEngine: "https://duckduckgo.com/?q=%s",
583+ SearchShortcuts: map[string]port.SearchShortcut{
584+ "ddg": {URL: "https://duckduckgo.com/?q=%s", Description: "DuckDuckGo"},
585+ },
586+ Performance: port.SystemviewPerformancePayload{
587+ Profile: "default",
588+ Custom: port.SystemviewCustomPerformancePayload{SkiaCPUThreads: 2, SkiaGPUThreads: 1, WebProcessMemoryMB: 1024, NetworkProcessMemoryMB: 256, WebViewPoolPrewarm: 2},
589+ Hardware: port.SystemviewHardwarePayload{CPUCores: 4, CPUThreads: 8, TotalRAMMB: 16384, GPUVendor: "AMD", GPUName: "Radeon", VRAMMB: 4096},
590+ },
591+ }
592+}
593+
594+func testDefaultConfigPayload() port.SystemviewConfigPayload {
595+ cfg := testConfigPayload()
596+ cfg.Appearance.DefaultFontSize = 14
597+ cfg.DefaultUIScale = 1
598+ return cfg
599+}
600+
464601 type fakeDOM struct {
465602 mounted bool
466603 html string
@@ -641,6 +778,10 @@ type fakeConfigService struct {
641778 current port.SystemviewConfigPayload
642779 defaultCfg port.SystemviewConfigPayload
643780 keybindings any
781+ savedConfig port.WebUIConfig
782+ setReq port.SetKeybindingRequest
783+ setResp any
784+ resetReq port.ResetKeybindingRequest
644785 }
645786
646787 func (s *fakeConfigService) Current(context.Context) (port.SystemviewConfigPayload, error) {
@@ -653,8 +794,21 @@ func (s *fakeConfigService) Default(context.Context) (port.SystemviewConfigPaylo
653794 return s.defaultCfg, nil
654795 }
655796
656-func (s *fakeConfigService) Save(context.Context, port.WebUIConfig) error {
797+func (s *fakeConfigService) Save(_ context.Context, cfg port.WebUIConfig) error {
657798 s.calledSave = true
799+ s.savedConfig = cfg
800+ s.current.Appearance = cfg.Appearance
801+ s.current.DefaultUIScale = cfg.DefaultUIScale
802+ s.current.DefaultSearchEngine = cfg.DefaultSearchEngine
803+ s.current.SearchShortcuts = cfg.SearchShortcuts
804+ s.current.Performance.Profile = cfg.Performance.Profile
805+ s.current.Performance.Custom = port.SystemviewCustomPerformancePayload{
806+ SkiaCPUThreads: cfg.Performance.Custom.SkiaCPUThreads,
807+ SkiaGPUThreads: cfg.Performance.Custom.SkiaGPUThreads,
808+ WebProcessMemoryMB: cfg.Performance.Custom.WebProcessMemoryMB,
809+ NetworkProcessMemoryMB: cfg.Performance.Custom.NetworkProcessMemoryMB,
810+ WebViewPoolPrewarm: cfg.Performance.Custom.WebViewPoolPrewarm,
811+ }
658812 return nil
659813 }
660814
@@ -663,13 +817,15 @@ func (s *fakeConfigService) GetKeybindings(context.Context) (any, error) {
663817 return s.keybindings, nil
664818 }
665819
666-func (s *fakeConfigService) SetKeybinding(context.Context, port.SetKeybindingRequest) (any, error) {
820+func (s *fakeConfigService) SetKeybinding(_ context.Context, req port.SetKeybindingRequest) (any, error) {
667821 s.calledSet = true
668- return nil, nil
822+ s.setReq = req
823+ return s.setResp, nil
669824 }
670825
671-func (s *fakeConfigService) ResetKeybinding(context.Context, port.ResetKeybindingRequest) error {
826+func (s *fakeConfigService) ResetKeybinding(_ context.Context, req port.ResetKeybindingRequest) error {
672827 s.calledReset = true
828+ s.resetReq = req
673829 return nil
674830 }
675831
@@ -455,12 +455,149 @@ func TestAppLoadInitialConfigRouteRendersData(t *testing.T) {
455 func TestConfigHTMLFallsBackForMalformedKeybindings(t *testing.T) {455 func TestConfigHTMLFallsBackForMalformedKeybindings(t *testing.T) {
456 t.Parallel()456 t.Parallel()
457 457
458- html := configHTML(port.SystemviewConfigPayload{}, map[string]any{"groups": "oops"})458+ html := configHTML(configRenderData{Config: port.SystemviewConfigPayload{}, Keybindings: map[string]any{"groups": "oops"}})
459 459
460 assert.Contains(t, html, "Keybindings unavailable")460 assert.Contains(t, html, "Keybindings unavailable")
461 assert.NotContains(t, html, "groups[0].bindings[0].action")461 assert.NotContains(t, html, "groups[0].bindings[0].action")
462 }462 }
463 463
464+func TestAppLoadInitialConfigRouteRendersEditControls(t *testing.T) {
465+ t.Parallel()
466+
467+ service := &fakeConfigService{
468+ current: testConfigPayload(),
469+ keybindings: port.KeybindingsConfig{Groups: []port.KeybindingGroup{{
470+ Mode: "default",
471+ DisplayName: "Default",
472+ Bindings: []port.KeybindingEntry{{
473+ Action: "toggle_history_systemview",
474+ Description: "Toggle history",
475+ Keys: []string{"ctrl+h"},
476+ DefaultKeys: []string{"ctrl+h"},
477+ }},
478+ }}},
479+ }
480+ app := NewApp(Dependencies{Config: service, LocationURI: "dumb://config"})
481+
482+ require.NoError(t, app.LoadInitial(context.Background()))
483+ assert.Contains(t, app.renderedHTML, `data-sv-action="config.appearance.save"`)
484+ assert.Contains(t, app.renderedHTML, `data-sv-action="config.appearance.reset"`)
485+ assert.Contains(t, app.renderedHTML, `data-sv-action="config.search.save"`)
486+ assert.Contains(t, app.renderedHTML, `data-sv-action="config.searchShortcut.create"`)
487+ assert.Contains(t, app.renderedHTML, `data-sv-action="config.searchShortcut.update"`)
488+ assert.Contains(t, app.renderedHTML, `data-sv-action="config.searchShortcut.delete"`)
489+ assert.Contains(t, app.renderedHTML, `data-sv-action="config.performance.save"`)
490+ assert.Contains(t, app.renderedHTML, `data-sv-action="config.keybinding.set"`)
491+ assert.Contains(t, app.renderedHTML, `data-sv-action="config.keybinding.reset"`)
492+ assert.Contains(t, app.renderedHTML, `data-sv-action="config.keybinding.resetAll"`)
493+ assert.Contains(t, app.renderedHTML, "may require restarting")
494+}
495+
496+func TestAppHandleConfigActionsRefreshesDOM(t *testing.T) {
497+ dom := &fakeDOM{}
498+ service := &fakeConfigService{
499+ current: testConfigPayload(),
500+ defaultCfg: testDefaultConfigPayload(),
501+ keybindings: port.KeybindingsConfig{Groups: []port.KeybindingGroup{{
502+ Mode: "default",
503+ DisplayName: "Default",
504+ Bindings: []port.KeybindingEntry{{
505+ Action: "toggle_history_systemview",
506+ Description: "Toggle history",
507+ Keys: []string{"ctrl+h"},
508+ DefaultKeys: []string{"ctrl+h"},
509+ }},
510+ }}},
511+ setResp: port.SetKeybindingResponse{},
512+ }
513+ app := NewApp(Dependencies{DOM: dom, Config: service, LocationURI: "dumb://config"})
514+
515+ require.NoError(t, app.HandleDOMAction(context.Background(), DOMAction{
516+ Action: configActionSaveAppearance,
517+ Data: map[string]string{
518+ "sans_font": "Inter", "serif_font": "Georgia", "monospace_font": "JetBrains Mono",
519+ "default_font_size": "18", "default_ui_scale": "1.25", "color_scheme": "prefer-light",
520+ "light_background": "#ffffff", "light_surface": "#fafafa", "light_surface_variant": "#eeeeee", "light_text": "#111111", "light_muted": "#666666", "light_accent": "#0055ff", "light_border": "#dddddd",
521+ "dark_background": "#111111", "dark_surface": "#1a1a1a", "dark_surface_variant": "#2a2a2a", "dark_text": "#f5f5f5", "dark_muted": "#a0a0a0", "dark_accent": "#66aaff", "dark_border": "#333333",
522+ },
523+ }))
524+ assert.True(t, service.calledSave)
525+ assert.Equal(t, 18, service.savedConfig.Appearance.DefaultFontSize)
526+ assert.Equal(t, 1.25, service.savedConfig.DefaultUIScale)
527+ assert.Contains(t, dom.html, "Saved appearance settings")
528+ assert.Contains(t, dom.html, `class="sv-app sv-light"`)
529+
530+ require.NoError(t, app.HandleDOMAction(context.Background(), DOMAction{
531+ Action: configActionCreateSearchShortcut,
532+ Data: map[string]string{"key": "g", "url": "https://google.com/search?q=%s", "description": "Google"},
533+ }))
534+ assert.Equal(t, "Google", service.savedConfig.SearchShortcuts["g"].Description)
535+ assert.Contains(t, dom.html, "Created search shortcut g")
536+
537+ require.NoError(t, app.HandleDOMAction(context.Background(), DOMAction{
538+ Action: configActionSavePerformance,
539+ Data: map[string]string{
540+ "profile": "custom", "skia_cpu_threads": "4", "skia_gpu_threads": "2",
541+ "web_process_memory_mb": "2048", "network_process_memory_mb": "512", "webview_pool_prewarm": "4",
542+ },
543+ }))
544+ assert.Equal(t, "custom", service.savedConfig.Performance.Profile)
545+ assert.Equal(t, 2048, service.savedConfig.Performance.Custom.WebProcessMemoryMB)
546+ assert.Contains(t, dom.html, "Saved performance settings")
547+
548+ require.NoError(t, app.HandleDOMAction(context.Background(), DOMAction{
549+ Action: configActionSetKeybinding,
550+ Data: map[string]string{"mode": "default", "action": "toggle_history_systemview", "keys": "ctrl+h, alt+h"},
551+ }))
552+ assert.True(t, service.calledSet)
553+ assert.Equal(t, []string{"ctrl+h", "alt+h"}, service.setReq.Keys)
554+ assert.Contains(t, dom.html, "Saved keybinding toggle_history_systemview")
555+
556+ require.NoError(t, app.HandleDOMAction(context.Background(), DOMAction{
557+ Action: configActionResetKeybinding,
558+ Data: map[string]string{"mode": "default", "action": "toggle_history_systemview"},
559+ }))
560+ assert.True(t, service.calledReset)
561+ assert.Equal(t, "toggle_history_systemview", service.resetReq.Action)
562+ assert.Contains(t, dom.html, "Reset keybinding toggle_history_systemview")
563+
564+ require.NoError(t, app.HandleDOMAction(context.Background(), DOMAction{Action: configActionResetAllKeybindings}))
565+ assert.True(t, service.calledResetAll)
566+ assert.Contains(t, dom.html, "Reset all keybindings to defaults")
567+}
568+
569+func testConfigPayload() port.SystemviewConfigPayload {
570+ return port.SystemviewConfigPayload{
571+ EngineType: "webkit",
572+ Appearance: port.WebUIAppearanceConfig{
573+ ColorScheme: "prefer-dark",
574+ SansFont: "Inter",
575+ SerifFont: "Georgia",
576+ MonospaceFont: "JetBrains Mono",
577+ DefaultFontSize: 16,
578+ LightPalette: port.ColorPalette{Background: "#ffffff", Surface: "#fafafa", SurfaceVariant: "#eeeeee", Text: "#111111", Muted: "#666666", Accent: "#0055ff", Border: "#dddddd"},
579+ DarkPalette: port.ColorPalette{Background: "#111111", Surface: "#1a1a1a", SurfaceVariant: "#2a2a2a", Text: "#f5f5f5", Muted: "#a0a0a0", Accent: "#66aaff", Border: "#333333"},
580+ },
581+ DefaultUIScale: 1,
582+ DefaultSearchEngine: "https://duckduckgo.com/?q=%s",
583+ SearchShortcuts: map[string]port.SearchShortcut{
584+ "ddg": {URL: "https://duckduckgo.com/?q=%s", Description: "DuckDuckGo"},
585+ },
586+ Performance: port.SystemviewPerformancePayload{
587+ Profile: "default",
588+ Custom: port.SystemviewCustomPerformancePayload{SkiaCPUThreads: 2, SkiaGPUThreads: 1, WebProcessMemoryMB: 1024, NetworkProcessMemoryMB: 256, WebViewPoolPrewarm: 2},
589+ Hardware: port.SystemviewHardwarePayload{CPUCores: 4, CPUThreads: 8, TotalRAMMB: 16384, GPUVendor: "AMD", GPUName: "Radeon", VRAMMB: 4096},
590+ },
591+ }
592+}
593+
594+func testDefaultConfigPayload() port.SystemviewConfigPayload {
595+ cfg := testConfigPayload()
596+ cfg.Appearance.DefaultFontSize = 14
597+ cfg.DefaultUIScale = 1
598+ return cfg
599+}
600+
464 type fakeDOM struct {601 type fakeDOM struct {
465 mounted bool602 mounted bool
466 html string603 html string
@@ -641,6 +778,10 @@ type fakeConfigService struct {
641 current port.SystemviewConfigPayload778 current port.SystemviewConfigPayload
642 defaultCfg port.SystemviewConfigPayload779 defaultCfg port.SystemviewConfigPayload
643 keybindings any780 keybindings any
781+ savedConfig port.WebUIConfig
782+ setReq port.SetKeybindingRequest
783+ setResp any
784+ resetReq port.ResetKeybindingRequest
644 }785 }
645 786
646 func (s *fakeConfigService) Current(context.Context) (port.SystemviewConfigPayload, error) {787 func (s *fakeConfigService) Current(context.Context) (port.SystemviewConfigPayload, error) {
@@ -653,8 +794,21 @@ func (s *fakeConfigService) Default(context.Context) (port.SystemviewConfigPaylo
653 return s.defaultCfg, nil794 return s.defaultCfg, nil
654 }795 }
655 796
656-func (s *fakeConfigService) Save(context.Context, port.WebUIConfig) error {797+func (s *fakeConfigService) Save(_ context.Context, cfg port.WebUIConfig) error {
657 s.calledSave = true798 s.calledSave = true
799+ s.savedConfig = cfg
800+ s.current.Appearance = cfg.Appearance
801+ s.current.DefaultUIScale = cfg.DefaultUIScale
802+ s.current.DefaultSearchEngine = cfg.DefaultSearchEngine
803+ s.current.SearchShortcuts = cfg.SearchShortcuts
804+ s.current.Performance.Profile = cfg.Performance.Profile
805+ s.current.Performance.Custom = port.SystemviewCustomPerformancePayload{
806+ SkiaCPUThreads: cfg.Performance.Custom.SkiaCPUThreads,
807+ SkiaGPUThreads: cfg.Performance.Custom.SkiaGPUThreads,
808+ WebProcessMemoryMB: cfg.Performance.Custom.WebProcessMemoryMB,
809+ NetworkProcessMemoryMB: cfg.Performance.Custom.NetworkProcessMemoryMB,
810+ WebViewPoolPrewarm: cfg.Performance.Custom.WebViewPoolPrewarm,
811+ }
658 return nil812 return nil
659 }813 }
660 814
@@ -663,13 +817,15 @@ func (s *fakeConfigService) GetKeybindings(context.Context) (any, error) {
663 return s.keybindings, nil817 return s.keybindings, nil
664 }818 }
665 819
666-func (s *fakeConfigService) SetKeybinding(context.Context, port.SetKeybindingRequest) (any, error) {820+func (s *fakeConfigService) SetKeybinding(_ context.Context, req port.SetKeybindingRequest) (any, error) {
667 s.calledSet = true821 s.calledSet = true
668- return nil, nil822+ s.setReq = req
823+ return s.setResp, nil
669 }824 }
670 825
671-func (s *fakeConfigService) ResetKeybinding(context.Context, port.ResetKeybindingRequest) error {826+func (s *fakeConfigService) ResetKeybinding(_ context.Context, req port.ResetKeybindingRequest) error {
672 s.calledReset = true827 s.calledReset = true
828+ s.resetReq = req
673 return nil829 return nil
674 }830 }
675 831
M internal/ui/systemviews/config.templ +136 -12
@@ -1,17 +1,134 @@
11 package systemviews
22
3+import "github.com/bnema/dumber/internal/application/port"
4+
35 templ ConfigView(data configRenderData) {
4- @Section("sv-config-values", "Config values") {
5- @KV(flattenValues(data.Config))
6+ @Alert("success", data.Notice)
7+ @Alert("error", data.Error)
8+ @Section("sv-config-appearance", "Appearance") {
9+ @AppearanceForm(data.Config)
10+ }
11+ @Section("sv-config-search", "Search") {
12+ @SearchSettings(data.Config)
13+ }
14+ @Section("sv-config-performance", "Performance") {
15+ @PerformanceSettings(data.Config)
616 }
717 @Section("sv-config-keybindings", "Keybindings") {
818 @Meta(configKeybindingSummary(data.Keybindings))
919 @ConfigKeybindings(data.Keybindings)
1020 }
21+ @Section("sv-config-values", "Current payload") {
22+ @KV(flattenValues(data.Config))
23+ }
24+}
25+
26+templ AppearanceForm(config port.SystemviewConfigPayload) {
27+ <form class="sv-card-form sv-config-form" data-sv-action="config.appearance.save">
28+ <label><span>Sans font</span><input name="sans_font" type="text" value={ config.Appearance.SansFont } required/></label>
29+ <label><span>Serif font</span><input name="serif_font" type="text" value={ config.Appearance.SerifFont } required/></label>
30+ <label><span>Monospace font</span><input name="monospace_font" type="text" value={ config.Appearance.MonospaceFont } required/></label>
31+ <label><span>Default font size</span><input name="default_font_size" type="number" min="1" max="72" value={ formatConfigInt(config.Appearance.DefaultFontSize) } required/></label>
32+ <label><span>UI scale</span><input name="default_ui_scale" type="number" min="0.5" max="3" step="0.05" value={ formatConfigFloat(config.DefaultUIScale) } required/></label>
33+ <label><span>Color scheme</span><select name="color_scheme">
34+ <option value="default" selected?={ colorSchemeSelected(config.Appearance.ColorScheme, "default") }>System default</option>
35+ <option value="prefer-light" selected?={ colorSchemeSelected(config.Appearance.ColorScheme, "prefer-light") }>Prefer light</option>
36+ <option value="prefer-dark" selected?={ colorSchemeSelected(config.Appearance.ColorScheme, "prefer-dark") }>Prefer dark</option>
37+ </select></label>
38+ @PaletteFields("Light palette", "light", config.Appearance.LightPalette)
39+ @PaletteFields("Dark palette", "dark", config.Appearance.DarkPalette)
40+ <div class="sv-button-row sv-form-actions">
41+ <button class="sv-button" type="submit">Save appearance</button>
42+ <button class="sv-button sv-button-secondary" type="button" data-sv-action="config.appearance.reset" data-sv-confirm="Reset appearance settings to defaults?">Reset appearance</button>
43+ </div>
44+ </form>
45+}
46+
47+templ PaletteFields(title string, prefix string, palette port.ColorPalette) {
48+ <fieldset class="sv-fieldset">
49+ <legend>{ title }</legend>
50+ <label><span>Background</span><input name={ prefix + "_background" } type="text" value={ palette.Background } required/></label>
51+ <label><span>Surface</span><input name={ prefix + "_surface" } type="text" value={ palette.Surface } required/></label>
52+ <label><span>Surface variant</span><input name={ prefix + "_surface_variant" } type="text" value={ palette.SurfaceVariant } required/></label>
53+ <label><span>Text</span><input name={ prefix + "_text" } type="text" value={ palette.Text } required/></label>
54+ <label><span>Muted</span><input name={ prefix + "_muted" } type="text" value={ palette.Muted } required/></label>
55+ <label><span>Accent</span><input name={ prefix + "_accent" } type="text" value={ palette.Accent } required/></label>
56+ <label><span>Border</span><input name={ prefix + "_border" } type="text" value={ palette.Border } required/></label>
57+ </fieldset>
58+}
59+
60+templ SearchSettings(config port.SystemviewConfigPayload) {
61+ <form class="sv-card-form" data-sv-action="config.search.save">
62+ <label><span>Default search engine</span><input name="default_search_engine" type="text" value={ config.DefaultSearchEngine } placeholder="https://duckduckgo.com/?q=%s" required/></label>
63+ <button class="sv-button" type="submit">Save search default</button>
64+ </form>
65+ <div class="sv-group">
66+ <div class="sv-group-header"><h4>Search shortcuts</h4></div>
67+ <p class="sv-meta">Shortcut URLs must include the %s placeholder for the search query.</p>
68+ <form class="sv-inline-form" data-sv-action="config.searchShortcut.create">
69+ <label><span>Key</span><input name="key" type="text" placeholder="ddg" required/></label>
70+ <label><span>URL</span><input name="url" type="text" placeholder="https://duckduckgo.com/?q=%s" required/></label>
71+ <label><span>Description</span><input name="description" type="text" placeholder="DuckDuckGo" required/></label>
72+ <button class="sv-button sv-button-secondary" type="submit">Add shortcut</button>
73+ </form>
74+ if len(searchShortcutRows(config.SearchShortcuts)) == 0 {
75+ @EmptyState("No search shortcuts configured")
76+ } else {
77+ <ul class="sv-list">
78+ for _, shortcut := range searchShortcutRows(config.SearchShortcuts) {
79+ @SearchShortcutItem(shortcut)
80+ }
81+ </ul>
82+ }
83+ </div>
84+}
85+
86+templ SearchShortcutItem(shortcut searchShortcutRow) {
87+ <li class="sv-list-row">
88+ <form class="sv-inline-form" data-sv-action="config.searchShortcut.update">
89+ <input type="hidden" name="key" value={ shortcut.Key }/>
90+ <label><span>Key</span><input name="new_key" type="text" value={ shortcut.Key } required/></label>
91+ <label><span>URL</span><input name="url" type="text" value={ shortcut.URL } required/></label>
92+ <label><span>Description</span><input name="description" type="text" value={ shortcut.Description } required/></label>
93+ <button class="sv-button sv-button-secondary" type="submit">Save</button>
94+ <button class="sv-button sv-button-secondary sv-button-danger" type="button" data-sv-action="config.searchShortcut.delete" data-key={ shortcut.Key } data-sv-confirm="Delete this search shortcut?">Delete</button>
95+ </form>
96+ </li>
97+}
98+
99+templ PerformanceSettings(config port.SystemviewConfigPayload) {
100+ <form class="sv-card-form sv-config-form" data-sv-action="config.performance.save">
101+ <label><span>Profile</span><select name="profile">
102+ <option value="default" selected?={ performanceProfileSelected(config.Performance.Profile, "default") }>Default</option>
103+ <option value="lite" selected?={ performanceProfileSelected(config.Performance.Profile, "lite") }>Lite</option>
104+ <option value="max" selected?={ performanceProfileSelected(config.Performance.Profile, "max") }>Max</option>
105+ <option value="custom" selected?={ performanceProfileSelected(config.Performance.Profile, "custom") }>Custom</option>
106+ </select></label>
107+ <label><span>Skia CPU threads</span><input name="skia_cpu_threads" type="number" min="0" max="8" value={ formatConfigInt(config.Performance.Custom.SkiaCPUThreads) }/></label>
108+ <label><span>Skia GPU threads</span><input name="skia_gpu_threads" type="number" min="-1" max="8" value={ formatConfigInt(config.Performance.Custom.SkiaGPUThreads) }/></label>
109+ <label><span>Web process memory MB</span><input name="web_process_memory_mb" type="number" min="0" max="16384" value={ formatConfigInt(config.Performance.Custom.WebProcessMemoryMB) }/></label>
110+ <label><span>Network process memory MB</span><input name="network_process_memory_mb" type="number" min="0" max="4096" value={ formatConfigInt(config.Performance.Custom.NetworkProcessMemoryMB) }/></label>
111+ <label><span>WebView pool prewarm</span><input name="webview_pool_prewarm" type="number" min="0" max="20" value={ formatConfigInt(config.Performance.Custom.WebViewPoolPrewarm) }/></label>
112+ <button class="sv-button" type="submit">Save performance</button>
113+ </form>
114+ <div class="sv-stat-grid">
115+ <div class="sv-stat-card"><span class="sv-stat-value">{ formatConfigInt(config.Performance.Hardware.CPUThreads) }</span><span class="sv-stat-label">CPU threads</span></div>
116+ <div class="sv-stat-card"><span class="sv-stat-value">{ formatConfigInt(config.Performance.Hardware.TotalRAMMB) }</span><span class="sv-stat-label">RAM MB</span></div>
117+ <div class="sv-stat-card"><span class="sv-stat-value">{ config.Performance.Hardware.GPUName }</span><span class="sv-stat-label">GPU</span></div>
118+ <div class="sv-stat-card"><span class="sv-stat-value">{ config.EngineType }</span><span class="sv-stat-label">Engine</span></div>
119+ </div>
120+ if !engineIsWebKit(config.EngineType) {
121+ <p class="sv-meta">Performance tuning applies only to the WebKit engine.</p>
122+ } else {
123+ <p class="sv-meta">Performance profile changes may require restarting the browser to affect existing engine processes.</p>
124+ }
11125 }
12126
13127 templ ConfigKeybindings(keybindings any) {
14128 {{ groups := configKeybindingGroups(keybindings) }}
129+ <div class="sv-button-row">
130+ <button class="sv-button sv-button-secondary sv-button-danger" type="button" data-sv-action="config.keybinding.resetAll" data-sv-confirm="Reset all keybindings to defaults?">Reset all keybindings</button>
131+ </div>
15132 if groups == nil {
16133 @EmptyState("Keybindings unavailable")
17134 } else if len(groups) == 0 {
@@ -39,7 +156,7 @@ templ ConfigKeybindingGroup(group renderedKeybindingGroup) {
39156 } else {
40157 <ul class="sv-list">
41158 for _, binding := range group.Bindings {
42- @ConfigKeybinding(binding)
159+ @ConfigKeybinding(group, binding)
43160 }
44161 </ul>
45162 }
@@ -47,21 +164,28 @@ templ ConfigKeybindingGroup(group renderedKeybindingGroup) {
47164 </div>
48165 }
49166
50-templ ConfigKeybinding(binding renderedKeybinding) {
167+templ ConfigKeybinding(group renderedKeybindingGroup, binding renderedKeybinding) {
51168 <li class="sv-list-row">
52169 <div class="sv-keybinding-row">
53170 <div>
54171 <strong>{ keybindingDescription(binding) }</strong>
55172 <p class="sv-meta">{ keybindingAction(binding) }</p>
173+ <div class="sv-binding-keys">
174+ @KeyList(binding.Keys)
175+ <span class="sv-badge">{ keybindingStatus(binding) }</span>
176+ if keybindingIsCustom(binding) && len(binding.DefaultKeys) > 0 {
177+ <span class="sv-meta">default:</span>
178+ @KeyList(binding.DefaultKeys)
179+ }
180+ </div>
56181 </div>
57- <div class="sv-keybinding-keys">
58- @KeyList(binding.Keys)
59- <span class="sv-badge">{ keybindingStatus(binding) }</span>
60- if keybindingIsCustom(binding) && len(binding.DefaultKeys) > 0 {
61- <span class="sv-meta">default:</span>
62- @KeyList(binding.DefaultKeys)
63- }
64- </div>
182+ <form class="sv-inline-form" data-sv-action="config.keybinding.set">
183+ <input type="hidden" name="mode" value={ group.Mode }/>
184+ <input type="hidden" name="action" value={ binding.Action }/>
185+ <label><span>Keys</span><input name="keys" type="text" value={ keybindingKeysValue(binding.Keys) } placeholder="ctrl+h" required/></label>
186+ <button class="sv-button sv-button-secondary" type="submit">Save</button>
187+ <button class="sv-button sv-button-secondary" type="button" data-sv-action="config.keybinding.reset" data-mode={ group.Mode } data-action={ binding.Action }>Reset</button>
188+ </form>
65189 </div>
66190 </li>
67191 }
@@ -1,17 +1,134 @@
1 package systemviews1 package systemviews
2 2
3+import "github.com/bnema/dumber/internal/application/port"
4+
3 templ ConfigView(data configRenderData) {5 templ ConfigView(data configRenderData) {
4- @Section("sv-config-values", "Config values") {6+ @Alert("success", data.Notice)
5- @KV(flattenValues(data.Config))7+ @Alert("error", data.Error)
8+ @Section("sv-config-appearance", "Appearance") {
9+ @AppearanceForm(data.Config)
10+ }
11+ @Section("sv-config-search", "Search") {
12+ @SearchSettings(data.Config)
13+ }
14+ @Section("sv-config-performance", "Performance") {
15+ @PerformanceSettings(data.Config)
6 }16 }
7 @Section("sv-config-keybindings", "Keybindings") {17 @Section("sv-config-keybindings", "Keybindings") {
8 @Meta(configKeybindingSummary(data.Keybindings))18 @Meta(configKeybindingSummary(data.Keybindings))
9 @ConfigKeybindings(data.Keybindings)19 @ConfigKeybindings(data.Keybindings)
10 }20 }
21+ @Section("sv-config-values", "Current payload") {
22+ @KV(flattenValues(data.Config))
23+ }
24+}
25+
26+templ AppearanceForm(config port.SystemviewConfigPayload) {
27+ <form class="sv-card-form sv-config-form" data-sv-action="config.appearance.save">
28+ <label><span>Sans font</span><input name="sans_font" type="text" value={ config.Appearance.SansFont } required/></label>
29+ <label><span>Serif font</span><input name="serif_font" type="text" value={ config.Appearance.SerifFont } required/></label>
30+ <label><span>Monospace font</span><input name="monospace_font" type="text" value={ config.Appearance.MonospaceFont } required/></label>
31+ <label><span>Default font size</span><input name="default_font_size" type="number" min="1" max="72" value={ formatConfigInt(config.Appearance.DefaultFontSize) } required/></label>
32+ <label><span>UI scale</span><input name="default_ui_scale" type="number" min="0.5" max="3" step="0.05" value={ formatConfigFloat(config.DefaultUIScale) } required/></label>
33+ <label><span>Color scheme</span><select name="color_scheme">
34+ <option value="default" selected?={ colorSchemeSelected(config.Appearance.ColorScheme, "default") }>System default</option>
35+ <option value="prefer-light" selected?={ colorSchemeSelected(config.Appearance.ColorScheme, "prefer-light") }>Prefer light</option>
36+ <option value="prefer-dark" selected?={ colorSchemeSelected(config.Appearance.ColorScheme, "prefer-dark") }>Prefer dark</option>
37+ </select></label>
38+ @PaletteFields("Light palette", "light", config.Appearance.LightPalette)
39+ @PaletteFields("Dark palette", "dark", config.Appearance.DarkPalette)
40+ <div class="sv-button-row sv-form-actions">
41+ <button class="sv-button" type="submit">Save appearance</button>
42+ <button class="sv-button sv-button-secondary" type="button" data-sv-action="config.appearance.reset" data-sv-confirm="Reset appearance settings to defaults?">Reset appearance</button>
43+ </div>
44+ </form>
45+}
46+
47+templ PaletteFields(title string, prefix string, palette port.ColorPalette) {
48+ <fieldset class="sv-fieldset">
49+ <legend>{ title }</legend>
50+ <label><span>Background</span><input name={ prefix + "_background" } type="text" value={ palette.Background } required/></label>
51+ <label><span>Surface</span><input name={ prefix + "_surface" } type="text" value={ palette.Surface } required/></label>
52+ <label><span>Surface variant</span><input name={ prefix + "_surface_variant" } type="text" value={ palette.SurfaceVariant } required/></label>
53+ <label><span>Text</span><input name={ prefix + "_text" } type="text" value={ palette.Text } required/></label>
54+ <label><span>Muted</span><input name={ prefix + "_muted" } type="text" value={ palette.Muted } required/></label>
55+ <label><span>Accent</span><input name={ prefix + "_accent" } type="text" value={ palette.Accent } required/></label>
56+ <label><span>Border</span><input name={ prefix + "_border" } type="text" value={ palette.Border } required/></label>
57+ </fieldset>
58+}
59+
60+templ SearchSettings(config port.SystemviewConfigPayload) {
61+ <form class="sv-card-form" data-sv-action="config.search.save">
62+ <label><span>Default search engine</span><input name="default_search_engine" type="text" value={ config.DefaultSearchEngine } placeholder="https://duckduckgo.com/?q=%s" required/></label>
63+ <button class="sv-button" type="submit">Save search default</button>
64+ </form>
65+ <div class="sv-group">
66+ <div class="sv-group-header"><h4>Search shortcuts</h4></div>
67+ <p class="sv-meta">Shortcut URLs must include the %s placeholder for the search query.</p>
68+ <form class="sv-inline-form" data-sv-action="config.searchShortcut.create">
69+ <label><span>Key</span><input name="key" type="text" placeholder="ddg" required/></label>
70+ <label><span>URL</span><input name="url" type="text" placeholder="https://duckduckgo.com/?q=%s" required/></label>
71+ <label><span>Description</span><input name="description" type="text" placeholder="DuckDuckGo" required/></label>
72+ <button class="sv-button sv-button-secondary" type="submit">Add shortcut</button>
73+ </form>
74+ if len(searchShortcutRows(config.SearchShortcuts)) == 0 {
75+ @EmptyState("No search shortcuts configured")
76+ } else {
77+ <ul class="sv-list">
78+ for _, shortcut := range searchShortcutRows(config.SearchShortcuts) {
79+ @SearchShortcutItem(shortcut)
80+ }
81+ </ul>
82+ }
83+ </div>
84+}
85+
86+templ SearchShortcutItem(shortcut searchShortcutRow) {
87+ <li class="sv-list-row">
88+ <form class="sv-inline-form" data-sv-action="config.searchShortcut.update">
89+ <input type="hidden" name="key" value={ shortcut.Key }/>
90+ <label><span>Key</span><input name="new_key" type="text" value={ shortcut.Key } required/></label>
91+ <label><span>URL</span><input name="url" type="text" value={ shortcut.URL } required/></label>
92+ <label><span>Description</span><input name="description" type="text" value={ shortcut.Description } required/></label>
93+ <button class="sv-button sv-button-secondary" type="submit">Save</button>
94+ <button class="sv-button sv-button-secondary sv-button-danger" type="button" data-sv-action="config.searchShortcut.delete" data-key={ shortcut.Key } data-sv-confirm="Delete this search shortcut?">Delete</button>
95+ </form>
96+ </li>
97+}
98+
99+templ PerformanceSettings(config port.SystemviewConfigPayload) {
100+ <form class="sv-card-form sv-config-form" data-sv-action="config.performance.save">
101+ <label><span>Profile</span><select name="profile">
102+ <option value="default" selected?={ performanceProfileSelected(config.Performance.Profile, "default") }>Default</option>
103+ <option value="lite" selected?={ performanceProfileSelected(config.Performance.Profile, "lite") }>Lite</option>
104+ <option value="max" selected?={ performanceProfileSelected(config.Performance.Profile, "max") }>Max</option>
105+ <option value="custom" selected?={ performanceProfileSelected(config.Performance.Profile, "custom") }>Custom</option>
106+ </select></label>
107+ <label><span>Skia CPU threads</span><input name="skia_cpu_threads" type="number" min="0" max="8" value={ formatConfigInt(config.Performance.Custom.SkiaCPUThreads) }/></label>
108+ <label><span>Skia GPU threads</span><input name="skia_gpu_threads" type="number" min="-1" max="8" value={ formatConfigInt(config.Performance.Custom.SkiaGPUThreads) }/></label>
109+ <label><span>Web process memory MB</span><input name="web_process_memory_mb" type="number" min="0" max="16384" value={ formatConfigInt(config.Performance.Custom.WebProcessMemoryMB) }/></label>
110+ <label><span>Network process memory MB</span><input name="network_process_memory_mb" type="number" min="0" max="4096" value={ formatConfigInt(config.Performance.Custom.NetworkProcessMemoryMB) }/></label>
111+ <label><span>WebView pool prewarm</span><input name="webview_pool_prewarm" type="number" min="0" max="20" value={ formatConfigInt(config.Performance.Custom.WebViewPoolPrewarm) }/></label>
112+ <button class="sv-button" type="submit">Save performance</button>
113+ </form>
114+ <div class="sv-stat-grid">
115+ <div class="sv-stat-card"><span class="sv-stat-value">{ formatConfigInt(config.Performance.Hardware.CPUThreads) }</span><span class="sv-stat-label">CPU threads</span></div>
116+ <div class="sv-stat-card"><span class="sv-stat-value">{ formatConfigInt(config.Performance.Hardware.TotalRAMMB) }</span><span class="sv-stat-label">RAM MB</span></div>
117+ <div class="sv-stat-card"><span class="sv-stat-value">{ config.Performance.Hardware.GPUName }</span><span class="sv-stat-label">GPU</span></div>
118+ <div class="sv-stat-card"><span class="sv-stat-value">{ config.EngineType }</span><span class="sv-stat-label">Engine</span></div>
119+ </div>
120+ if !engineIsWebKit(config.EngineType) {
121+ <p class="sv-meta">Performance tuning applies only to the WebKit engine.</p>
122+ } else {
123+ <p class="sv-meta">Performance profile changes may require restarting the browser to affect existing engine processes.</p>
124+ }
11 }125 }
12 126
13 templ ConfigKeybindings(keybindings any) {127 templ ConfigKeybindings(keybindings any) {
14 {{ groups := configKeybindingGroups(keybindings) }}128 {{ groups := configKeybindingGroups(keybindings) }}
129+ <div class="sv-button-row">
130+ <button class="sv-button sv-button-secondary sv-button-danger" type="button" data-sv-action="config.keybinding.resetAll" data-sv-confirm="Reset all keybindings to defaults?">Reset all keybindings</button>
131+ </div>
15 if groups == nil {132 if groups == nil {
16 @EmptyState("Keybindings unavailable")133 @EmptyState("Keybindings unavailable")
17 } else if len(groups) == 0 {134 } else if len(groups) == 0 {
@@ -39,7 +156,7 @@ templ ConfigKeybindingGroup(group renderedKeybindingGroup) {
39 } else {156 } else {
40 <ul class="sv-list">157 <ul class="sv-list">
41 for _, binding := range group.Bindings {158 for _, binding := range group.Bindings {
42- @ConfigKeybinding(binding)159+ @ConfigKeybinding(group, binding)
43 }160 }
44 </ul>161 </ul>
45 }162 }
@@ -47,21 +164,28 @@ templ ConfigKeybindingGroup(group renderedKeybindingGroup) {
47 </div>164 </div>
48 }165 }
49 166
50-templ ConfigKeybinding(binding renderedKeybinding) {167+templ ConfigKeybinding(group renderedKeybindingGroup, binding renderedKeybinding) {
51 <li class="sv-list-row">168 <li class="sv-list-row">
52 <div class="sv-keybinding-row">169 <div class="sv-keybinding-row">
53 <div>170 <div>
54 <strong>{ keybindingDescription(binding) }</strong>171 <strong>{ keybindingDescription(binding) }</strong>
55 <p class="sv-meta">{ keybindingAction(binding) }</p>172 <p class="sv-meta">{ keybindingAction(binding) }</p>
173+ <div class="sv-binding-keys">
174+ @KeyList(binding.Keys)
175+ <span class="sv-badge">{ keybindingStatus(binding) }</span>
176+ if keybindingIsCustom(binding) && len(binding.DefaultKeys) > 0 {
177+ <span class="sv-meta">default:</span>
178+ @KeyList(binding.DefaultKeys)
179+ }
180+ </div>
56 </div>181 </div>
57- <div class="sv-keybinding-keys">182+ <form class="sv-inline-form" data-sv-action="config.keybinding.set">
58- @KeyList(binding.Keys)183+ <input type="hidden" name="mode" value={ group.Mode }/>
59- <span class="sv-badge">{ keybindingStatus(binding) }</span>184+ <input type="hidden" name="action" value={ binding.Action }/>
60- if keybindingIsCustom(binding) && len(binding.DefaultKeys) > 0 {185+ <label><span>Keys</span><input name="keys" type="text" value={ keybindingKeysValue(binding.Keys) } placeholder="ctrl+h" required/></label>
61- <span class="sv-meta">default:</span>186+ <button class="sv-button sv-button-secondary" type="submit">Save</button>
62- @KeyList(binding.DefaultKeys)187+ <button class="sv-button sv-button-secondary" type="button" data-sv-action="config.keybinding.reset" data-mode={ group.Mode } data-action={ binding.Action }>Reset</button>
63- }188+ </form>
64- </div>
65 </div>189 </div>
66 </li>190 </li>
67 }191 }
A internal/ui/systemviews/config_actions.go +434 -0
@@ -0,0 +1,434 @@
1+package systemviews
2+
3+import (
4+ "context"
5+ "fmt"
6+ "strconv"
7+ "strings"
8+
9+ "github.com/bnema/dumber/internal/application/port"
10+)
11+
12+const (
13+ configActionSaveAppearance = "config.appearance.save"
14+ configActionResetAppearance = "config.appearance.reset"
15+ configActionSaveSearch = "config.search.save"
16+ configActionCreateSearchShortcut = "config.searchShortcut.create"
17+ configActionUpdateSearchShortcut = "config.searchShortcut.update"
18+ configActionDeleteSearchShortcut = "config.searchShortcut.delete"
19+ configActionSavePerformance = "config.performance.save"
20+ configActionSetKeybinding = "config.keybinding.set"
21+ configActionResetKeybinding = "config.keybinding.reset"
22+ configActionResetAllKeybindings = "config.keybinding.resetAll"
23+ configRequestIDPrefix = "systemviews-config"
24+)
25+
26+func (a *App) handleConfigAction(ctx context.Context, event DOMAction) error {
27+ if a.deps.Config == nil {
28+ return fmt.Errorf("config service not configured")
29+ }
30+
31+ switch event.Action {
32+ case configActionSaveAppearance:
33+ return a.saveAppearanceConfig(ctx, event.Data)
34+ case configActionResetAppearance:
35+ return a.resetAppearanceConfig(ctx)
36+ case configActionSaveSearch:
37+ return a.saveSearchConfig(ctx, event.Data)
38+ case configActionCreateSearchShortcut:
39+ return a.createSearchShortcut(ctx, event.Data)
40+ case configActionUpdateSearchShortcut:
41+ return a.updateSearchShortcut(ctx, event.Data)
42+ case configActionDeleteSearchShortcut:
43+ return a.deleteSearchShortcut(ctx, event.Data)
44+ case configActionSavePerformance:
45+ return a.savePerformanceConfig(ctx, event.Data)
46+ case configActionSetKeybinding:
47+ return a.setConfigKeybinding(ctx, event.Data)
48+ case configActionResetKeybinding:
49+ return a.resetConfigKeybinding(ctx, event.Data)
50+ case configActionResetAllKeybindings:
51+ if err := a.deps.Config.ResetAllKeybindings(ctx); err != nil {
52+ return err
53+ }
54+ a.configNotice = "Reset all keybindings to defaults"
55+ }
56+ return nil
57+}
58+
59+func (a *App) saveAppearanceConfig(ctx context.Context, data map[string]string) error {
60+ cfg, err := a.editableConfig(ctx)
61+ if err != nil {
62+ return err
63+ }
64+
65+ fontSize, err := parseConfigInt(data["default_font_size"], "default font size")
66+ if err != nil {
67+ return err
68+ }
69+ uiScale, err := parseConfigFloat(data["default_ui_scale"], "UI scale")
70+ if err != nil {
71+ return err
72+ }
73+
74+ cfg.Appearance.SansFont = strings.TrimSpace(data["sans_font"])
75+ cfg.Appearance.SerifFont = strings.TrimSpace(data["serif_font"])
76+ cfg.Appearance.MonospaceFont = strings.TrimSpace(data["monospace_font"])
77+ cfg.Appearance.DefaultFontSize = fontSize
78+ cfg.Appearance.ColorScheme = strings.TrimSpace(data["color_scheme"])
79+ cfg.Appearance.LightPalette = paletteFromForm(data, "light")
80+ cfg.Appearance.DarkPalette = paletteFromForm(data, "dark")
81+ cfg.DefaultUIScale = uiScale
82+
83+ if err := a.saveEditableConfig(ctx, cfg); err != nil {
84+ return err
85+ }
86+ a.configNotice = "Saved appearance settings"
87+ return nil
88+}
89+
90+func (a *App) resetAppearanceConfig(ctx context.Context) error {
91+ cfg, err := a.editableConfig(ctx)
92+ if err != nil {
93+ return err
94+ }
95+ defaults, err := a.deps.Config.Default(ctx)
96+ if err != nil {
97+ return err
98+ }
99+ cfg.Appearance = defaults.Appearance
100+ cfg.DefaultUIScale = defaults.DefaultUIScale
101+ if err := a.saveEditableConfig(ctx, cfg); err != nil {
102+ return err
103+ }
104+ a.configNotice = "Reset appearance settings to defaults"
105+ return nil
106+}
107+
108+func (a *App) saveSearchConfig(ctx context.Context, data map[string]string) error {
109+ cfg, err := a.editableConfig(ctx)
110+ if err != nil {
111+ return err
112+ }
113+ cfg.DefaultSearchEngine = strings.TrimSpace(data["default_search_engine"])
114+ if err := validateSearchURL(cfg.DefaultSearchEngine, "default search engine"); err != nil {
115+ return err
116+ }
117+ if err := a.saveEditableConfig(ctx, cfg); err != nil {
118+ return err
119+ }
120+ a.configNotice = "Saved default search engine"
121+ return nil
122+}
123+
124+func (a *App) createSearchShortcut(ctx context.Context, data map[string]string) error {
125+ cfg, err := a.editableConfig(ctx)
126+ if err != nil {
127+ return err
128+ }
129+ key, shortcut, err := searchShortcutFromForm(data, "key")
130+ if err != nil {
131+ return err
132+ }
133+ cfg.SearchShortcuts = cloneSearchShortcuts(cfg.SearchShortcuts)
134+ if _, exists := cfg.SearchShortcuts[key]; exists {
135+ return fmt.Errorf("search shortcut %q already exists", key)
136+ }
137+ cfg.SearchShortcuts[key] = shortcut
138+ if err := a.saveEditableConfig(ctx, cfg); err != nil {
139+ return err
140+ }
141+ a.configNotice = "Created search shortcut " + key
142+ return nil
143+}
144+
145+func (a *App) updateSearchShortcut(ctx context.Context, data map[string]string) error {
146+ cfg, err := a.editableConfig(ctx)
147+ if err != nil {
148+ return err
149+ }
150+ oldKey := strings.TrimSpace(data["key"])
151+ if oldKey == "" {
152+ return fmt.Errorf("search shortcut key is required")
153+ }
154+ newKey, shortcut, err := searchShortcutFromForm(data, "new_key")
155+ if err != nil {
156+ return err
157+ }
158+ cfg.SearchShortcuts = cloneSearchShortcuts(cfg.SearchShortcuts)
159+ if _, exists := cfg.SearchShortcuts[oldKey]; !exists {
160+ return fmt.Errorf("search shortcut %q not found", oldKey)
161+ }
162+ if newKey != oldKey {
163+ if _, exists := cfg.SearchShortcuts[newKey]; exists {
164+ return fmt.Errorf("search shortcut %q already exists", newKey)
165+ }
166+ delete(cfg.SearchShortcuts, oldKey)
167+ }
168+ cfg.SearchShortcuts[newKey] = shortcut
169+ if err := a.saveEditableConfig(ctx, cfg); err != nil {
170+ return err
171+ }
172+ a.configNotice = "Saved search shortcut " + newKey
173+ return nil
174+}
175+
176+func (a *App) deleteSearchShortcut(ctx context.Context, data map[string]string) error {
177+ cfg, err := a.editableConfig(ctx)
178+ if err != nil {
179+ return err
180+ }
181+ key := strings.TrimSpace(data["key"])
182+ if key == "" {
183+ return fmt.Errorf("search shortcut key is required")
184+ }
185+ cfg.SearchShortcuts = cloneSearchShortcuts(cfg.SearchShortcuts)
186+ delete(cfg.SearchShortcuts, key)
187+ if err := a.saveEditableConfig(ctx, cfg); err != nil {
188+ return err
189+ }
190+ a.configNotice = "Deleted search shortcut " + key
191+ return nil
192+}
193+
194+func (a *App) savePerformanceConfig(ctx context.Context, data map[string]string) error {
195+ cfg, err := a.editableConfig(ctx)
196+ if err != nil {
197+ return err
198+ }
199+ profile := strings.TrimSpace(data["profile"])
200+ if !validPerformanceProfile(profile) {
201+ return fmt.Errorf("invalid performance profile")
202+ }
203+ skiaCPU, err := parseConfigInt(data["skia_cpu_threads"], "Skia CPU threads")
204+ if err != nil {
205+ return err
206+ }
207+ skiaGPU, err := parseConfigInt(data["skia_gpu_threads"], "Skia GPU threads")
208+ if err != nil {
209+ return err
210+ }
211+ webMemory, err := parseConfigInt(data["web_process_memory_mb"], "web process memory")
212+ if err != nil {
213+ return err
214+ }
215+ networkMemory, err := parseConfigInt(data["network_process_memory_mb"], "network process memory")
216+ if err != nil {
217+ return err
218+ }
219+ prewarm, err := parseConfigInt(data["webview_pool_prewarm"], "WebView pool prewarm")
220+ if err != nil {
221+ return err
222+ }
223+
224+ cfg.Performance.Profile = profile
225+ cfg.Performance.Custom = port.WebUICustomPerformanceConfig{
226+ SkiaCPUThreads: skiaCPU,
227+ SkiaGPUThreads: skiaGPU,
228+ WebProcessMemoryMB: webMemory,
229+ NetworkProcessMemoryMB: networkMemory,
230+ WebViewPoolPrewarm: prewarm,
231+ }
232+ if err := a.saveEditableConfig(ctx, cfg); err != nil {
233+ return err
234+ }
235+ a.configNotice = "Saved performance settings. Restart may be required."
236+ return nil
237+}
238+
239+func (a *App) setConfigKeybinding(ctx context.Context, data map[string]string) error {
240+ mode := strings.TrimSpace(data["mode"])
241+ action := strings.TrimSpace(data["action"])
242+ keys := parseKeyList(data["keys"])
243+ if mode == "" || action == "" {
244+ return fmt.Errorf("mode and action are required")
245+ }
246+ resp, err := a.deps.Config.SetKeybinding(ctx, port.SetKeybindingRequest{
247+ RequestID: configRequestIDPrefix,
248+ Mode: mode,
249+ Action: action,
250+ Keys: keys,
251+ })
252+ if err != nil {
253+ return err
254+ }
255+ if conflicts := keybindingConflictCount(resp); conflicts > 0 {
256+ a.configNotice = fmt.Sprintf("Saved keybinding with %d conflict(s)", conflicts)
257+ return nil
258+ }
259+ a.configNotice = "Saved keybinding " + action
260+ return nil
261+}
262+
263+func (a *App) resetConfigKeybinding(ctx context.Context, data map[string]string) error {
264+ mode := strings.TrimSpace(data["mode"])
265+ action := strings.TrimSpace(data["action"])
266+ if mode == "" || action == "" {
267+ return fmt.Errorf("mode and action are required")
268+ }
269+ if err := a.deps.Config.ResetKeybinding(ctx, port.ResetKeybindingRequest{
270+ RequestID: configRequestIDPrefix,
271+ Mode: mode,
272+ Action: action,
273+ }); err != nil {
274+ return err
275+ }
276+ a.configNotice = "Reset keybinding " + action
277+ return nil
278+}
279+
280+func (a *App) editableConfig(ctx context.Context) (port.WebUIConfig, error) {
281+ payload, err := a.currentConfigPayload(ctx)
282+ if err != nil {
283+ return port.WebUIConfig{}, err
284+ }
285+ return webUIConfigFromPayload(payload), nil
286+}
287+
288+func (a *App) currentConfigPayload(ctx context.Context) (port.SystemviewConfigPayload, error) {
289+ if a.config != nil {
290+ return *a.config, nil
291+ }
292+ cfg, err := a.deps.Config.Current(ctx)
293+ if err != nil {
294+ return port.SystemviewConfigPayload{}, err
295+ }
296+ a.config = &cfg
297+ return cfg, nil
298+}
299+
300+func (a *App) saveEditableConfig(ctx context.Context, cfg port.WebUIConfig) error {
301+ if err := a.deps.Config.Save(ctx, cfg); err != nil {
302+ return err
303+ }
304+ a.config = nil
305+ return nil
306+}
307+
308+func webUIConfigFromPayload(payload port.SystemviewConfigPayload) port.WebUIConfig {
309+ return port.WebUIConfig{
310+ Appearance: payload.Appearance,
311+ DefaultUIScale: payload.DefaultUIScale,
312+ DefaultSearchEngine: payload.DefaultSearchEngine,
313+ SearchShortcuts: cloneSearchShortcuts(payload.SearchShortcuts),
314+ Performance: port.WebUIPerformanceConfig{
315+ Profile: payload.Performance.Profile,
316+ Custom: port.WebUICustomPerformanceConfig{
317+ SkiaCPUThreads: payload.Performance.Custom.SkiaCPUThreads,
318+ SkiaGPUThreads: payload.Performance.Custom.SkiaGPUThreads,
319+ WebProcessMemoryMB: payload.Performance.Custom.WebProcessMemoryMB,
320+ NetworkProcessMemoryMB: payload.Performance.Custom.NetworkProcessMemoryMB,
321+ WebViewPoolPrewarm: payload.Performance.Custom.WebViewPoolPrewarm,
322+ },
323+ },
324+ }
325+}
326+
327+func cloneSearchShortcuts(shortcuts map[string]port.SearchShortcut) map[string]port.SearchShortcut {
328+ clone := make(map[string]port.SearchShortcut, len(shortcuts))
329+ for key, shortcut := range shortcuts {
330+ clone[key] = shortcut
331+ }
332+ return clone
333+}
334+
335+func paletteFromForm(data map[string]string, prefix string) port.ColorPalette {
336+ return port.ColorPalette{
337+ Background: strings.TrimSpace(data[prefix+"_background"]),
338+ Surface: strings.TrimSpace(data[prefix+"_surface"]),
339+ SurfaceVariant: strings.TrimSpace(data[prefix+"_surface_variant"]),
340+ Text: strings.TrimSpace(data[prefix+"_text"]),
341+ Muted: strings.TrimSpace(data[prefix+"_muted"]),
342+ Accent: strings.TrimSpace(data[prefix+"_accent"]),
343+ Border: strings.TrimSpace(data[prefix+"_border"]),
344+ }
345+}
346+
347+func searchShortcutFromForm(data map[string]string, keyField string) (string, port.SearchShortcut, error) {
348+ key := strings.TrimSpace(data[keyField])
349+ url := strings.TrimSpace(data["url"])
350+ description := strings.TrimSpace(data["description"])
351+ if key == "" {
352+ return "", port.SearchShortcut{}, fmt.Errorf("search shortcut key is required")
353+ }
354+ if description == "" {
355+ return "", port.SearchShortcut{}, fmt.Errorf("search shortcut description is required")
356+ }
357+ if err := validateSearchURL(url, "search shortcut URL"); err != nil {
358+ return "", port.SearchShortcut{}, err
359+ }
360+ return key, port.SearchShortcut{URL: url, Description: description}, nil
361+}
362+
363+func validateSearchURL(raw, label string) error {
364+ if strings.TrimSpace(raw) == "" {
365+ return fmt.Errorf("%s is required", label)
366+ }
367+ if !strings.Contains(raw, "%s") {
368+ return fmt.Errorf("%s must contain %%s placeholder", label)
369+ }
370+ return nil
371+}
372+
373+func validPerformanceProfile(profile string) bool {
374+ switch profile {
375+ case "default", "lite", "max", "custom", "":
376+ return true
377+ default:
378+ return false
379+ }
380+}
381+
382+func parseConfigInt(raw, label string) (int, error) {
383+ value, err := strconv.Atoi(strings.TrimSpace(raw))
384+ if err != nil {
385+ return 0, fmt.Errorf("invalid %s", label)
386+ }
387+ return value, nil
388+}
389+
390+func parseConfigFloat(raw, label string) (float64, error) {
391+ value, err := strconv.ParseFloat(strings.TrimSpace(raw), 64)
392+ if err != nil {
393+ return 0, fmt.Errorf("invalid %s", label)
394+ }
395+ return value, nil
396+}
397+
398+func parseKeyList(raw string) []string {
399+ raw = strings.TrimSpace(raw)
400+ if raw == "" {
401+ return nil
402+ }
403+ parts := strings.Split(raw, ",")
404+ if len(parts) == 1 {
405+ parts = strings.Fields(raw)
406+ }
407+ keys := make([]string, 0, len(parts))
408+ for _, part := range parts {
409+ key := strings.TrimSpace(part)
410+ if key != "" {
411+ keys = append(keys, key)
412+ }
413+ }
414+ return keys
415+}
416+
417+func keybindingConflictCount(resp any) int {
418+ switch v := resp.(type) {
419+ case nil:
420+ return 0
421+ case port.SetKeybindingResponse:
422+ return len(v.Conflicts)
423+ case *port.SetKeybindingResponse:
424+ if v == nil {
425+ return 0
426+ }
427+ return len(v.Conflicts)
428+ case map[string]any:
429+ if conflicts, ok := anySlice(v["conflicts"]); ok {
430+ return len(conflicts)
431+ }
432+ }
433+ return 0
434+}
@@ -0,0 +1,434 @@
1+package systemviews
2+
3+import (
4+ "context"
5+ "fmt"
6+ "strconv"
7+ "strings"
8+
9+ "github.com/bnema/dumber/internal/application/port"
10+)
11+
12+const (
13+ configActionSaveAppearance = "config.appearance.save"
14+ configActionResetAppearance = "config.appearance.reset"
15+ configActionSaveSearch = "config.search.save"
16+ configActionCreateSearchShortcut = "config.searchShortcut.create"
17+ configActionUpdateSearchShortcut = "config.searchShortcut.update"
18+ configActionDeleteSearchShortcut = "config.searchShortcut.delete"
19+ configActionSavePerformance = "config.performance.save"
20+ configActionSetKeybinding = "config.keybinding.set"
21+ configActionResetKeybinding = "config.keybinding.reset"
22+ configActionResetAllKeybindings = "config.keybinding.resetAll"
23+ configRequestIDPrefix = "systemviews-config"
24+)
25+
26+func (a *App) handleConfigAction(ctx context.Context, event DOMAction) error {
27+ if a.deps.Config == nil {
28+ return fmt.Errorf("config service not configured")
29+ }
30+
31+ switch event.Action {
32+ case configActionSaveAppearance:
33+ return a.saveAppearanceConfig(ctx, event.Data)
34+ case configActionResetAppearance:
35+ return a.resetAppearanceConfig(ctx)
36+ case configActionSaveSearch:
37+ return a.saveSearchConfig(ctx, event.Data)
38+ case configActionCreateSearchShortcut:
39+ return a.createSearchShortcut(ctx, event.Data)
40+ case configActionUpdateSearchShortcut:
41+ return a.updateSearchShortcut(ctx, event.Data)
42+ case configActionDeleteSearchShortcut:
43+ return a.deleteSearchShortcut(ctx, event.Data)
44+ case configActionSavePerformance:
45+ return a.savePerformanceConfig(ctx, event.Data)
46+ case configActionSetKeybinding:
47+ return a.setConfigKeybinding(ctx, event.Data)
48+ case configActionResetKeybinding:
49+ return a.resetConfigKeybinding(ctx, event.Data)
50+ case configActionResetAllKeybindings:
51+ if err := a.deps.Config.ResetAllKeybindings(ctx); err != nil {
52+ return err
53+ }
54+ a.configNotice = "Reset all keybindings to defaults"
55+ }
56+ return nil
57+}
58+
59+func (a *App) saveAppearanceConfig(ctx context.Context, data map[string]string) error {
60+ cfg, err := a.editableConfig(ctx)
61+ if err != nil {
62+ return err
63+ }
64+
65+ fontSize, err := parseConfigInt(data["default_font_size"], "default font size")
66+ if err != nil {
67+ return err
68+ }
69+ uiScale, err := parseConfigFloat(data["default_ui_scale"], "UI scale")
70+ if err != nil {
71+ return err
72+ }
73+
74+ cfg.Appearance.SansFont = strings.TrimSpace(data["sans_font"])
75+ cfg.Appearance.SerifFont = strings.TrimSpace(data["serif_font"])
76+ cfg.Appearance.MonospaceFont = strings.TrimSpace(data["monospace_font"])
77+ cfg.Appearance.DefaultFontSize = fontSize
78+ cfg.Appearance.ColorScheme = strings.TrimSpace(data["color_scheme"])
79+ cfg.Appearance.LightPalette = paletteFromForm(data, "light")
80+ cfg.Appearance.DarkPalette = paletteFromForm(data, "dark")
81+ cfg.DefaultUIScale = uiScale
82+
83+ if err := a.saveEditableConfig(ctx, cfg); err != nil {
84+ return err
85+ }
86+ a.configNotice = "Saved appearance settings"
87+ return nil
88+}
89+
90+func (a *App) resetAppearanceConfig(ctx context.Context) error {
91+ cfg, err := a.editableConfig(ctx)
92+ if err != nil {
93+ return err
94+ }
95+ defaults, err := a.deps.Config.Default(ctx)
96+ if err != nil {
97+ return err
98+ }
99+ cfg.Appearance = defaults.Appearance
100+ cfg.DefaultUIScale = defaults.DefaultUIScale
101+ if err := a.saveEditableConfig(ctx, cfg); err != nil {
102+ return err
103+ }
104+ a.configNotice = "Reset appearance settings to defaults"
105+ return nil
106+}
107+
108+func (a *App) saveSearchConfig(ctx context.Context, data map[string]string) error {
109+ cfg, err := a.editableConfig(ctx)
110+ if err != nil {
111+ return err
112+ }
113+ cfg.DefaultSearchEngine = strings.TrimSpace(data["default_search_engine"])
114+ if err := validateSearchURL(cfg.DefaultSearchEngine, "default search engine"); err != nil {
115+ return err
116+ }
117+ if err := a.saveEditableConfig(ctx, cfg); err != nil {
118+ return err
119+ }
120+ a.configNotice = "Saved default search engine"
121+ return nil
122+}
123+
124+func (a *App) createSearchShortcut(ctx context.Context, data map[string]string) error {
125+ cfg, err := a.editableConfig(ctx)
126+ if err != nil {
127+ return err
128+ }
129+ key, shortcut, err := searchShortcutFromForm(data, "key")
130+ if err != nil {
131+ return err
132+ }
133+ cfg.SearchShortcuts = cloneSearchShortcuts(cfg.SearchShortcuts)
134+ if _, exists := cfg.SearchShortcuts[key]; exists {
135+ return fmt.Errorf("search shortcut %q already exists", key)
136+ }
137+ cfg.SearchShortcuts[key] = shortcut
138+ if err := a.saveEditableConfig(ctx, cfg); err != nil {
139+ return err
140+ }
141+ a.configNotice = "Created search shortcut " + key
142+ return nil
143+}
144+
145+func (a *App) updateSearchShortcut(ctx context.Context, data map[string]string) error {
146+ cfg, err := a.editableConfig(ctx)
147+ if err != nil {
148+ return err
149+ }
150+ oldKey := strings.TrimSpace(data["key"])
151+ if oldKey == "" {
152+ return fmt.Errorf("search shortcut key is required")
153+ }
154+ newKey, shortcut, err := searchShortcutFromForm(data, "new_key")
155+ if err != nil {
156+ return err
157+ }
158+ cfg.SearchShortcuts = cloneSearchShortcuts(cfg.SearchShortcuts)
159+ if _, exists := cfg.SearchShortcuts[oldKey]; !exists {
160+ return fmt.Errorf("search shortcut %q not found", oldKey)
161+ }
162+ if newKey != oldKey {
163+ if _, exists := cfg.SearchShortcuts[newKey]; exists {
164+ return fmt.Errorf("search shortcut %q already exists", newKey)
165+ }
166+ delete(cfg.SearchShortcuts, oldKey)
167+ }
168+ cfg.SearchShortcuts[newKey] = shortcut
169+ if err := a.saveEditableConfig(ctx, cfg); err != nil {
170+ return err
171+ }
172+ a.configNotice = "Saved search shortcut " + newKey
173+ return nil
174+}
175+
176+func (a *App) deleteSearchShortcut(ctx context.Context, data map[string]string) error {
177+ cfg, err := a.editableConfig(ctx)
178+ if err != nil {
179+ return err
180+ }
181+ key := strings.TrimSpace(data["key"])
182+ if key == "" {
183+ return fmt.Errorf("search shortcut key is required")
184+ }
185+ cfg.SearchShortcuts = cloneSearchShortcuts(cfg.SearchShortcuts)
186+ delete(cfg.SearchShortcuts, key)
187+ if err := a.saveEditableConfig(ctx, cfg); err != nil {
188+ return err
189+ }
190+ a.configNotice = "Deleted search shortcut " + key
191+ return nil
192+}
193+
194+func (a *App) savePerformanceConfig(ctx context.Context, data map[string]string) error {
195+ cfg, err := a.editableConfig(ctx)
196+ if err != nil {
197+ return err
198+ }
199+ profile := strings.TrimSpace(data["profile"])
200+ if !validPerformanceProfile(profile) {
201+ return fmt.Errorf("invalid performance profile")
202+ }
203+ skiaCPU, err := parseConfigInt(data["skia_cpu_threads"], "Skia CPU threads")
204+ if err != nil {
205+ return err
206+ }
207+ skiaGPU, err := parseConfigInt(data["skia_gpu_threads"], "Skia GPU threads")
208+ if err != nil {
209+ return err
210+ }
211+ webMemory, err := parseConfigInt(data["web_process_memory_mb"], "web process memory")
212+ if err != nil {
213+ return err
214+ }
215+ networkMemory, err := parseConfigInt(data["network_process_memory_mb"], "network process memory")
216+ if err != nil {
217+ return err
218+ }
219+ prewarm, err := parseConfigInt(data["webview_pool_prewarm"], "WebView pool prewarm")
220+ if err != nil {
221+ return err
222+ }
223+
224+ cfg.Performance.Profile = profile
225+ cfg.Performance.Custom = port.WebUICustomPerformanceConfig{
226+ SkiaCPUThreads: skiaCPU,
227+ SkiaGPUThreads: skiaGPU,
228+ WebProcessMemoryMB: webMemory,
229+ NetworkProcessMemoryMB: networkMemory,
230+ WebViewPoolPrewarm: prewarm,
231+ }
232+ if err := a.saveEditableConfig(ctx, cfg); err != nil {
233+ return err
234+ }
235+ a.configNotice = "Saved performance settings. Restart may be required."
236+ return nil
237+}
238+
239+func (a *App) setConfigKeybinding(ctx context.Context, data map[string]string) error {
240+ mode := strings.TrimSpace(data["mode"])
241+ action := strings.TrimSpace(data["action"])
242+ keys := parseKeyList(data["keys"])
243+ if mode == "" || action == "" {
244+ return fmt.Errorf("mode and action are required")
245+ }
246+ resp, err := a.deps.Config.SetKeybinding(ctx, port.SetKeybindingRequest{
247+ RequestID: configRequestIDPrefix,
248+ Mode: mode,
249+ Action: action,
250+ Keys: keys,
251+ })
252+ if err != nil {
253+ return err
254+ }
255+ if conflicts := keybindingConflictCount(resp); conflicts > 0 {
256+ a.configNotice = fmt.Sprintf("Saved keybinding with %d conflict(s)", conflicts)
257+ return nil
258+ }
259+ a.configNotice = "Saved keybinding " + action
260+ return nil
261+}
262+
263+func (a *App) resetConfigKeybinding(ctx context.Context, data map[string]string) error {
264+ mode := strings.TrimSpace(data["mode"])
265+ action := strings.TrimSpace(data["action"])
266+ if mode == "" || action == "" {
267+ return fmt.Errorf("mode and action are required")
268+ }
269+ if err := a.deps.Config.ResetKeybinding(ctx, port.ResetKeybindingRequest{
270+ RequestID: configRequestIDPrefix,
271+ Mode: mode,
272+ Action: action,
273+ }); err != nil {
274+ return err
275+ }
276+ a.configNotice = "Reset keybinding " + action
277+ return nil
278+}
279+
280+func (a *App) editableConfig(ctx context.Context) (port.WebUIConfig, error) {
281+ payload, err := a.currentConfigPayload(ctx)
282+ if err != nil {
283+ return port.WebUIConfig{}, err
284+ }
285+ return webUIConfigFromPayload(payload), nil
286+}
287+
288+func (a *App) currentConfigPayload(ctx context.Context) (port.SystemviewConfigPayload, error) {
289+ if a.config != nil {
290+ return *a.config, nil
291+ }
292+ cfg, err := a.deps.Config.Current(ctx)
293+ if err != nil {
294+ return port.SystemviewConfigPayload{}, err
295+ }
296+ a.config = &cfg
297+ return cfg, nil
298+}
299+
300+func (a *App) saveEditableConfig(ctx context.Context, cfg port.WebUIConfig) error {
301+ if err := a.deps.Config.Save(ctx, cfg); err != nil {
302+ return err
303+ }
304+ a.config = nil
305+ return nil
306+}
307+
308+func webUIConfigFromPayload(payload port.SystemviewConfigPayload) port.WebUIConfig {
309+ return port.WebUIConfig{
310+ Appearance: payload.Appearance,
311+ DefaultUIScale: payload.DefaultUIScale,
312+ DefaultSearchEngine: payload.DefaultSearchEngine,
313+ SearchShortcuts: cloneSearchShortcuts(payload.SearchShortcuts),
314+ Performance: port.WebUIPerformanceConfig{
315+ Profile: payload.Performance.Profile,
316+ Custom: port.WebUICustomPerformanceConfig{
317+ SkiaCPUThreads: payload.Performance.Custom.SkiaCPUThreads,
318+ SkiaGPUThreads: payload.Performance.Custom.SkiaGPUThreads,
319+ WebProcessMemoryMB: payload.Performance.Custom.WebProcessMemoryMB,
320+ NetworkProcessMemoryMB: payload.Performance.Custom.NetworkProcessMemoryMB,
321+ WebViewPoolPrewarm: payload.Performance.Custom.WebViewPoolPrewarm,
322+ },
323+ },
324+ }
325+}
326+
327+func cloneSearchShortcuts(shortcuts map[string]port.SearchShortcut) map[string]port.SearchShortcut {
328+ clone := make(map[string]port.SearchShortcut, len(shortcuts))
329+ for key, shortcut := range shortcuts {
330+ clone[key] = shortcut
331+ }
332+ return clone
333+}
334+
335+func paletteFromForm(data map[string]string, prefix string) port.ColorPalette {
336+ return port.ColorPalette{
337+ Background: strings.TrimSpace(data[prefix+"_background"]),
338+ Surface: strings.TrimSpace(data[prefix+"_surface"]),
339+ SurfaceVariant: strings.TrimSpace(data[prefix+"_surface_variant"]),
340+ Text: strings.TrimSpace(data[prefix+"_text"]),
341+ Muted: strings.TrimSpace(data[prefix+"_muted"]),
342+ Accent: strings.TrimSpace(data[prefix+"_accent"]),
343+ Border: strings.TrimSpace(data[prefix+"_border"]),
344+ }
345+}
346+
347+func searchShortcutFromForm(data map[string]string, keyField string) (string, port.SearchShortcut, error) {
348+ key := strings.TrimSpace(data[keyField])
349+ url := strings.TrimSpace(data["url"])
350+ description := strings.TrimSpace(data["description"])
351+ if key == "" {
352+ return "", port.SearchShortcut{}, fmt.Errorf("search shortcut key is required")
353+ }
354+ if description == "" {
355+ return "", port.SearchShortcut{}, fmt.Errorf("search shortcut description is required")
356+ }
357+ if err := validateSearchURL(url, "search shortcut URL"); err != nil {
358+ return "", port.SearchShortcut{}, err
359+ }
360+ return key, port.SearchShortcut{URL: url, Description: description}, nil
361+}
362+
363+func validateSearchURL(raw, label string) error {
364+ if strings.TrimSpace(raw) == "" {
365+ return fmt.Errorf("%s is required", label)
366+ }
367+ if !strings.Contains(raw, "%s") {
368+ return fmt.Errorf("%s must contain %%s placeholder", label)
369+ }
370+ return nil
371+}
372+
373+func validPerformanceProfile(profile string) bool {
374+ switch profile {
375+ case "default", "lite", "max", "custom", "":
376+ return true
377+ default:
378+ return false
379+ }
380+}
381+
382+func parseConfigInt(raw, label string) (int, error) {
383+ value, err := strconv.Atoi(strings.TrimSpace(raw))
384+ if err != nil {
385+ return 0, fmt.Errorf("invalid %s", label)
386+ }
387+ return value, nil
388+}
389+
390+func parseConfigFloat(raw, label string) (float64, error) {
391+ value, err := strconv.ParseFloat(strings.TrimSpace(raw), 64)
392+ if err != nil {
393+ return 0, fmt.Errorf("invalid %s", label)
394+ }
395+ return value, nil
396+}
397+
398+func parseKeyList(raw string) []string {
399+ raw = strings.TrimSpace(raw)
400+ if raw == "" {
401+ return nil
402+ }
403+ parts := strings.Split(raw, ",")
404+ if len(parts) == 1 {
405+ parts = strings.Fields(raw)
406+ }
407+ keys := make([]string, 0, len(parts))
408+ for _, part := range parts {
409+ key := strings.TrimSpace(part)
410+ if key != "" {
411+ keys = append(keys, key)
412+ }
413+ }
414+ return keys
415+}
416+
417+func keybindingConflictCount(resp any) int {
418+ switch v := resp.(type) {
419+ case nil:
420+ return 0
421+ case port.SetKeybindingResponse:
422+ return len(v.Conflicts)
423+ case *port.SetKeybindingResponse:
424+ if v == nil {
425+ return 0
426+ }
427+ return len(v.Conflicts)
428+ case map[string]any:
429+ if conflicts, ok := anySlice(v["conflicts"]); ok {
430+ return len(conflicts)
431+ }
432+ }
433+ return 0
434+}
M internal/ui/systemviews/config_templ.go +946 -83
@@ -7,6 +7,8 @@ package systemviews
77 import "github.com/a-h/templ"
88 import templruntime "github.com/a-h/templ/runtime"
99
10+import "github.com/bnema/dumber/internal/application/port"
11+
1012 func ConfigView(data configRenderData) templ.Component {
1113 return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
1214 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
@@ -23,63 +25,855 @@ func ConfigView(data configRenderData) templ.Component {
2325 }()
2426 }
2527 ctx = templ.InitializeContext(ctx)
26- templ_7745c5c3_Var1 := templ.GetChildren(ctx)
27- if templ_7745c5c3_Var1 == nil {
28- templ_7745c5c3_Var1 = templ.NopComponent
28+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
29+ if templ_7745c5c3_Var1 == nil {
30+ templ_7745c5c3_Var1 = templ.NopComponent
31+ }
32+ ctx = templ.ClearChildren(ctx)
33+ templ_7745c5c3_Err = Alert("success", data.Notice).Render(ctx, templ_7745c5c3_Buffer)
34+ if templ_7745c5c3_Err != nil {
35+ return templ_7745c5c3_Err
36+ }
37+ templ_7745c5c3_Err = Alert("error", data.Error).Render(ctx, templ_7745c5c3_Buffer)
38+ if templ_7745c5c3_Err != nil {
39+ return templ_7745c5c3_Err
40+ }
41+ templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
42+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
43+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
44+ if !templ_7745c5c3_IsBuffer {
45+ defer func() {
46+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
47+ if templ_7745c5c3_Err == nil {
48+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
49+ }
50+ }()
51+ }
52+ ctx = templ.InitializeContext(ctx)
53+ templ_7745c5c3_Err = AppearanceForm(data.Config).Render(ctx, templ_7745c5c3_Buffer)
54+ if templ_7745c5c3_Err != nil {
55+ return templ_7745c5c3_Err
56+ }
57+ return nil
58+ })
59+ templ_7745c5c3_Err = Section("sv-config-appearance", "Appearance").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
60+ if templ_7745c5c3_Err != nil {
61+ return templ_7745c5c3_Err
62+ }
63+ templ_7745c5c3_Var3 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
64+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
65+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
66+ if !templ_7745c5c3_IsBuffer {
67+ defer func() {
68+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
69+ if templ_7745c5c3_Err == nil {
70+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
71+ }
72+ }()
73+ }
74+ ctx = templ.InitializeContext(ctx)
75+ templ_7745c5c3_Err = SearchSettings(data.Config).Render(ctx, templ_7745c5c3_Buffer)
76+ if templ_7745c5c3_Err != nil {
77+ return templ_7745c5c3_Err
78+ }
79+ return nil
80+ })
81+ templ_7745c5c3_Err = Section("sv-config-search", "Search").Render(templ.WithChildren(ctx, templ_7745c5c3_Var3), templ_7745c5c3_Buffer)
82+ if templ_7745c5c3_Err != nil {
83+ return templ_7745c5c3_Err
84+ }
85+ templ_7745c5c3_Var4 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
86+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
87+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
88+ if !templ_7745c5c3_IsBuffer {
89+ defer func() {
90+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
91+ if templ_7745c5c3_Err == nil {
92+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
93+ }
94+ }()
95+ }
96+ ctx = templ.InitializeContext(ctx)
97+ templ_7745c5c3_Err = PerformanceSettings(data.Config).Render(ctx, templ_7745c5c3_Buffer)
98+ if templ_7745c5c3_Err != nil {
99+ return templ_7745c5c3_Err
100+ }
101+ return nil
102+ })
103+ templ_7745c5c3_Err = Section("sv-config-performance", "Performance").Render(templ.WithChildren(ctx, templ_7745c5c3_Var4), templ_7745c5c3_Buffer)
104+ if templ_7745c5c3_Err != nil {
105+ return templ_7745c5c3_Err
106+ }
107+ templ_7745c5c3_Var5 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
108+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
109+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
110+ if !templ_7745c5c3_IsBuffer {
111+ defer func() {
112+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
113+ if templ_7745c5c3_Err == nil {
114+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
115+ }
116+ }()
117+ }
118+ ctx = templ.InitializeContext(ctx)
119+ templ_7745c5c3_Err = Meta(configKeybindingSummary(data.Keybindings)).Render(ctx, templ_7745c5c3_Buffer)
120+ if templ_7745c5c3_Err != nil {
121+ return templ_7745c5c3_Err
122+ }
123+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, " ")
124+ if templ_7745c5c3_Err != nil {
125+ return templ_7745c5c3_Err
126+ }
127+ templ_7745c5c3_Err = ConfigKeybindings(data.Keybindings).Render(ctx, templ_7745c5c3_Buffer)
128+ if templ_7745c5c3_Err != nil {
129+ return templ_7745c5c3_Err
130+ }
131+ return nil
132+ })
133+ templ_7745c5c3_Err = Section("sv-config-keybindings", "Keybindings").Render(templ.WithChildren(ctx, templ_7745c5c3_Var5), templ_7745c5c3_Buffer)
134+ if templ_7745c5c3_Err != nil {
135+ return templ_7745c5c3_Err
136+ }
137+ templ_7745c5c3_Var6 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
138+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
139+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
140+ if !templ_7745c5c3_IsBuffer {
141+ defer func() {
142+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
143+ if templ_7745c5c3_Err == nil {
144+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
145+ }
146+ }()
147+ }
148+ ctx = templ.InitializeContext(ctx)
149+ templ_7745c5c3_Err = KV(flattenValues(data.Config)).Render(ctx, templ_7745c5c3_Buffer)
150+ if templ_7745c5c3_Err != nil {
151+ return templ_7745c5c3_Err
152+ }
153+ return nil
154+ })
155+ templ_7745c5c3_Err = Section("sv-config-values", "Current payload").Render(templ.WithChildren(ctx, templ_7745c5c3_Var6), templ_7745c5c3_Buffer)
156+ if templ_7745c5c3_Err != nil {
157+ return templ_7745c5c3_Err
158+ }
159+ return nil
160+ })
161+}
162+
163+func AppearanceForm(config port.SystemviewConfigPayload) templ.Component {
164+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
165+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
166+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
167+ return templ_7745c5c3_CtxErr
168+ }
169+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
170+ if !templ_7745c5c3_IsBuffer {
171+ defer func() {
172+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
173+ if templ_7745c5c3_Err == nil {
174+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
175+ }
176+ }()
177+ }
178+ ctx = templ.InitializeContext(ctx)
179+ templ_7745c5c3_Var7 := templ.GetChildren(ctx)
180+ if templ_7745c5c3_Var7 == nil {
181+ templ_7745c5c3_Var7 = templ.NopComponent
182+ }
183+ ctx = templ.ClearChildren(ctx)
184+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<form class=\"sv-card-form sv-config-form\" data-sv-action=\"config.appearance.save\"><label><span>Sans font</span><input name=\"sans_font\" type=\"text\" value=\"")
185+ if templ_7745c5c3_Err != nil {
186+ return templ_7745c5c3_Err
187+ }
188+ var templ_7745c5c3_Var8 string
189+ templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(config.Appearance.SansFont)
190+ if templ_7745c5c3_Err != nil {
191+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 28, Col: 101}
192+ }
193+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
194+ if templ_7745c5c3_Err != nil {
195+ return templ_7745c5c3_Err
196+ }
197+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\" required></label> <label><span>Serif font</span><input name=\"serif_font\" type=\"text\" value=\"")
198+ if templ_7745c5c3_Err != nil {
199+ return templ_7745c5c3_Err
200+ }
201+ var templ_7745c5c3_Var9 string
202+ templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(config.Appearance.SerifFont)
203+ if templ_7745c5c3_Err != nil {
204+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 29, Col: 104}
205+ }
206+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
207+ if templ_7745c5c3_Err != nil {
208+ return templ_7745c5c3_Err
209+ }
210+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\" required></label> <label><span>Monospace font</span><input name=\"monospace_font\" type=\"text\" value=\"")
211+ if templ_7745c5c3_Err != nil {
212+ return templ_7745c5c3_Err
213+ }
214+ var templ_7745c5c3_Var10 string
215+ templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(config.Appearance.MonospaceFont)
216+ if templ_7745c5c3_Err != nil {
217+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 30, Col: 116}
218+ }
219+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
220+ if templ_7745c5c3_Err != nil {
221+ return templ_7745c5c3_Err
222+ }
223+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\" required></label> <label><span>Default font size</span><input name=\"default_font_size\" type=\"number\" min=\"1\" max=\"72\" value=\"")
224+ if templ_7745c5c3_Err != nil {
225+ return templ_7745c5c3_Err
226+ }
227+ var templ_7745c5c3_Var11 string
228+ templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(formatConfigInt(config.Appearance.DefaultFontSize))
229+ if templ_7745c5c3_Err != nil {
230+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 31, Col: 160}
231+ }
232+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
233+ if templ_7745c5c3_Err != nil {
234+ return templ_7745c5c3_Err
235+ }
236+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\" required></label> <label><span>UI scale</span><input name=\"default_ui_scale\" type=\"number\" min=\"0.5\" max=\"3\" step=\"0.05\" value=\"")
237+ if templ_7745c5c3_Err != nil {
238+ return templ_7745c5c3_Err
239+ }
240+ var templ_7745c5c3_Var12 string
241+ templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(formatConfigFloat(config.DefaultUIScale))
242+ if templ_7745c5c3_Err != nil {
243+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 32, Col: 153}
244+ }
245+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
246+ if templ_7745c5c3_Err != nil {
247+ return templ_7745c5c3_Err
248+ }
249+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\" required></label> <label><span>Color scheme</span><select name=\"color_scheme\"><option value=\"default\"")
250+ if templ_7745c5c3_Err != nil {
251+ return templ_7745c5c3_Err
252+ }
253+ if colorSchemeSelected(config.Appearance.ColorScheme, "default") {
254+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, " selected")
255+ if templ_7745c5c3_Err != nil {
256+ return templ_7745c5c3_Err
257+ }
258+ }
259+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, ">System default</option> <option value=\"prefer-light\"")
260+ if templ_7745c5c3_Err != nil {
261+ return templ_7745c5c3_Err
262+ }
263+ if colorSchemeSelected(config.Appearance.ColorScheme, "prefer-light") {
264+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, " selected")
265+ if templ_7745c5c3_Err != nil {
266+ return templ_7745c5c3_Err
267+ }
268+ }
269+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, ">Prefer light</option> <option value=\"prefer-dark\"")
270+ if templ_7745c5c3_Err != nil {
271+ return templ_7745c5c3_Err
272+ }
273+ if colorSchemeSelected(config.Appearance.ColorScheme, "prefer-dark") {
274+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, " selected")
275+ if templ_7745c5c3_Err != nil {
276+ return templ_7745c5c3_Err
277+ }
278+ }
279+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, ">Prefer dark</option></select></label>")
280+ if templ_7745c5c3_Err != nil {
281+ return templ_7745c5c3_Err
282+ }
283+ templ_7745c5c3_Err = PaletteFields("Light palette", "light", config.Appearance.LightPalette).Render(ctx, templ_7745c5c3_Buffer)
284+ if templ_7745c5c3_Err != nil {
285+ return templ_7745c5c3_Err
286+ }
287+ templ_7745c5c3_Err = PaletteFields("Dark palette", "dark", config.Appearance.DarkPalette).Render(ctx, templ_7745c5c3_Buffer)
288+ if templ_7745c5c3_Err != nil {
289+ return templ_7745c5c3_Err
290+ }
291+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<div class=\"sv-button-row sv-form-actions\"><button class=\"sv-button\" type=\"submit\">Save appearance</button> <button class=\"sv-button sv-button-secondary\" type=\"button\" data-sv-action=\"config.appearance.reset\" data-sv-confirm=\"Reset appearance settings to defaults?\">Reset appearance</button></div></form>")
292+ if templ_7745c5c3_Err != nil {
293+ return templ_7745c5c3_Err
294+ }
295+ return nil
296+ })
297+}
298+
299+func PaletteFields(title string, prefix string, palette port.ColorPalette) templ.Component {
300+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
301+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
302+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
303+ return templ_7745c5c3_CtxErr
304+ }
305+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
306+ if !templ_7745c5c3_IsBuffer {
307+ defer func() {
308+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
309+ if templ_7745c5c3_Err == nil {
310+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
311+ }
312+ }()
313+ }
314+ ctx = templ.InitializeContext(ctx)
315+ templ_7745c5c3_Var13 := templ.GetChildren(ctx)
316+ if templ_7745c5c3_Var13 == nil {
317+ templ_7745c5c3_Var13 = templ.NopComponent
318+ }
319+ ctx = templ.ClearChildren(ctx)
320+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<fieldset class=\"sv-fieldset\"><legend>")
321+ if templ_7745c5c3_Err != nil {
322+ return templ_7745c5c3_Err
323+ }
324+ var templ_7745c5c3_Var14 string
325+ templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(title)
326+ if templ_7745c5c3_Err != nil {
327+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 49, Col: 17}
328+ }
329+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
330+ if templ_7745c5c3_Err != nil {
331+ return templ_7745c5c3_Err
332+ }
333+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</legend> <label><span>Background</span><input name=\"")
334+ if templ_7745c5c3_Err != nil {
335+ return templ_7745c5c3_Err
336+ }
337+ var templ_7745c5c3_Var15 string
338+ templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(prefix + "_background")
339+ if templ_7745c5c3_Err != nil {
340+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 50, Col: 68}
341+ }
342+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
343+ if templ_7745c5c3_Err != nil {
344+ return templ_7745c5c3_Err
345+ }
346+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\" type=\"text\" value=\"")
347+ if templ_7745c5c3_Err != nil {
348+ return templ_7745c5c3_Err
349+ }
350+ var templ_7745c5c3_Var16 string
351+ templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(palette.Background)
352+ if templ_7745c5c3_Err != nil {
353+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 50, Col: 109}
354+ }
355+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
356+ if templ_7745c5c3_Err != nil {
357+ return templ_7745c5c3_Err
358+ }
359+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "\" required></label> <label><span>Surface</span><input name=\"")
360+ if templ_7745c5c3_Err != nil {
361+ return templ_7745c5c3_Err
362+ }
363+ var templ_7745c5c3_Var17 string
364+ templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(prefix + "_surface")
365+ if templ_7745c5c3_Err != nil {
366+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 51, Col: 62}
367+ }
368+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
369+ if templ_7745c5c3_Err != nil {
370+ return templ_7745c5c3_Err
371+ }
372+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "\" type=\"text\" value=\"")
373+ if templ_7745c5c3_Err != nil {
374+ return templ_7745c5c3_Err
375+ }
376+ var templ_7745c5c3_Var18 string
377+ templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(palette.Surface)
378+ if templ_7745c5c3_Err != nil {
379+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 51, Col: 100}
380+ }
381+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
382+ if templ_7745c5c3_Err != nil {
383+ return templ_7745c5c3_Err
384+ }
385+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\" required></label> <label><span>Surface variant</span><input name=\"")
386+ if templ_7745c5c3_Err != nil {
387+ return templ_7745c5c3_Err
388+ }
389+ var templ_7745c5c3_Var19 string
390+ templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(prefix + "_surface_variant")
391+ if templ_7745c5c3_Err != nil {
392+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 52, Col: 78}
393+ }
394+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
395+ if templ_7745c5c3_Err != nil {
396+ return templ_7745c5c3_Err
397+ }
398+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "\" type=\"text\" value=\"")
399+ if templ_7745c5c3_Err != nil {
400+ return templ_7745c5c3_Err
401+ }
402+ var templ_7745c5c3_Var20 string
403+ templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(palette.SurfaceVariant)
404+ if templ_7745c5c3_Err != nil {
405+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 52, Col: 123}
406+ }
407+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
408+ if templ_7745c5c3_Err != nil {
409+ return templ_7745c5c3_Err
410+ }
411+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "\" required></label> <label><span>Text</span><input name=\"")
412+ if templ_7745c5c3_Err != nil {
413+ return templ_7745c5c3_Err
414+ }
415+ var templ_7745c5c3_Var21 string
416+ templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(prefix + "_text")
417+ if templ_7745c5c3_Err != nil {
418+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 53, Col: 56}
419+ }
420+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
421+ if templ_7745c5c3_Err != nil {
422+ return templ_7745c5c3_Err
423+ }
424+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "\" type=\"text\" value=\"")
425+ if templ_7745c5c3_Err != nil {
426+ return templ_7745c5c3_Err
427+ }
428+ var templ_7745c5c3_Var22 string
429+ templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(palette.Text)
430+ if templ_7745c5c3_Err != nil {
431+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 53, Col: 91}
432+ }
433+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
434+ if templ_7745c5c3_Err != nil {
435+ return templ_7745c5c3_Err
436+ }
437+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\" required></label> <label><span>Muted</span><input name=\"")
438+ if templ_7745c5c3_Err != nil {
439+ return templ_7745c5c3_Err
440+ }
441+ var templ_7745c5c3_Var23 string
442+ templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(prefix + "_muted")
443+ if templ_7745c5c3_Err != nil {
444+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 54, Col: 58}
445+ }
446+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
447+ if templ_7745c5c3_Err != nil {
448+ return templ_7745c5c3_Err
449+ }
450+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "\" type=\"text\" value=\"")
451+ if templ_7745c5c3_Err != nil {
452+ return templ_7745c5c3_Err
453+ }
454+ var templ_7745c5c3_Var24 string
455+ templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(palette.Muted)
456+ if templ_7745c5c3_Err != nil {
457+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 54, Col: 94}
458+ }
459+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
460+ if templ_7745c5c3_Err != nil {
461+ return templ_7745c5c3_Err
462+ }
463+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "\" required></label> <label><span>Accent</span><input name=\"")
464+ if templ_7745c5c3_Err != nil {
465+ return templ_7745c5c3_Err
466+ }
467+ var templ_7745c5c3_Var25 string
468+ templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(prefix + "_accent")
469+ if templ_7745c5c3_Err != nil {
470+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 55, Col: 60}
471+ }
472+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
473+ if templ_7745c5c3_Err != nil {
474+ return templ_7745c5c3_Err
475+ }
476+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "\" type=\"text\" value=\"")
477+ if templ_7745c5c3_Err != nil {
478+ return templ_7745c5c3_Err
479+ }
480+ var templ_7745c5c3_Var26 string
481+ templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(palette.Accent)
482+ if templ_7745c5c3_Err != nil {
483+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 55, Col: 97}
484+ }
485+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26))
486+ if templ_7745c5c3_Err != nil {
487+ return templ_7745c5c3_Err
488+ }
489+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "\" required></label> <label><span>Border</span><input name=\"")
490+ if templ_7745c5c3_Err != nil {
491+ return templ_7745c5c3_Err
492+ }
493+ var templ_7745c5c3_Var27 string
494+ templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(prefix + "_border")
495+ if templ_7745c5c3_Err != nil {
496+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 56, Col: 60}
497+ }
498+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
499+ if templ_7745c5c3_Err != nil {
500+ return templ_7745c5c3_Err
501+ }
502+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "\" type=\"text\" value=\"")
503+ if templ_7745c5c3_Err != nil {
504+ return templ_7745c5c3_Err
505+ }
506+ var templ_7745c5c3_Var28 string
507+ templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(palette.Border)
508+ if templ_7745c5c3_Err != nil {
509+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 56, Col: 97}
510+ }
511+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))
512+ if templ_7745c5c3_Err != nil {
513+ return templ_7745c5c3_Err
514+ }
515+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "\" required></label></fieldset>")
516+ if templ_7745c5c3_Err != nil {
517+ return templ_7745c5c3_Err
518+ }
519+ return nil
520+ })
521+}
522+
523+func SearchSettings(config port.SystemviewConfigPayload) templ.Component {
524+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
525+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
526+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
527+ return templ_7745c5c3_CtxErr
528+ }
529+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
530+ if !templ_7745c5c3_IsBuffer {
531+ defer func() {
532+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
533+ if templ_7745c5c3_Err == nil {
534+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
535+ }
536+ }()
537+ }
538+ ctx = templ.InitializeContext(ctx)
539+ templ_7745c5c3_Var29 := templ.GetChildren(ctx)
540+ if templ_7745c5c3_Var29 == nil {
541+ templ_7745c5c3_Var29 = templ.NopComponent
542+ }
543+ ctx = templ.ClearChildren(ctx)
544+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<form class=\"sv-card-form\" data-sv-action=\"config.search.save\"><label><span>Default search engine</span><input name=\"default_search_engine\" type=\"text\" value=\"")
545+ if templ_7745c5c3_Err != nil {
546+ return templ_7745c5c3_Err
547+ }
548+ var templ_7745c5c3_Var30 string
549+ templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(config.DefaultSearchEngine)
550+ if templ_7745c5c3_Err != nil {
551+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 62, Col: 125}
552+ }
553+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
554+ if templ_7745c5c3_Err != nil {
555+ return templ_7745c5c3_Err
556+ }
557+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "\" placeholder=\"https://duckduckgo.com/?q=%s\" required></label> <button class=\"sv-button\" type=\"submit\">Save search default</button></form><div class=\"sv-group\"><div class=\"sv-group-header\"><h4>Search shortcuts</h4></div><p class=\"sv-meta\">Shortcut URLs must include the %s placeholder for the search query.</p><form class=\"sv-inline-form\" data-sv-action=\"config.searchShortcut.create\"><label><span>Key</span><input name=\"key\" type=\"text\" placeholder=\"ddg\" required></label> <label><span>URL</span><input name=\"url\" type=\"text\" placeholder=\"https://duckduckgo.com/?q=%s\" required></label> <label><span>Description</span><input name=\"description\" type=\"text\" placeholder=\"DuckDuckGo\" required></label> <button class=\"sv-button sv-button-secondary\" type=\"submit\">Add shortcut</button></form>")
558+ if templ_7745c5c3_Err != nil {
559+ return templ_7745c5c3_Err
560+ }
561+ if len(searchShortcutRows(config.SearchShortcuts)) == 0 {
562+ templ_7745c5c3_Err = EmptyState("No search shortcuts configured").Render(ctx, templ_7745c5c3_Buffer)
563+ if templ_7745c5c3_Err != nil {
564+ return templ_7745c5c3_Err
565+ }
566+ } else {
567+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<ul class=\"sv-list\">")
568+ if templ_7745c5c3_Err != nil {
569+ return templ_7745c5c3_Err
570+ }
571+ for _, shortcut := range searchShortcutRows(config.SearchShortcuts) {
572+ templ_7745c5c3_Err = SearchShortcutItem(shortcut).Render(ctx, templ_7745c5c3_Buffer)
573+ if templ_7745c5c3_Err != nil {
574+ return templ_7745c5c3_Err
575+ }
576+ }
577+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "</ul>")
578+ if templ_7745c5c3_Err != nil {
579+ return templ_7745c5c3_Err
580+ }
581+ }
582+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "</div>")
583+ if templ_7745c5c3_Err != nil {
584+ return templ_7745c5c3_Err
585+ }
586+ return nil
587+ })
588+}
589+
590+func SearchShortcutItem(shortcut searchShortcutRow) templ.Component {
591+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
592+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
593+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
594+ return templ_7745c5c3_CtxErr
595+ }
596+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
597+ if !templ_7745c5c3_IsBuffer {
598+ defer func() {
599+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
600+ if templ_7745c5c3_Err == nil {
601+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
602+ }
603+ }()
604+ }
605+ ctx = templ.InitializeContext(ctx)
606+ templ_7745c5c3_Var31 := templ.GetChildren(ctx)
607+ if templ_7745c5c3_Var31 == nil {
608+ templ_7745c5c3_Var31 = templ.NopComponent
29609 }
30610 ctx = templ.ClearChildren(ctx)
31- templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
32- templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
33- templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
34- if !templ_7745c5c3_IsBuffer {
35- defer func() {
36- templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
37- if templ_7745c5c3_Err == nil {
38- templ_7745c5c3_Err = templ_7745c5c3_BufErr
39- }
40- }()
41- }
42- ctx = templ.InitializeContext(ctx)
43- templ_7745c5c3_Err = KV(flattenValues(data.Config)).Render(ctx, templ_7745c5c3_Buffer)
611+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "<li class=\"sv-list-row\"><form class=\"sv-inline-form\" data-sv-action=\"config.searchShortcut.update\"><input type=\"hidden\" name=\"key\" value=\"")
612+ if templ_7745c5c3_Err != nil {
613+ return templ_7745c5c3_Err
614+ }
615+ var templ_7745c5c3_Var32 string
616+ templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(shortcut.Key)
617+ if templ_7745c5c3_Err != nil {
618+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 89, Col: 55}
619+ }
620+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))
621+ if templ_7745c5c3_Err != nil {
622+ return templ_7745c5c3_Err
623+ }
624+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "\"> <label><span>Key</span><input name=\"new_key\" type=\"text\" value=\"")
625+ if templ_7745c5c3_Err != nil {
626+ return templ_7745c5c3_Err
627+ }
628+ var templ_7745c5c3_Var33 string
629+ templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(shortcut.Key)
630+ if templ_7745c5c3_Err != nil {
631+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 90, Col: 80}
632+ }
633+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33))
634+ if templ_7745c5c3_Err != nil {
635+ return templ_7745c5c3_Err
636+ }
637+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "\" required></label> <label><span>URL</span><input name=\"url\" type=\"text\" value=\"")
638+ if templ_7745c5c3_Err != nil {
639+ return templ_7745c5c3_Err
640+ }
641+ var templ_7745c5c3_Var34 string
642+ templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(shortcut.URL)
643+ if templ_7745c5c3_Err != nil {
644+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 91, Col: 76}
645+ }
646+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))
647+ if templ_7745c5c3_Err != nil {
648+ return templ_7745c5c3_Err
649+ }
650+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "\" required></label> <label><span>Description</span><input name=\"description\" type=\"text\" value=\"")
651+ if templ_7745c5c3_Err != nil {
652+ return templ_7745c5c3_Err
653+ }
654+ var templ_7745c5c3_Var35 string
655+ templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(shortcut.Description)
656+ if templ_7745c5c3_Err != nil {
657+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 92, Col: 100}
658+ }
659+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35))
660+ if templ_7745c5c3_Err != nil {
661+ return templ_7745c5c3_Err
662+ }
663+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "\" required></label> <button class=\"sv-button sv-button-secondary\" type=\"submit\">Save</button> <button class=\"sv-button sv-button-secondary sv-button-danger\" type=\"button\" data-sv-action=\"config.searchShortcut.delete\" data-key=\"")
664+ if templ_7745c5c3_Err != nil {
665+ return templ_7745c5c3_Err
666+ }
667+ var templ_7745c5c3_Var36 string
668+ templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(shortcut.Key)
669+ if templ_7745c5c3_Err != nil {
670+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 94, Col: 149}
671+ }
672+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36))
673+ if templ_7745c5c3_Err != nil {
674+ return templ_7745c5c3_Err
675+ }
676+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "\" data-sv-confirm=\"Delete this search shortcut?\">Delete</button></form></li>")
677+ if templ_7745c5c3_Err != nil {
678+ return templ_7745c5c3_Err
679+ }
680+ return nil
681+ })
682+}
683+
684+func PerformanceSettings(config port.SystemviewConfigPayload) templ.Component {
685+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
686+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
687+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
688+ return templ_7745c5c3_CtxErr
689+ }
690+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
691+ if !templ_7745c5c3_IsBuffer {
692+ defer func() {
693+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
694+ if templ_7745c5c3_Err == nil {
695+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
696+ }
697+ }()
698+ }
699+ ctx = templ.InitializeContext(ctx)
700+ templ_7745c5c3_Var37 := templ.GetChildren(ctx)
701+ if templ_7745c5c3_Var37 == nil {
702+ templ_7745c5c3_Var37 = templ.NopComponent
703+ }
704+ ctx = templ.ClearChildren(ctx)
705+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "<form class=\"sv-card-form sv-config-form\" data-sv-action=\"config.performance.save\"><label><span>Profile</span><select name=\"profile\"><option value=\"default\"")
706+ if templ_7745c5c3_Err != nil {
707+ return templ_7745c5c3_Err
708+ }
709+ if performanceProfileSelected(config.Performance.Profile, "default") {
710+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, " selected")
44711 if templ_7745c5c3_Err != nil {
45712 return templ_7745c5c3_Err
46713 }
47- return nil
48- })
49- templ_7745c5c3_Err = Section("sv-config-values", "Config values").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
714+ }
715+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, ">Default</option> <option value=\"lite\"")
50716 if templ_7745c5c3_Err != nil {
51717 return templ_7745c5c3_Err
52718 }
53- templ_7745c5c3_Var3 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
54- templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
55- templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
56- if !templ_7745c5c3_IsBuffer {
57- defer func() {
58- templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
59- if templ_7745c5c3_Err == nil {
60- templ_7745c5c3_Err = templ_7745c5c3_BufErr
61- }
62- }()
63- }
64- ctx = templ.InitializeContext(ctx)
65- templ_7745c5c3_Err = Meta(configKeybindingSummary(data.Keybindings)).Render(ctx, templ_7745c5c3_Buffer)
719+ if performanceProfileSelected(config.Performance.Profile, "lite") {
720+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, " selected")
66721 if templ_7745c5c3_Err != nil {
67722 return templ_7745c5c3_Err
68723 }
69- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, " ")
724+ }
725+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, ">Lite</option> <option value=\"max\"")
726+ if templ_7745c5c3_Err != nil {
727+ return templ_7745c5c3_Err
728+ }
729+ if performanceProfileSelected(config.Performance.Profile, "max") {
730+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, " selected")
70731 if templ_7745c5c3_Err != nil {
71732 return templ_7745c5c3_Err
72733 }
73- templ_7745c5c3_Err = ConfigKeybindings(data.Keybindings).Render(ctx, templ_7745c5c3_Buffer)
734+ }
735+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, ">Max</option> <option value=\"custom\"")
736+ if templ_7745c5c3_Err != nil {
737+ return templ_7745c5c3_Err
738+ }
739+ if performanceProfileSelected(config.Performance.Profile, "custom") {
740+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, " selected")
74741 if templ_7745c5c3_Err != nil {
75742 return templ_7745c5c3_Err
76743 }
77- return nil
78- })
79- templ_7745c5c3_Err = Section("sv-config-keybindings", "Keybindings").Render(templ.WithChildren(ctx, templ_7745c5c3_Var3), templ_7745c5c3_Buffer)
744+ }
745+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, ">Custom</option></select></label> <label><span>Skia CPU threads</span><input name=\"skia_cpu_threads\" type=\"number\" min=\"0\" max=\"8\" value=\"")
746+ if templ_7745c5c3_Err != nil {
747+ return templ_7745c5c3_Err
748+ }
749+ var templ_7745c5c3_Var38 string
750+ templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(formatConfigInt(config.Performance.Custom.SkiaCPUThreads))
751+ if templ_7745c5c3_Err != nil {
752+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 107, Col: 164}
753+ }
754+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38))
755+ if templ_7745c5c3_Err != nil {
756+ return templ_7745c5c3_Err
757+ }
758+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "\"></label> <label><span>Skia GPU threads</span><input name=\"skia_gpu_threads\" type=\"number\" min=\"-1\" max=\"8\" value=\"")
759+ if templ_7745c5c3_Err != nil {
760+ return templ_7745c5c3_Err
761+ }
762+ var templ_7745c5c3_Var39 string
763+ templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(formatConfigInt(config.Performance.Custom.SkiaGPUThreads))
764+ if templ_7745c5c3_Err != nil {
765+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 108, Col: 165}
766+ }
767+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39))
768+ if templ_7745c5c3_Err != nil {
769+ return templ_7745c5c3_Err
770+ }
771+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "\"></label> <label><span>Web process memory MB</span><input name=\"web_process_memory_mb\" type=\"number\" min=\"0\" max=\"16384\" value=\"")
772+ if templ_7745c5c3_Err != nil {
773+ return templ_7745c5c3_Err
774+ }
775+ var templ_7745c5c3_Var40 string
776+ templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs(formatConfigInt(config.Performance.Custom.WebProcessMemoryMB))
777+ if templ_7745c5c3_Err != nil {
778+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 109, Col: 182}
779+ }
780+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40))
781+ if templ_7745c5c3_Err != nil {
782+ return templ_7745c5c3_Err
783+ }
784+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "\"></label> <label><span>Network process memory MB</span><input name=\"network_process_memory_mb\" type=\"number\" min=\"0\" max=\"4096\" value=\"")
785+ if templ_7745c5c3_Err != nil {
786+ return templ_7745c5c3_Err
787+ }
788+ var templ_7745c5c3_Var41 string
789+ templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.JoinStringErrs(formatConfigInt(config.Performance.Custom.NetworkProcessMemoryMB))
790+ if templ_7745c5c3_Err != nil {
791+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 110, Col: 193}
792+ }
793+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var41))
794+ if templ_7745c5c3_Err != nil {
795+ return templ_7745c5c3_Err
796+ }
797+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "\"></label> <label><span>WebView pool prewarm</span><input name=\"webview_pool_prewarm\" type=\"number\" min=\"0\" max=\"20\" value=\"")
798+ if templ_7745c5c3_Err != nil {
799+ return templ_7745c5c3_Err
800+ }
801+ var templ_7745c5c3_Var42 string
802+ templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.JoinStringErrs(formatConfigInt(config.Performance.Custom.WebViewPoolPrewarm))
803+ if templ_7745c5c3_Err != nil {
804+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 111, Col: 177}
805+ }
806+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var42))
807+ if templ_7745c5c3_Err != nil {
808+ return templ_7745c5c3_Err
809+ }
810+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "\"></label> <button class=\"sv-button\" type=\"submit\">Save performance</button></form><div class=\"sv-stat-grid\"><div class=\"sv-stat-card\"><span class=\"sv-stat-value\">")
811+ if templ_7745c5c3_Err != nil {
812+ return templ_7745c5c3_Err
813+ }
814+ var templ_7745c5c3_Var43 string
815+ templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.JoinStringErrs(formatConfigInt(config.Performance.Hardware.CPUThreads))
816+ if templ_7745c5c3_Err != nil {
817+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 115, Col: 113}
818+ }
819+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var43))
820+ if templ_7745c5c3_Err != nil {
821+ return templ_7745c5c3_Err
822+ }
823+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "</span><span class=\"sv-stat-label\">CPU threads</span></div><div class=\"sv-stat-card\"><span class=\"sv-stat-value\">")
824+ if templ_7745c5c3_Err != nil {
825+ return templ_7745c5c3_Err
826+ }
827+ var templ_7745c5c3_Var44 string
828+ templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.JoinStringErrs(formatConfigInt(config.Performance.Hardware.TotalRAMMB))
829+ if templ_7745c5c3_Err != nil {
830+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 116, Col: 113}
831+ }
832+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44))
833+ if templ_7745c5c3_Err != nil {
834+ return templ_7745c5c3_Err
835+ }
836+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "</span><span class=\"sv-stat-label\">RAM MB</span></div><div class=\"sv-stat-card\"><span class=\"sv-stat-value\">")
837+ if templ_7745c5c3_Err != nil {
838+ return templ_7745c5c3_Err
839+ }
840+ var templ_7745c5c3_Var45 string
841+ templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.JoinStringErrs(config.Performance.Hardware.GPUName)
842+ if templ_7745c5c3_Err != nil {
843+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 117, Col: 93}
844+ }
845+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var45))
846+ if templ_7745c5c3_Err != nil {
847+ return templ_7745c5c3_Err
848+ }
849+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "</span><span class=\"sv-stat-label\">GPU</span></div><div class=\"sv-stat-card\"><span class=\"sv-stat-value\">")
850+ if templ_7745c5c3_Err != nil {
851+ return templ_7745c5c3_Err
852+ }
853+ var templ_7745c5c3_Var46 string
854+ templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.JoinStringErrs(config.EngineType)
855+ if templ_7745c5c3_Err != nil {
856+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 118, Col: 75}
857+ }
858+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var46))
80859 if templ_7745c5c3_Err != nil {
81860 return templ_7745c5c3_Err
82861 }
862+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "</span><span class=\"sv-stat-label\">Engine</span></div></div>")
863+ if templ_7745c5c3_Err != nil {
864+ return templ_7745c5c3_Err
865+ }
866+ if !engineIsWebKit(config.EngineType) {
867+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "<p class=\"sv-meta\">Performance tuning applies only to the WebKit engine.</p>")
868+ if templ_7745c5c3_Err != nil {
869+ return templ_7745c5c3_Err
870+ }
871+ } else {
872+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "<p class=\"sv-meta\">Performance profile changes may require restarting the browser to affect existing engine processes.</p>")
873+ if templ_7745c5c3_Err != nil {
874+ return templ_7745c5c3_Err
875+ }
876+ }
83877 return nil
84878 })
85879 }
@@ -100,12 +894,16 @@ func ConfigKeybindings(keybindings any) templ.Component {
100894 }()
101895 }
102896 ctx = templ.InitializeContext(ctx)
103- templ_7745c5c3_Var4 := templ.GetChildren(ctx)
104- if templ_7745c5c3_Var4 == nil {
105- templ_7745c5c3_Var4 = templ.NopComponent
897+ templ_7745c5c3_Var47 := templ.GetChildren(ctx)
898+ if templ_7745c5c3_Var47 == nil {
899+ templ_7745c5c3_Var47 = templ.NopComponent
106900 }
107901 ctx = templ.ClearChildren(ctx)
108902 groups := configKeybindingGroups(keybindings)
903+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "<div class=\"sv-button-row\"><button class=\"sv-button sv-button-secondary sv-button-danger\" type=\"button\" data-sv-action=\"config.keybinding.resetAll\" data-sv-confirm=\"Reset all keybindings to defaults?\">Reset all keybindings</button></div>")
904+ if templ_7745c5c3_Err != nil {
905+ return templ_7745c5c3_Err
906+ }
109907 if groups == nil {
110908 templ_7745c5c3_Err = EmptyState("Keybindings unavailable").Render(ctx, templ_7745c5c3_Buffer)
111909 if templ_7745c5c3_Err != nil {
@@ -144,48 +942,48 @@ func ConfigKeybindingGroup(group renderedKeybindingGroup) templ.Component {
144942 }()
145943 }
146944 ctx = templ.InitializeContext(ctx)
147- templ_7745c5c3_Var5 := templ.GetChildren(ctx)
148- if templ_7745c5c3_Var5 == nil {
149- templ_7745c5c3_Var5 = templ.NopComponent
945+ templ_7745c5c3_Var48 := templ.GetChildren(ctx)
946+ if templ_7745c5c3_Var48 == nil {
947+ templ_7745c5c3_Var48 = templ.NopComponent
150948 }
151949 ctx = templ.ClearChildren(ctx)
152- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"sv-group\"><div class=\"sv-group-header\"><div><h4>")
950+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "<div class=\"sv-group\"><div class=\"sv-group-header\"><div><h4>")
153951 if templ_7745c5c3_Err != nil {
154952 return templ_7745c5c3_Err
155953 }
156- var templ_7745c5c3_Var6 string
157- templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(keybindingGroupTitle(group))
954+ var templ_7745c5c3_Var49 string
955+ templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinStringErrs(keybindingGroupTitle(group))
158956 if templ_7745c5c3_Err != nil {
159- return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 30, Col: 37}
957+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 147, Col: 37}
160958 }
161- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
959+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49))
162960 if templ_7745c5c3_Err != nil {
163961 return templ_7745c5c3_Err
164962 }
165- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</h4>")
963+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "</h4>")
166964 if templ_7745c5c3_Err != nil {
167965 return templ_7745c5c3_Err
168966 }
169967 if keybindingGroupMeta(group) != "" {
170- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<p class=\"sv-meta\">")
968+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "<p class=\"sv-meta\">")
171969 if templ_7745c5c3_Err != nil {
172970 return templ_7745c5c3_Err
173971 }
174- var templ_7745c5c3_Var7 string
175- templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(keybindingGroupMeta(group))
972+ var templ_7745c5c3_Var50 string
973+ templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.JoinStringErrs(keybindingGroupMeta(group))
176974 if templ_7745c5c3_Err != nil {
177- return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 32, Col: 52}
975+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 149, Col: 52}
178976 }
179- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
977+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var50))
180978 if templ_7745c5c3_Err != nil {
181979 return templ_7745c5c3_Err
182980 }
183- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</p>")
981+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "</p>")
184982 if templ_7745c5c3_Err != nil {
185983 return templ_7745c5c3_Err
186984 }
187985 }
188- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</div></div><div class=\"sv-group-body\">")
986+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "</div></div><div class=\"sv-group-body\">")
189987 if templ_7745c5c3_Err != nil {
190988 return templ_7745c5c3_Err
191989 }
@@ -195,22 +993,22 @@ func ConfigKeybindingGroup(group renderedKeybindingGroup) templ.Component {
195993 return templ_7745c5c3_Err
196994 }
197995 } else {
198- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<ul class=\"sv-list\">")
996+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "<ul class=\"sv-list\">")
199997 if templ_7745c5c3_Err != nil {
200998 return templ_7745c5c3_Err
201999 }
2021000 for _, binding := range group.Bindings {
203- templ_7745c5c3_Err = ConfigKeybinding(binding).Render(ctx, templ_7745c5c3_Buffer)
1001+ templ_7745c5c3_Err = ConfigKeybinding(group, binding).Render(ctx, templ_7745c5c3_Buffer)
2041002 if templ_7745c5c3_Err != nil {
2051003 return templ_7745c5c3_Err
2061004 }
2071005 }
208- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</ul>")
1006+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "</ul>")
2091007 if templ_7745c5c3_Err != nil {
2101008 return templ_7745c5c3_Err
2111009 }
2121010 }
213- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</div></div>")
1011+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "</div></div>")
2141012 if templ_7745c5c3_Err != nil {
2151013 return templ_7745c5c3_Err
2161014 }
@@ -218,7 +1016,7 @@ func ConfigKeybindingGroup(group renderedKeybindingGroup) templ.Component {
2181016 })
2191017 }
2201018
221-func ConfigKeybinding(binding renderedKeybinding) templ.Component {
1019+func ConfigKeybinding(group renderedKeybindingGroup, binding renderedKeybinding) templ.Component {
2221020 return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
2231021 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
2241022 if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@@ -234,38 +1032,38 @@ func ConfigKeybinding(binding renderedKeybinding) templ.Component {
2341032 }()
2351033 }
2361034 ctx = templ.InitializeContext(ctx)
237- templ_7745c5c3_Var8 := templ.GetChildren(ctx)
238- if templ_7745c5c3_Var8 == nil {
239- templ_7745c5c3_Var8 = templ.NopComponent
1035+ templ_7745c5c3_Var51 := templ.GetChildren(ctx)
1036+ if templ_7745c5c3_Var51 == nil {
1037+ templ_7745c5c3_Var51 = templ.NopComponent
2401038 }
2411039 ctx = templ.ClearChildren(ctx)
242- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<li class=\"sv-list-row\"><div class=\"sv-keybinding-row\"><div><strong>")
1040+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "<li class=\"sv-list-row\"><div class=\"sv-keybinding-row\"><div><strong>")
2431041 if templ_7745c5c3_Err != nil {
2441042 return templ_7745c5c3_Err
2451043 }
246- var templ_7745c5c3_Var9 string
247- templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(keybindingDescription(binding))
1044+ var templ_7745c5c3_Var52 string
1045+ templ_7745c5c3_Var52, templ_7745c5c3_Err = templ.JoinStringErrs(keybindingDescription(binding))
2481046 if templ_7745c5c3_Err != nil {
249- return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 54, Col: 44}
1047+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 171, Col: 44}
2501048 }
251- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
1049+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var52))
2521050 if templ_7745c5c3_Err != nil {
2531051 return templ_7745c5c3_Err
2541052 }
255- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</strong><p class=\"sv-meta\">")
1053+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "</strong><p class=\"sv-meta\">")
2561054 if templ_7745c5c3_Err != nil {
2571055 return templ_7745c5c3_Err
2581056 }
259- var templ_7745c5c3_Var10 string
260- templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(keybindingAction(binding))
1057+ var templ_7745c5c3_Var53 string
1058+ templ_7745c5c3_Var53, templ_7745c5c3_Err = templ.JoinStringErrs(keybindingAction(binding))
2611059 if templ_7745c5c3_Err != nil {
262- return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 55, Col: 50}
1060+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 172, Col: 50}
2631061 }
264- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
1062+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var53))
2651063 if templ_7745c5c3_Err != nil {
2661064 return templ_7745c5c3_Err
2671065 }
268- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</p></div><div class=\"sv-keybinding-keys\">")
1066+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "</p><div class=\"sv-binding-keys\">")
2691067 if templ_7745c5c3_Err != nil {
2701068 return templ_7745c5c3_Err
2711069 }
@@ -273,25 +1071,25 @@ func ConfigKeybinding(binding renderedKeybinding) templ.Component {
2731071 if templ_7745c5c3_Err != nil {
2741072 return templ_7745c5c3_Err
2751073 }
276- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<span class=\"sv-badge\">")
1074+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "<span class=\"sv-badge\">")
2771075 if templ_7745c5c3_Err != nil {
2781076 return templ_7745c5c3_Err
2791077 }
280- var templ_7745c5c3_Var11 string
281- templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(keybindingStatus(binding))
1078+ var templ_7745c5c3_Var54 string
1079+ templ_7745c5c3_Var54, templ_7745c5c3_Err = templ.JoinStringErrs(keybindingStatus(binding))
2821080 if templ_7745c5c3_Err != nil {
283- return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 59, Col: 54}
1081+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 175, Col: 55}
2841082 }
285- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
1083+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var54))
2861084 if templ_7745c5c3_Err != nil {
2871085 return templ_7745c5c3_Err
2881086 }
289- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</span> ")
1087+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "</span> ")
2901088 if templ_7745c5c3_Err != nil {
2911089 return templ_7745c5c3_Err
2921090 }
2931091 if keybindingIsCustom(binding) && len(binding.DefaultKeys) > 0 {
294- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<span class=\"sv-meta\">default:</span>")
1092+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "<span class=\"sv-meta\">default:</span>")
2951093 if templ_7745c5c3_Err != nil {
2961094 return templ_7745c5c3_Err
2971095 }
@@ -300,7 +1098,72 @@ func ConfigKeybinding(binding renderedKeybinding) templ.Component {
3001098 return templ_7745c5c3_Err
3011099 }
3021100 }
303- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</div></div></li>")
1101+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "</div></div><form class=\"sv-inline-form\" data-sv-action=\"config.keybinding.set\"><input type=\"hidden\" name=\"mode\" value=\"")
1102+ if templ_7745c5c3_Err != nil {
1103+ return templ_7745c5c3_Err
1104+ }
1105+ var templ_7745c5c3_Var55 string
1106+ templ_7745c5c3_Var55, templ_7745c5c3_Err = templ.JoinStringErrs(group.Mode)
1107+ if templ_7745c5c3_Err != nil {
1108+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 183, Col: 55}
1109+ }
1110+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var55))
1111+ if templ_7745c5c3_Err != nil {
1112+ return templ_7745c5c3_Err
1113+ }
1114+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "\"> <input type=\"hidden\" name=\"action\" value=\"")
1115+ if templ_7745c5c3_Err != nil {
1116+ return templ_7745c5c3_Err
1117+ }
1118+ var templ_7745c5c3_Var56 string
1119+ templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.JoinStringErrs(binding.Action)
1120+ if templ_7745c5c3_Err != nil {
1121+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 184, Col: 61}
1122+ }
1123+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var56))
1124+ if templ_7745c5c3_Err != nil {
1125+ return templ_7745c5c3_Err
1126+ }
1127+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "\"> <label><span>Keys</span><input name=\"keys\" type=\"text\" value=\"")
1128+ if templ_7745c5c3_Err != nil {
1129+ return templ_7745c5c3_Err
1130+ }
1131+ var templ_7745c5c3_Var57 string
1132+ templ_7745c5c3_Var57, templ_7745c5c3_Err = templ.JoinStringErrs(keybindingKeysValue(binding.Keys))
1133+ if templ_7745c5c3_Err != nil {
1134+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 185, Col: 100}
1135+ }
1136+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var57))
1137+ if templ_7745c5c3_Err != nil {
1138+ return templ_7745c5c3_Err
1139+ }
1140+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "\" placeholder=\"ctrl+h\" required></label> <button class=\"sv-button sv-button-secondary\" type=\"submit\">Save</button> <button class=\"sv-button sv-button-secondary\" type=\"button\" data-sv-action=\"config.keybinding.reset\" data-mode=\"")
1141+ if templ_7745c5c3_Err != nil {
1142+ return templ_7745c5c3_Err
1143+ }
1144+ var templ_7745c5c3_Var58 string
1145+ templ_7745c5c3_Var58, templ_7745c5c3_Err = templ.JoinStringErrs(group.Mode)
1146+ if templ_7745c5c3_Err != nil {
1147+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 187, Col: 127}
1148+ }
1149+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var58))
1150+ if templ_7745c5c3_Err != nil {
1151+ return templ_7745c5c3_Err
1152+ }
1153+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "\" data-action=\"")
1154+ if templ_7745c5c3_Err != nil {
1155+ return templ_7745c5c3_Err
1156+ }
1157+ var templ_7745c5c3_Var59 string
1158+ templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.JoinStringErrs(binding.Action)
1159+ if templ_7745c5c3_Err != nil {
1160+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 187, Col: 158}
1161+ }
1162+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var59))
1163+ if templ_7745c5c3_Err != nil {
1164+ return templ_7745c5c3_Err
1165+ }
1166+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "\">Reset</button></form></div></li>")
3041167 if templ_7745c5c3_Err != nil {
3051168 return templ_7745c5c3_Err
3061169 }
@@ -7,6 +7,8 @@ package systemviews
7 import "github.com/a-h/templ"7 import "github.com/a-h/templ"
8 import templruntime "github.com/a-h/templ/runtime"8 import templruntime "github.com/a-h/templ/runtime"
9 9
10+import "github.com/bnema/dumber/internal/application/port"
11+
10 func ConfigView(data configRenderData) templ.Component {12 func ConfigView(data configRenderData) templ.Component {
11 return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {13 return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
12 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context14 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
@@ -23,63 +25,855 @@ func ConfigView(data configRenderData) templ.Component {
23 }()25 }()
24 }26 }
25 ctx = templ.InitializeContext(ctx)27 ctx = templ.InitializeContext(ctx)
26- templ_7745c5c3_Var1 := templ.GetChildren(ctx)28+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
27- if templ_7745c5c3_Var1 == nil {29+ if templ_7745c5c3_Var1 == nil {
28- templ_7745c5c3_Var1 = templ.NopComponent30+ templ_7745c5c3_Var1 = templ.NopComponent
31+ }
32+ ctx = templ.ClearChildren(ctx)
33+ templ_7745c5c3_Err = Alert("success", data.Notice).Render(ctx, templ_7745c5c3_Buffer)
34+ if templ_7745c5c3_Err != nil {
35+ return templ_7745c5c3_Err
36+ }
37+ templ_7745c5c3_Err = Alert("error", data.Error).Render(ctx, templ_7745c5c3_Buffer)
38+ if templ_7745c5c3_Err != nil {
39+ return templ_7745c5c3_Err
40+ }
41+ templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
42+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
43+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
44+ if !templ_7745c5c3_IsBuffer {
45+ defer func() {
46+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
47+ if templ_7745c5c3_Err == nil {
48+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
49+ }
50+ }()
51+ }
52+ ctx = templ.InitializeContext(ctx)
53+ templ_7745c5c3_Err = AppearanceForm(data.Config).Render(ctx, templ_7745c5c3_Buffer)
54+ if templ_7745c5c3_Err != nil {
55+ return templ_7745c5c3_Err
56+ }
57+ return nil
58+ })
59+ templ_7745c5c3_Err = Section("sv-config-appearance", "Appearance").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
60+ if templ_7745c5c3_Err != nil {
61+ return templ_7745c5c3_Err
62+ }
63+ templ_7745c5c3_Var3 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
64+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
65+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
66+ if !templ_7745c5c3_IsBuffer {
67+ defer func() {
68+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
69+ if templ_7745c5c3_Err == nil {
70+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
71+ }
72+ }()
73+ }
74+ ctx = templ.InitializeContext(ctx)
75+ templ_7745c5c3_Err = SearchSettings(data.Config).Render(ctx, templ_7745c5c3_Buffer)
76+ if templ_7745c5c3_Err != nil {
77+ return templ_7745c5c3_Err
78+ }
79+ return nil
80+ })
81+ templ_7745c5c3_Err = Section("sv-config-search", "Search").Render(templ.WithChildren(ctx, templ_7745c5c3_Var3), templ_7745c5c3_Buffer)
82+ if templ_7745c5c3_Err != nil {
83+ return templ_7745c5c3_Err
84+ }
85+ templ_7745c5c3_Var4 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
86+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
87+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
88+ if !templ_7745c5c3_IsBuffer {
89+ defer func() {
90+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
91+ if templ_7745c5c3_Err == nil {
92+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
93+ }
94+ }()
95+ }
96+ ctx = templ.InitializeContext(ctx)
97+ templ_7745c5c3_Err = PerformanceSettings(data.Config).Render(ctx, templ_7745c5c3_Buffer)
98+ if templ_7745c5c3_Err != nil {
99+ return templ_7745c5c3_Err
100+ }
101+ return nil
102+ })
103+ templ_7745c5c3_Err = Section("sv-config-performance", "Performance").Render(templ.WithChildren(ctx, templ_7745c5c3_Var4), templ_7745c5c3_Buffer)
104+ if templ_7745c5c3_Err != nil {
105+ return templ_7745c5c3_Err
106+ }
107+ templ_7745c5c3_Var5 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
108+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
109+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
110+ if !templ_7745c5c3_IsBuffer {
111+ defer func() {
112+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
113+ if templ_7745c5c3_Err == nil {
114+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
115+ }
116+ }()
117+ }
118+ ctx = templ.InitializeContext(ctx)
119+ templ_7745c5c3_Err = Meta(configKeybindingSummary(data.Keybindings)).Render(ctx, templ_7745c5c3_Buffer)
120+ if templ_7745c5c3_Err != nil {
121+ return templ_7745c5c3_Err
122+ }
123+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, " ")
124+ if templ_7745c5c3_Err != nil {
125+ return templ_7745c5c3_Err
126+ }
127+ templ_7745c5c3_Err = ConfigKeybindings(data.Keybindings).Render(ctx, templ_7745c5c3_Buffer)
128+ if templ_7745c5c3_Err != nil {
129+ return templ_7745c5c3_Err
130+ }
131+ return nil
132+ })
133+ templ_7745c5c3_Err = Section("sv-config-keybindings", "Keybindings").Render(templ.WithChildren(ctx, templ_7745c5c3_Var5), templ_7745c5c3_Buffer)
134+ if templ_7745c5c3_Err != nil {
135+ return templ_7745c5c3_Err
136+ }
137+ templ_7745c5c3_Var6 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
138+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
139+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
140+ if !templ_7745c5c3_IsBuffer {
141+ defer func() {
142+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
143+ if templ_7745c5c3_Err == nil {
144+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
145+ }
146+ }()
147+ }
148+ ctx = templ.InitializeContext(ctx)
149+ templ_7745c5c3_Err = KV(flattenValues(data.Config)).Render(ctx, templ_7745c5c3_Buffer)
150+ if templ_7745c5c3_Err != nil {
151+ return templ_7745c5c3_Err
152+ }
153+ return nil
154+ })
155+ templ_7745c5c3_Err = Section("sv-config-values", "Current payload").Render(templ.WithChildren(ctx, templ_7745c5c3_Var6), templ_7745c5c3_Buffer)
156+ if templ_7745c5c3_Err != nil {
157+ return templ_7745c5c3_Err
158+ }
159+ return nil
160+ })
161+}
162+
163+func AppearanceForm(config port.SystemviewConfigPayload) templ.Component {
164+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
165+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
166+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
167+ return templ_7745c5c3_CtxErr
168+ }
169+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
170+ if !templ_7745c5c3_IsBuffer {
171+ defer func() {
172+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
173+ if templ_7745c5c3_Err == nil {
174+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
175+ }
176+ }()
177+ }
178+ ctx = templ.InitializeContext(ctx)
179+ templ_7745c5c3_Var7 := templ.GetChildren(ctx)
180+ if templ_7745c5c3_Var7 == nil {
181+ templ_7745c5c3_Var7 = templ.NopComponent
182+ }
183+ ctx = templ.ClearChildren(ctx)
184+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<form class=\"sv-card-form sv-config-form\" data-sv-action=\"config.appearance.save\"><label><span>Sans font</span><input name=\"sans_font\" type=\"text\" value=\"")
185+ if templ_7745c5c3_Err != nil {
186+ return templ_7745c5c3_Err
187+ }
188+ var templ_7745c5c3_Var8 string
189+ templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(config.Appearance.SansFont)
190+ if templ_7745c5c3_Err != nil {
191+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 28, Col: 101}
192+ }
193+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
194+ if templ_7745c5c3_Err != nil {
195+ return templ_7745c5c3_Err
196+ }
197+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\" required></label> <label><span>Serif font</span><input name=\"serif_font\" type=\"text\" value=\"")
198+ if templ_7745c5c3_Err != nil {
199+ return templ_7745c5c3_Err
200+ }
201+ var templ_7745c5c3_Var9 string
202+ templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(config.Appearance.SerifFont)
203+ if templ_7745c5c3_Err != nil {
204+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 29, Col: 104}
205+ }
206+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
207+ if templ_7745c5c3_Err != nil {
208+ return templ_7745c5c3_Err
209+ }
210+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\" required></label> <label><span>Monospace font</span><input name=\"monospace_font\" type=\"text\" value=\"")
211+ if templ_7745c5c3_Err != nil {
212+ return templ_7745c5c3_Err
213+ }
214+ var templ_7745c5c3_Var10 string
215+ templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(config.Appearance.MonospaceFont)
216+ if templ_7745c5c3_Err != nil {
217+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 30, Col: 116}
218+ }
219+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
220+ if templ_7745c5c3_Err != nil {
221+ return templ_7745c5c3_Err
222+ }
223+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\" required></label> <label><span>Default font size</span><input name=\"default_font_size\" type=\"number\" min=\"1\" max=\"72\" value=\"")
224+ if templ_7745c5c3_Err != nil {
225+ return templ_7745c5c3_Err
226+ }
227+ var templ_7745c5c3_Var11 string
228+ templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(formatConfigInt(config.Appearance.DefaultFontSize))
229+ if templ_7745c5c3_Err != nil {
230+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 31, Col: 160}
231+ }
232+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
233+ if templ_7745c5c3_Err != nil {
234+ return templ_7745c5c3_Err
235+ }
236+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\" required></label> <label><span>UI scale</span><input name=\"default_ui_scale\" type=\"number\" min=\"0.5\" max=\"3\" step=\"0.05\" value=\"")
237+ if templ_7745c5c3_Err != nil {
238+ return templ_7745c5c3_Err
239+ }
240+ var templ_7745c5c3_Var12 string
241+ templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(formatConfigFloat(config.DefaultUIScale))
242+ if templ_7745c5c3_Err != nil {
243+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 32, Col: 153}
244+ }
245+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
246+ if templ_7745c5c3_Err != nil {
247+ return templ_7745c5c3_Err
248+ }
249+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\" required></label> <label><span>Color scheme</span><select name=\"color_scheme\"><option value=\"default\"")
250+ if templ_7745c5c3_Err != nil {
251+ return templ_7745c5c3_Err
252+ }
253+ if colorSchemeSelected(config.Appearance.ColorScheme, "default") {
254+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, " selected")
255+ if templ_7745c5c3_Err != nil {
256+ return templ_7745c5c3_Err
257+ }
258+ }
259+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, ">System default</option> <option value=\"prefer-light\"")
260+ if templ_7745c5c3_Err != nil {
261+ return templ_7745c5c3_Err
262+ }
263+ if colorSchemeSelected(config.Appearance.ColorScheme, "prefer-light") {
264+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, " selected")
265+ if templ_7745c5c3_Err != nil {
266+ return templ_7745c5c3_Err
267+ }
268+ }
269+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, ">Prefer light</option> <option value=\"prefer-dark\"")
270+ if templ_7745c5c3_Err != nil {
271+ return templ_7745c5c3_Err
272+ }
273+ if colorSchemeSelected(config.Appearance.ColorScheme, "prefer-dark") {
274+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, " selected")
275+ if templ_7745c5c3_Err != nil {
276+ return templ_7745c5c3_Err
277+ }
278+ }
279+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, ">Prefer dark</option></select></label>")
280+ if templ_7745c5c3_Err != nil {
281+ return templ_7745c5c3_Err
282+ }
283+ templ_7745c5c3_Err = PaletteFields("Light palette", "light", config.Appearance.LightPalette).Render(ctx, templ_7745c5c3_Buffer)
284+ if templ_7745c5c3_Err != nil {
285+ return templ_7745c5c3_Err
286+ }
287+ templ_7745c5c3_Err = PaletteFields("Dark palette", "dark", config.Appearance.DarkPalette).Render(ctx, templ_7745c5c3_Buffer)
288+ if templ_7745c5c3_Err != nil {
289+ return templ_7745c5c3_Err
290+ }
291+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<div class=\"sv-button-row sv-form-actions\"><button class=\"sv-button\" type=\"submit\">Save appearance</button> <button class=\"sv-button sv-button-secondary\" type=\"button\" data-sv-action=\"config.appearance.reset\" data-sv-confirm=\"Reset appearance settings to defaults?\">Reset appearance</button></div></form>")
292+ if templ_7745c5c3_Err != nil {
293+ return templ_7745c5c3_Err
294+ }
295+ return nil
296+ })
297+}
298+
299+func PaletteFields(title string, prefix string, palette port.ColorPalette) templ.Component {
300+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
301+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
302+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
303+ return templ_7745c5c3_CtxErr
304+ }
305+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
306+ if !templ_7745c5c3_IsBuffer {
307+ defer func() {
308+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
309+ if templ_7745c5c3_Err == nil {
310+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
311+ }
312+ }()
313+ }
314+ ctx = templ.InitializeContext(ctx)
315+ templ_7745c5c3_Var13 := templ.GetChildren(ctx)
316+ if templ_7745c5c3_Var13 == nil {
317+ templ_7745c5c3_Var13 = templ.NopComponent
318+ }
319+ ctx = templ.ClearChildren(ctx)
320+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<fieldset class=\"sv-fieldset\"><legend>")
321+ if templ_7745c5c3_Err != nil {
322+ return templ_7745c5c3_Err
323+ }
324+ var templ_7745c5c3_Var14 string
325+ templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(title)
326+ if templ_7745c5c3_Err != nil {
327+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 49, Col: 17}
328+ }
329+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
330+ if templ_7745c5c3_Err != nil {
331+ return templ_7745c5c3_Err
332+ }
333+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</legend> <label><span>Background</span><input name=\"")
334+ if templ_7745c5c3_Err != nil {
335+ return templ_7745c5c3_Err
336+ }
337+ var templ_7745c5c3_Var15 string
338+ templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(prefix + "_background")
339+ if templ_7745c5c3_Err != nil {
340+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 50, Col: 68}
341+ }
342+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
343+ if templ_7745c5c3_Err != nil {
344+ return templ_7745c5c3_Err
345+ }
346+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\" type=\"text\" value=\"")
347+ if templ_7745c5c3_Err != nil {
348+ return templ_7745c5c3_Err
349+ }
350+ var templ_7745c5c3_Var16 string
351+ templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(palette.Background)
352+ if templ_7745c5c3_Err != nil {
353+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 50, Col: 109}
354+ }
355+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
356+ if templ_7745c5c3_Err != nil {
357+ return templ_7745c5c3_Err
358+ }
359+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "\" required></label> <label><span>Surface</span><input name=\"")
360+ if templ_7745c5c3_Err != nil {
361+ return templ_7745c5c3_Err
362+ }
363+ var templ_7745c5c3_Var17 string
364+ templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(prefix + "_surface")
365+ if templ_7745c5c3_Err != nil {
366+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 51, Col: 62}
367+ }
368+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
369+ if templ_7745c5c3_Err != nil {
370+ return templ_7745c5c3_Err
371+ }
372+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "\" type=\"text\" value=\"")
373+ if templ_7745c5c3_Err != nil {
374+ return templ_7745c5c3_Err
375+ }
376+ var templ_7745c5c3_Var18 string
377+ templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(palette.Surface)
378+ if templ_7745c5c3_Err != nil {
379+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 51, Col: 100}
380+ }
381+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
382+ if templ_7745c5c3_Err != nil {
383+ return templ_7745c5c3_Err
384+ }
385+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\" required></label> <label><span>Surface variant</span><input name=\"")
386+ if templ_7745c5c3_Err != nil {
387+ return templ_7745c5c3_Err
388+ }
389+ var templ_7745c5c3_Var19 string
390+ templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(prefix + "_surface_variant")
391+ if templ_7745c5c3_Err != nil {
392+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 52, Col: 78}
393+ }
394+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
395+ if templ_7745c5c3_Err != nil {
396+ return templ_7745c5c3_Err
397+ }
398+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "\" type=\"text\" value=\"")
399+ if templ_7745c5c3_Err != nil {
400+ return templ_7745c5c3_Err
401+ }
402+ var templ_7745c5c3_Var20 string
403+ templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(palette.SurfaceVariant)
404+ if templ_7745c5c3_Err != nil {
405+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 52, Col: 123}
406+ }
407+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
408+ if templ_7745c5c3_Err != nil {
409+ return templ_7745c5c3_Err
410+ }
411+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "\" required></label> <label><span>Text</span><input name=\"")
412+ if templ_7745c5c3_Err != nil {
413+ return templ_7745c5c3_Err
414+ }
415+ var templ_7745c5c3_Var21 string
416+ templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(prefix + "_text")
417+ if templ_7745c5c3_Err != nil {
418+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 53, Col: 56}
419+ }
420+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
421+ if templ_7745c5c3_Err != nil {
422+ return templ_7745c5c3_Err
423+ }
424+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "\" type=\"text\" value=\"")
425+ if templ_7745c5c3_Err != nil {
426+ return templ_7745c5c3_Err
427+ }
428+ var templ_7745c5c3_Var22 string
429+ templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(palette.Text)
430+ if templ_7745c5c3_Err != nil {
431+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 53, Col: 91}
432+ }
433+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
434+ if templ_7745c5c3_Err != nil {
435+ return templ_7745c5c3_Err
436+ }
437+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\" required></label> <label><span>Muted</span><input name=\"")
438+ if templ_7745c5c3_Err != nil {
439+ return templ_7745c5c3_Err
440+ }
441+ var templ_7745c5c3_Var23 string
442+ templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(prefix + "_muted")
443+ if templ_7745c5c3_Err != nil {
444+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 54, Col: 58}
445+ }
446+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
447+ if templ_7745c5c3_Err != nil {
448+ return templ_7745c5c3_Err
449+ }
450+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "\" type=\"text\" value=\"")
451+ if templ_7745c5c3_Err != nil {
452+ return templ_7745c5c3_Err
453+ }
454+ var templ_7745c5c3_Var24 string
455+ templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(palette.Muted)
456+ if templ_7745c5c3_Err != nil {
457+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 54, Col: 94}
458+ }
459+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
460+ if templ_7745c5c3_Err != nil {
461+ return templ_7745c5c3_Err
462+ }
463+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "\" required></label> <label><span>Accent</span><input name=\"")
464+ if templ_7745c5c3_Err != nil {
465+ return templ_7745c5c3_Err
466+ }
467+ var templ_7745c5c3_Var25 string
468+ templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(prefix + "_accent")
469+ if templ_7745c5c3_Err != nil {
470+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 55, Col: 60}
471+ }
472+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
473+ if templ_7745c5c3_Err != nil {
474+ return templ_7745c5c3_Err
475+ }
476+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "\" type=\"text\" value=\"")
477+ if templ_7745c5c3_Err != nil {
478+ return templ_7745c5c3_Err
479+ }
480+ var templ_7745c5c3_Var26 string
481+ templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(palette.Accent)
482+ if templ_7745c5c3_Err != nil {
483+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 55, Col: 97}
484+ }
485+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26))
486+ if templ_7745c5c3_Err != nil {
487+ return templ_7745c5c3_Err
488+ }
489+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "\" required></label> <label><span>Border</span><input name=\"")
490+ if templ_7745c5c3_Err != nil {
491+ return templ_7745c5c3_Err
492+ }
493+ var templ_7745c5c3_Var27 string
494+ templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(prefix + "_border")
495+ if templ_7745c5c3_Err != nil {
496+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 56, Col: 60}
497+ }
498+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
499+ if templ_7745c5c3_Err != nil {
500+ return templ_7745c5c3_Err
501+ }
502+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "\" type=\"text\" value=\"")
503+ if templ_7745c5c3_Err != nil {
504+ return templ_7745c5c3_Err
505+ }
506+ var templ_7745c5c3_Var28 string
507+ templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(palette.Border)
508+ if templ_7745c5c3_Err != nil {
509+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 56, Col: 97}
510+ }
511+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))
512+ if templ_7745c5c3_Err != nil {
513+ return templ_7745c5c3_Err
514+ }
515+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "\" required></label></fieldset>")
516+ if templ_7745c5c3_Err != nil {
517+ return templ_7745c5c3_Err
518+ }
519+ return nil
520+ })
521+}
522+
523+func SearchSettings(config port.SystemviewConfigPayload) templ.Component {
524+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
525+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
526+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
527+ return templ_7745c5c3_CtxErr
528+ }
529+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
530+ if !templ_7745c5c3_IsBuffer {
531+ defer func() {
532+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
533+ if templ_7745c5c3_Err == nil {
534+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
535+ }
536+ }()
537+ }
538+ ctx = templ.InitializeContext(ctx)
539+ templ_7745c5c3_Var29 := templ.GetChildren(ctx)
540+ if templ_7745c5c3_Var29 == nil {
541+ templ_7745c5c3_Var29 = templ.NopComponent
542+ }
543+ ctx = templ.ClearChildren(ctx)
544+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<form class=\"sv-card-form\" data-sv-action=\"config.search.save\"><label><span>Default search engine</span><input name=\"default_search_engine\" type=\"text\" value=\"")
545+ if templ_7745c5c3_Err != nil {
546+ return templ_7745c5c3_Err
547+ }
548+ var templ_7745c5c3_Var30 string
549+ templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(config.DefaultSearchEngine)
550+ if templ_7745c5c3_Err != nil {
551+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 62, Col: 125}
552+ }
553+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
554+ if templ_7745c5c3_Err != nil {
555+ return templ_7745c5c3_Err
556+ }
557+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "\" placeholder=\"https://duckduckgo.com/?q=%s\" required></label> <button class=\"sv-button\" type=\"submit\">Save search default</button></form><div class=\"sv-group\"><div class=\"sv-group-header\"><h4>Search shortcuts</h4></div><p class=\"sv-meta\">Shortcut URLs must include the %s placeholder for the search query.</p><form class=\"sv-inline-form\" data-sv-action=\"config.searchShortcut.create\"><label><span>Key</span><input name=\"key\" type=\"text\" placeholder=\"ddg\" required></label> <label><span>URL</span><input name=\"url\" type=\"text\" placeholder=\"https://duckduckgo.com/?q=%s\" required></label> <label><span>Description</span><input name=\"description\" type=\"text\" placeholder=\"DuckDuckGo\" required></label> <button class=\"sv-button sv-button-secondary\" type=\"submit\">Add shortcut</button></form>")
558+ if templ_7745c5c3_Err != nil {
559+ return templ_7745c5c3_Err
560+ }
561+ if len(searchShortcutRows(config.SearchShortcuts)) == 0 {
562+ templ_7745c5c3_Err = EmptyState("No search shortcuts configured").Render(ctx, templ_7745c5c3_Buffer)
563+ if templ_7745c5c3_Err != nil {
564+ return templ_7745c5c3_Err
565+ }
566+ } else {
567+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<ul class=\"sv-list\">")
568+ if templ_7745c5c3_Err != nil {
569+ return templ_7745c5c3_Err
570+ }
571+ for _, shortcut := range searchShortcutRows(config.SearchShortcuts) {
572+ templ_7745c5c3_Err = SearchShortcutItem(shortcut).Render(ctx, templ_7745c5c3_Buffer)
573+ if templ_7745c5c3_Err != nil {
574+ return templ_7745c5c3_Err
575+ }
576+ }
577+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "</ul>")
578+ if templ_7745c5c3_Err != nil {
579+ return templ_7745c5c3_Err
580+ }
581+ }
582+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "</div>")
583+ if templ_7745c5c3_Err != nil {
584+ return templ_7745c5c3_Err
585+ }
586+ return nil
587+ })
588+}
589+
590+func SearchShortcutItem(shortcut searchShortcutRow) templ.Component {
591+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
592+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
593+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
594+ return templ_7745c5c3_CtxErr
595+ }
596+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
597+ if !templ_7745c5c3_IsBuffer {
598+ defer func() {
599+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
600+ if templ_7745c5c3_Err == nil {
601+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
602+ }
603+ }()
604+ }
605+ ctx = templ.InitializeContext(ctx)
606+ templ_7745c5c3_Var31 := templ.GetChildren(ctx)
607+ if templ_7745c5c3_Var31 == nil {
608+ templ_7745c5c3_Var31 = templ.NopComponent
29 }609 }
30 ctx = templ.ClearChildren(ctx)610 ctx = templ.ClearChildren(ctx)
31- templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {611+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "<li class=\"sv-list-row\"><form class=\"sv-inline-form\" data-sv-action=\"config.searchShortcut.update\"><input type=\"hidden\" name=\"key\" value=\"")
32- templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context612+ if templ_7745c5c3_Err != nil {
33- templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)613+ return templ_7745c5c3_Err
34- if !templ_7745c5c3_IsBuffer {614+ }
35- defer func() {615+ var templ_7745c5c3_Var32 string
36- templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)616+ templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(shortcut.Key)
37- if templ_7745c5c3_Err == nil {617+ if templ_7745c5c3_Err != nil {
38- templ_7745c5c3_Err = templ_7745c5c3_BufErr618+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 89, Col: 55}
39- }619+ }
40- }()620+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))
41- }621+ if templ_7745c5c3_Err != nil {
42- ctx = templ.InitializeContext(ctx)622+ return templ_7745c5c3_Err
43- templ_7745c5c3_Err = KV(flattenValues(data.Config)).Render(ctx, templ_7745c5c3_Buffer)623+ }
624+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "\"> <label><span>Key</span><input name=\"new_key\" type=\"text\" value=\"")
625+ if templ_7745c5c3_Err != nil {
626+ return templ_7745c5c3_Err
627+ }
628+ var templ_7745c5c3_Var33 string
629+ templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(shortcut.Key)
630+ if templ_7745c5c3_Err != nil {
631+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 90, Col: 80}
632+ }
633+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33))
634+ if templ_7745c5c3_Err != nil {
635+ return templ_7745c5c3_Err
636+ }
637+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "\" required></label> <label><span>URL</span><input name=\"url\" type=\"text\" value=\"")
638+ if templ_7745c5c3_Err != nil {
639+ return templ_7745c5c3_Err
640+ }
641+ var templ_7745c5c3_Var34 string
642+ templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(shortcut.URL)
643+ if templ_7745c5c3_Err != nil {
644+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 91, Col: 76}
645+ }
646+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))
647+ if templ_7745c5c3_Err != nil {
648+ return templ_7745c5c3_Err
649+ }
650+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "\" required></label> <label><span>Description</span><input name=\"description\" type=\"text\" value=\"")
651+ if templ_7745c5c3_Err != nil {
652+ return templ_7745c5c3_Err
653+ }
654+ var templ_7745c5c3_Var35 string
655+ templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(shortcut.Description)
656+ if templ_7745c5c3_Err != nil {
657+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 92, Col: 100}
658+ }
659+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35))
660+ if templ_7745c5c3_Err != nil {
661+ return templ_7745c5c3_Err
662+ }
663+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "\" required></label> <button class=\"sv-button sv-button-secondary\" type=\"submit\">Save</button> <button class=\"sv-button sv-button-secondary sv-button-danger\" type=\"button\" data-sv-action=\"config.searchShortcut.delete\" data-key=\"")
664+ if templ_7745c5c3_Err != nil {
665+ return templ_7745c5c3_Err
666+ }
667+ var templ_7745c5c3_Var36 string
668+ templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(shortcut.Key)
669+ if templ_7745c5c3_Err != nil {
670+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 94, Col: 149}
671+ }
672+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36))
673+ if templ_7745c5c3_Err != nil {
674+ return templ_7745c5c3_Err
675+ }
676+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "\" data-sv-confirm=\"Delete this search shortcut?\">Delete</button></form></li>")
677+ if templ_7745c5c3_Err != nil {
678+ return templ_7745c5c3_Err
679+ }
680+ return nil
681+ })
682+}
683+
684+func PerformanceSettings(config port.SystemviewConfigPayload) templ.Component {
685+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
686+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
687+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
688+ return templ_7745c5c3_CtxErr
689+ }
690+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
691+ if !templ_7745c5c3_IsBuffer {
692+ defer func() {
693+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
694+ if templ_7745c5c3_Err == nil {
695+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
696+ }
697+ }()
698+ }
699+ ctx = templ.InitializeContext(ctx)
700+ templ_7745c5c3_Var37 := templ.GetChildren(ctx)
701+ if templ_7745c5c3_Var37 == nil {
702+ templ_7745c5c3_Var37 = templ.NopComponent
703+ }
704+ ctx = templ.ClearChildren(ctx)
705+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "<form class=\"sv-card-form sv-config-form\" data-sv-action=\"config.performance.save\"><label><span>Profile</span><select name=\"profile\"><option value=\"default\"")
706+ if templ_7745c5c3_Err != nil {
707+ return templ_7745c5c3_Err
708+ }
709+ if performanceProfileSelected(config.Performance.Profile, "default") {
710+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, " selected")
44 if templ_7745c5c3_Err != nil {711 if templ_7745c5c3_Err != nil {
45 return templ_7745c5c3_Err712 return templ_7745c5c3_Err
46 }713 }
47- return nil714+ }
48- })715+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, ">Default</option> <option value=\"lite\"")
49- templ_7745c5c3_Err = Section("sv-config-values", "Config values").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
50 if templ_7745c5c3_Err != nil {716 if templ_7745c5c3_Err != nil {
51 return templ_7745c5c3_Err717 return templ_7745c5c3_Err
52 }718 }
53- templ_7745c5c3_Var3 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {719+ if performanceProfileSelected(config.Performance.Profile, "lite") {
54- templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context720+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, " selected")
55- templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
56- if !templ_7745c5c3_IsBuffer {
57- defer func() {
58- templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
59- if templ_7745c5c3_Err == nil {
60- templ_7745c5c3_Err = templ_7745c5c3_BufErr
61- }
62- }()
63- }
64- ctx = templ.InitializeContext(ctx)
65- templ_7745c5c3_Err = Meta(configKeybindingSummary(data.Keybindings)).Render(ctx, templ_7745c5c3_Buffer)
66 if templ_7745c5c3_Err != nil {721 if templ_7745c5c3_Err != nil {
67 return templ_7745c5c3_Err722 return templ_7745c5c3_Err
68 }723 }
69- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, " ")724+ }
725+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, ">Lite</option> <option value=\"max\"")
726+ if templ_7745c5c3_Err != nil {
727+ return templ_7745c5c3_Err
728+ }
729+ if performanceProfileSelected(config.Performance.Profile, "max") {
730+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, " selected")
70 if templ_7745c5c3_Err != nil {731 if templ_7745c5c3_Err != nil {
71 return templ_7745c5c3_Err732 return templ_7745c5c3_Err
72 }733 }
73- templ_7745c5c3_Err = ConfigKeybindings(data.Keybindings).Render(ctx, templ_7745c5c3_Buffer)734+ }
735+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, ">Max</option> <option value=\"custom\"")
736+ if templ_7745c5c3_Err != nil {
737+ return templ_7745c5c3_Err
738+ }
739+ if performanceProfileSelected(config.Performance.Profile, "custom") {
740+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, " selected")
74 if templ_7745c5c3_Err != nil {741 if templ_7745c5c3_Err != nil {
75 return templ_7745c5c3_Err742 return templ_7745c5c3_Err
76 }743 }
77- return nil744+ }
78- })745+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, ">Custom</option></select></label> <label><span>Skia CPU threads</span><input name=\"skia_cpu_threads\" type=\"number\" min=\"0\" max=\"8\" value=\"")
79- templ_7745c5c3_Err = Section("sv-config-keybindings", "Keybindings").Render(templ.WithChildren(ctx, templ_7745c5c3_Var3), templ_7745c5c3_Buffer)746+ if templ_7745c5c3_Err != nil {
747+ return templ_7745c5c3_Err
748+ }
749+ var templ_7745c5c3_Var38 string
750+ templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(formatConfigInt(config.Performance.Custom.SkiaCPUThreads))
751+ if templ_7745c5c3_Err != nil {
752+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 107, Col: 164}
753+ }
754+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38))
755+ if templ_7745c5c3_Err != nil {
756+ return templ_7745c5c3_Err
757+ }
758+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "\"></label> <label><span>Skia GPU threads</span><input name=\"skia_gpu_threads\" type=\"number\" min=\"-1\" max=\"8\" value=\"")
759+ if templ_7745c5c3_Err != nil {
760+ return templ_7745c5c3_Err
761+ }
762+ var templ_7745c5c3_Var39 string
763+ templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(formatConfigInt(config.Performance.Custom.SkiaGPUThreads))
764+ if templ_7745c5c3_Err != nil {
765+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 108, Col: 165}
766+ }
767+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39))
768+ if templ_7745c5c3_Err != nil {
769+ return templ_7745c5c3_Err
770+ }
771+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "\"></label> <label><span>Web process memory MB</span><input name=\"web_process_memory_mb\" type=\"number\" min=\"0\" max=\"16384\" value=\"")
772+ if templ_7745c5c3_Err != nil {
773+ return templ_7745c5c3_Err
774+ }
775+ var templ_7745c5c3_Var40 string
776+ templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs(formatConfigInt(config.Performance.Custom.WebProcessMemoryMB))
777+ if templ_7745c5c3_Err != nil {
778+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 109, Col: 182}
779+ }
780+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40))
781+ if templ_7745c5c3_Err != nil {
782+ return templ_7745c5c3_Err
783+ }
784+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "\"></label> <label><span>Network process memory MB</span><input name=\"network_process_memory_mb\" type=\"number\" min=\"0\" max=\"4096\" value=\"")
785+ if templ_7745c5c3_Err != nil {
786+ return templ_7745c5c3_Err
787+ }
788+ var templ_7745c5c3_Var41 string
789+ templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.JoinStringErrs(formatConfigInt(config.Performance.Custom.NetworkProcessMemoryMB))
790+ if templ_7745c5c3_Err != nil {
791+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 110, Col: 193}
792+ }
793+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var41))
794+ if templ_7745c5c3_Err != nil {
795+ return templ_7745c5c3_Err
796+ }
797+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "\"></label> <label><span>WebView pool prewarm</span><input name=\"webview_pool_prewarm\" type=\"number\" min=\"0\" max=\"20\" value=\"")
798+ if templ_7745c5c3_Err != nil {
799+ return templ_7745c5c3_Err
800+ }
801+ var templ_7745c5c3_Var42 string
802+ templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.JoinStringErrs(formatConfigInt(config.Performance.Custom.WebViewPoolPrewarm))
803+ if templ_7745c5c3_Err != nil {
804+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 111, Col: 177}
805+ }
806+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var42))
807+ if templ_7745c5c3_Err != nil {
808+ return templ_7745c5c3_Err
809+ }
810+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "\"></label> <button class=\"sv-button\" type=\"submit\">Save performance</button></form><div class=\"sv-stat-grid\"><div class=\"sv-stat-card\"><span class=\"sv-stat-value\">")
811+ if templ_7745c5c3_Err != nil {
812+ return templ_7745c5c3_Err
813+ }
814+ var templ_7745c5c3_Var43 string
815+ templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.JoinStringErrs(formatConfigInt(config.Performance.Hardware.CPUThreads))
816+ if templ_7745c5c3_Err != nil {
817+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 115, Col: 113}
818+ }
819+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var43))
820+ if templ_7745c5c3_Err != nil {
821+ return templ_7745c5c3_Err
822+ }
823+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "</span><span class=\"sv-stat-label\">CPU threads</span></div><div class=\"sv-stat-card\"><span class=\"sv-stat-value\">")
824+ if templ_7745c5c3_Err != nil {
825+ return templ_7745c5c3_Err
826+ }
827+ var templ_7745c5c3_Var44 string
828+ templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.JoinStringErrs(formatConfigInt(config.Performance.Hardware.TotalRAMMB))
829+ if templ_7745c5c3_Err != nil {
830+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 116, Col: 113}
831+ }
832+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44))
833+ if templ_7745c5c3_Err != nil {
834+ return templ_7745c5c3_Err
835+ }
836+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "</span><span class=\"sv-stat-label\">RAM MB</span></div><div class=\"sv-stat-card\"><span class=\"sv-stat-value\">")
837+ if templ_7745c5c3_Err != nil {
838+ return templ_7745c5c3_Err
839+ }
840+ var templ_7745c5c3_Var45 string
841+ templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.JoinStringErrs(config.Performance.Hardware.GPUName)
842+ if templ_7745c5c3_Err != nil {
843+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 117, Col: 93}
844+ }
845+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var45))
846+ if templ_7745c5c3_Err != nil {
847+ return templ_7745c5c3_Err
848+ }
849+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "</span><span class=\"sv-stat-label\">GPU</span></div><div class=\"sv-stat-card\"><span class=\"sv-stat-value\">")
850+ if templ_7745c5c3_Err != nil {
851+ return templ_7745c5c3_Err
852+ }
853+ var templ_7745c5c3_Var46 string
854+ templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.JoinStringErrs(config.EngineType)
855+ if templ_7745c5c3_Err != nil {
856+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 118, Col: 75}
857+ }
858+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var46))
80 if templ_7745c5c3_Err != nil {859 if templ_7745c5c3_Err != nil {
81 return templ_7745c5c3_Err860 return templ_7745c5c3_Err
82 }861 }
862+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "</span><span class=\"sv-stat-label\">Engine</span></div></div>")
863+ if templ_7745c5c3_Err != nil {
864+ return templ_7745c5c3_Err
865+ }
866+ if !engineIsWebKit(config.EngineType) {
867+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "<p class=\"sv-meta\">Performance tuning applies only to the WebKit engine.</p>")
868+ if templ_7745c5c3_Err != nil {
869+ return templ_7745c5c3_Err
870+ }
871+ } else {
872+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "<p class=\"sv-meta\">Performance profile changes may require restarting the browser to affect existing engine processes.</p>")
873+ if templ_7745c5c3_Err != nil {
874+ return templ_7745c5c3_Err
875+ }
876+ }
83 return nil877 return nil
84 })878 })
85 }879 }
@@ -100,12 +894,16 @@ func ConfigKeybindings(keybindings any) templ.Component {
100 }()894 }()
101 }895 }
102 ctx = templ.InitializeContext(ctx)896 ctx = templ.InitializeContext(ctx)
103- templ_7745c5c3_Var4 := templ.GetChildren(ctx)897+ templ_7745c5c3_Var47 := templ.GetChildren(ctx)
104- if templ_7745c5c3_Var4 == nil {898+ if templ_7745c5c3_Var47 == nil {
105- templ_7745c5c3_Var4 = templ.NopComponent899+ templ_7745c5c3_Var47 = templ.NopComponent
106 }900 }
107 ctx = templ.ClearChildren(ctx)901 ctx = templ.ClearChildren(ctx)
108 groups := configKeybindingGroups(keybindings)902 groups := configKeybindingGroups(keybindings)
903+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "<div class=\"sv-button-row\"><button class=\"sv-button sv-button-secondary sv-button-danger\" type=\"button\" data-sv-action=\"config.keybinding.resetAll\" data-sv-confirm=\"Reset all keybindings to defaults?\">Reset all keybindings</button></div>")
904+ if templ_7745c5c3_Err != nil {
905+ return templ_7745c5c3_Err
906+ }
109 if groups == nil {907 if groups == nil {
110 templ_7745c5c3_Err = EmptyState("Keybindings unavailable").Render(ctx, templ_7745c5c3_Buffer)908 templ_7745c5c3_Err = EmptyState("Keybindings unavailable").Render(ctx, templ_7745c5c3_Buffer)
111 if templ_7745c5c3_Err != nil {909 if templ_7745c5c3_Err != nil {
@@ -144,48 +942,48 @@ func ConfigKeybindingGroup(group renderedKeybindingGroup) templ.Component {
144 }()942 }()
145 }943 }
146 ctx = templ.InitializeContext(ctx)944 ctx = templ.InitializeContext(ctx)
147- templ_7745c5c3_Var5 := templ.GetChildren(ctx)945+ templ_7745c5c3_Var48 := templ.GetChildren(ctx)
148- if templ_7745c5c3_Var5 == nil {946+ if templ_7745c5c3_Var48 == nil {
149- templ_7745c5c3_Var5 = templ.NopComponent947+ templ_7745c5c3_Var48 = templ.NopComponent
150 }948 }
151 ctx = templ.ClearChildren(ctx)949 ctx = templ.ClearChildren(ctx)
152- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"sv-group\"><div class=\"sv-group-header\"><div><h4>")950+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "<div class=\"sv-group\"><div class=\"sv-group-header\"><div><h4>")
153 if templ_7745c5c3_Err != nil {951 if templ_7745c5c3_Err != nil {
154 return templ_7745c5c3_Err952 return templ_7745c5c3_Err
155 }953 }
156- var templ_7745c5c3_Var6 string954+ var templ_7745c5c3_Var49 string
157- templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(keybindingGroupTitle(group))955+ templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinStringErrs(keybindingGroupTitle(group))
158 if templ_7745c5c3_Err != nil {956 if templ_7745c5c3_Err != nil {
159- return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 30, Col: 37}957+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 147, Col: 37}
160 }958 }
161- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))959+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49))
162 if templ_7745c5c3_Err != nil {960 if templ_7745c5c3_Err != nil {
163 return templ_7745c5c3_Err961 return templ_7745c5c3_Err
164 }962 }
165- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</h4>")963+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "</h4>")
166 if templ_7745c5c3_Err != nil {964 if templ_7745c5c3_Err != nil {
167 return templ_7745c5c3_Err965 return templ_7745c5c3_Err
168 }966 }
169 if keybindingGroupMeta(group) != "" {967 if keybindingGroupMeta(group) != "" {
170- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<p class=\"sv-meta\">")968+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "<p class=\"sv-meta\">")
171 if templ_7745c5c3_Err != nil {969 if templ_7745c5c3_Err != nil {
172 return templ_7745c5c3_Err970 return templ_7745c5c3_Err
173 }971 }
174- var templ_7745c5c3_Var7 string972+ var templ_7745c5c3_Var50 string
175- templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(keybindingGroupMeta(group))973+ templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.JoinStringErrs(keybindingGroupMeta(group))
176 if templ_7745c5c3_Err != nil {974 if templ_7745c5c3_Err != nil {
177- return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 32, Col: 52}975+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 149, Col: 52}
178 }976 }
179- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))977+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var50))
180 if templ_7745c5c3_Err != nil {978 if templ_7745c5c3_Err != nil {
181 return templ_7745c5c3_Err979 return templ_7745c5c3_Err
182 }980 }
183- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</p>")981+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "</p>")
184 if templ_7745c5c3_Err != nil {982 if templ_7745c5c3_Err != nil {
185 return templ_7745c5c3_Err983 return templ_7745c5c3_Err
186 }984 }
187 }985 }
188- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</div></div><div class=\"sv-group-body\">")986+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "</div></div><div class=\"sv-group-body\">")
189 if templ_7745c5c3_Err != nil {987 if templ_7745c5c3_Err != nil {
190 return templ_7745c5c3_Err988 return templ_7745c5c3_Err
191 }989 }
@@ -195,22 +993,22 @@ func ConfigKeybindingGroup(group renderedKeybindingGroup) templ.Component {
195 return templ_7745c5c3_Err993 return templ_7745c5c3_Err
196 }994 }
197 } else {995 } else {
198- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<ul class=\"sv-list\">")996+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "<ul class=\"sv-list\">")
199 if templ_7745c5c3_Err != nil {997 if templ_7745c5c3_Err != nil {
200 return templ_7745c5c3_Err998 return templ_7745c5c3_Err
201 }999 }
202 for _, binding := range group.Bindings {1000 for _, binding := range group.Bindings {
203- templ_7745c5c3_Err = ConfigKeybinding(binding).Render(ctx, templ_7745c5c3_Buffer)1001+ templ_7745c5c3_Err = ConfigKeybinding(group, binding).Render(ctx, templ_7745c5c3_Buffer)
204 if templ_7745c5c3_Err != nil {1002 if templ_7745c5c3_Err != nil {
205 return templ_7745c5c3_Err1003 return templ_7745c5c3_Err
206 }1004 }
207 }1005 }
208- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</ul>")1006+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "</ul>")
209 if templ_7745c5c3_Err != nil {1007 if templ_7745c5c3_Err != nil {
210 return templ_7745c5c3_Err1008 return templ_7745c5c3_Err
211 }1009 }
212 }1010 }
213- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</div></div>")1011+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "</div></div>")
214 if templ_7745c5c3_Err != nil {1012 if templ_7745c5c3_Err != nil {
215 return templ_7745c5c3_Err1013 return templ_7745c5c3_Err
216 }1014 }
@@ -218,7 +1016,7 @@ func ConfigKeybindingGroup(group renderedKeybindingGroup) templ.Component {
218 })1016 })
219 }1017 }
220 1018
221-func ConfigKeybinding(binding renderedKeybinding) templ.Component {1019+func ConfigKeybinding(group renderedKeybindingGroup, binding renderedKeybinding) templ.Component {
222 return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {1020 return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
223 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context1021 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
224 if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {1022 if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@@ -234,38 +1032,38 @@ func ConfigKeybinding(binding renderedKeybinding) templ.Component {
234 }()1032 }()
235 }1033 }
236 ctx = templ.InitializeContext(ctx)1034 ctx = templ.InitializeContext(ctx)
237- templ_7745c5c3_Var8 := templ.GetChildren(ctx)1035+ templ_7745c5c3_Var51 := templ.GetChildren(ctx)
238- if templ_7745c5c3_Var8 == nil {1036+ if templ_7745c5c3_Var51 == nil {
239- templ_7745c5c3_Var8 = templ.NopComponent1037+ templ_7745c5c3_Var51 = templ.NopComponent
240 }1038 }
241 ctx = templ.ClearChildren(ctx)1039 ctx = templ.ClearChildren(ctx)
242- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<li class=\"sv-list-row\"><div class=\"sv-keybinding-row\"><div><strong>")1040+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "<li class=\"sv-list-row\"><div class=\"sv-keybinding-row\"><div><strong>")
243 if templ_7745c5c3_Err != nil {1041 if templ_7745c5c3_Err != nil {
244 return templ_7745c5c3_Err1042 return templ_7745c5c3_Err
245 }1043 }
246- var templ_7745c5c3_Var9 string1044+ var templ_7745c5c3_Var52 string
247- templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(keybindingDescription(binding))1045+ templ_7745c5c3_Var52, templ_7745c5c3_Err = templ.JoinStringErrs(keybindingDescription(binding))
248 if templ_7745c5c3_Err != nil {1046 if templ_7745c5c3_Err != nil {
249- return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 54, Col: 44}1047+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 171, Col: 44}
250 }1048 }
251- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))1049+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var52))
252 if templ_7745c5c3_Err != nil {1050 if templ_7745c5c3_Err != nil {
253 return templ_7745c5c3_Err1051 return templ_7745c5c3_Err
254 }1052 }
255- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</strong><p class=\"sv-meta\">")1053+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "</strong><p class=\"sv-meta\">")
256 if templ_7745c5c3_Err != nil {1054 if templ_7745c5c3_Err != nil {
257 return templ_7745c5c3_Err1055 return templ_7745c5c3_Err
258 }1056 }
259- var templ_7745c5c3_Var10 string1057+ var templ_7745c5c3_Var53 string
260- templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(keybindingAction(binding))1058+ templ_7745c5c3_Var53, templ_7745c5c3_Err = templ.JoinStringErrs(keybindingAction(binding))
261 if templ_7745c5c3_Err != nil {1059 if templ_7745c5c3_Err != nil {
262- return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 55, Col: 50}1060+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 172, Col: 50}
263 }1061 }
264- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))1062+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var53))
265 if templ_7745c5c3_Err != nil {1063 if templ_7745c5c3_Err != nil {
266 return templ_7745c5c3_Err1064 return templ_7745c5c3_Err
267 }1065 }
268- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</p></div><div class=\"sv-keybinding-keys\">")1066+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "</p><div class=\"sv-binding-keys\">")
269 if templ_7745c5c3_Err != nil {1067 if templ_7745c5c3_Err != nil {
270 return templ_7745c5c3_Err1068 return templ_7745c5c3_Err
271 }1069 }
@@ -273,25 +1071,25 @@ func ConfigKeybinding(binding renderedKeybinding) templ.Component {
273 if templ_7745c5c3_Err != nil {1071 if templ_7745c5c3_Err != nil {
274 return templ_7745c5c3_Err1072 return templ_7745c5c3_Err
275 }1073 }
276- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<span class=\"sv-badge\">")1074+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "<span class=\"sv-badge\">")
277 if templ_7745c5c3_Err != nil {1075 if templ_7745c5c3_Err != nil {
278 return templ_7745c5c3_Err1076 return templ_7745c5c3_Err
279 }1077 }
280- var templ_7745c5c3_Var11 string1078+ var templ_7745c5c3_Var54 string
281- templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(keybindingStatus(binding))1079+ templ_7745c5c3_Var54, templ_7745c5c3_Err = templ.JoinStringErrs(keybindingStatus(binding))
282 if templ_7745c5c3_Err != nil {1080 if templ_7745c5c3_Err != nil {
283- return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 59, Col: 54}1081+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 175, Col: 55}
284 }1082 }
285- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))1083+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var54))
286 if templ_7745c5c3_Err != nil {1084 if templ_7745c5c3_Err != nil {
287 return templ_7745c5c3_Err1085 return templ_7745c5c3_Err
288 }1086 }
289- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</span> ")1087+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "</span> ")
290 if templ_7745c5c3_Err != nil {1088 if templ_7745c5c3_Err != nil {
291 return templ_7745c5c3_Err1089 return templ_7745c5c3_Err
292 }1090 }
293 if keybindingIsCustom(binding) && len(binding.DefaultKeys) > 0 {1091 if keybindingIsCustom(binding) && len(binding.DefaultKeys) > 0 {
294- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<span class=\"sv-meta\">default:</span>")1092+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "<span class=\"sv-meta\">default:</span>")
295 if templ_7745c5c3_Err != nil {1093 if templ_7745c5c3_Err != nil {
296 return templ_7745c5c3_Err1094 return templ_7745c5c3_Err
297 }1095 }
@@ -300,7 +1098,72 @@ func ConfigKeybinding(binding renderedKeybinding) templ.Component {
300 return templ_7745c5c3_Err1098 return templ_7745c5c3_Err
301 }1099 }
302 }1100 }
303- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</div></div></li>")1101+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "</div></div><form class=\"sv-inline-form\" data-sv-action=\"config.keybinding.set\"><input type=\"hidden\" name=\"mode\" value=\"")
1102+ if templ_7745c5c3_Err != nil {
1103+ return templ_7745c5c3_Err
1104+ }
1105+ var templ_7745c5c3_Var55 string
1106+ templ_7745c5c3_Var55, templ_7745c5c3_Err = templ.JoinStringErrs(group.Mode)
1107+ if templ_7745c5c3_Err != nil {
1108+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 183, Col: 55}
1109+ }
1110+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var55))
1111+ if templ_7745c5c3_Err != nil {
1112+ return templ_7745c5c3_Err
1113+ }
1114+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "\"> <input type=\"hidden\" name=\"action\" value=\"")
1115+ if templ_7745c5c3_Err != nil {
1116+ return templ_7745c5c3_Err
1117+ }
1118+ var templ_7745c5c3_Var56 string
1119+ templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.JoinStringErrs(binding.Action)
1120+ if templ_7745c5c3_Err != nil {
1121+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 184, Col: 61}
1122+ }
1123+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var56))
1124+ if templ_7745c5c3_Err != nil {
1125+ return templ_7745c5c3_Err
1126+ }
1127+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "\"> <label><span>Keys</span><input name=\"keys\" type=\"text\" value=\"")
1128+ if templ_7745c5c3_Err != nil {
1129+ return templ_7745c5c3_Err
1130+ }
1131+ var templ_7745c5c3_Var57 string
1132+ templ_7745c5c3_Var57, templ_7745c5c3_Err = templ.JoinStringErrs(keybindingKeysValue(binding.Keys))
1133+ if templ_7745c5c3_Err != nil {
1134+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 185, Col: 100}
1135+ }
1136+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var57))
1137+ if templ_7745c5c3_Err != nil {
1138+ return templ_7745c5c3_Err
1139+ }
1140+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "\" placeholder=\"ctrl+h\" required></label> <button class=\"sv-button sv-button-secondary\" type=\"submit\">Save</button> <button class=\"sv-button sv-button-secondary\" type=\"button\" data-sv-action=\"config.keybinding.reset\" data-mode=\"")
1141+ if templ_7745c5c3_Err != nil {
1142+ return templ_7745c5c3_Err
1143+ }
1144+ var templ_7745c5c3_Var58 string
1145+ templ_7745c5c3_Var58, templ_7745c5c3_Err = templ.JoinStringErrs(group.Mode)
1146+ if templ_7745c5c3_Err != nil {
1147+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 187, Col: 127}
1148+ }
1149+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var58))
1150+ if templ_7745c5c3_Err != nil {
1151+ return templ_7745c5c3_Err
1152+ }
1153+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "\" data-action=\"")
1154+ if templ_7745c5c3_Err != nil {
1155+ return templ_7745c5c3_Err
1156+ }
1157+ var templ_7745c5c3_Var59 string
1158+ templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.JoinStringErrs(binding.Action)
1159+ if templ_7745c5c3_Err != nil {
1160+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `config.templ`, Line: 187, Col: 158}
1161+ }
1162+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var59))
1163+ if templ_7745c5c3_Err != nil {
1164+ return templ_7745c5c3_Err
1165+ }
1166+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "\">Reset</button></form></div></li>")
304 if templ_7745c5c3_Err != nil {1167 if templ_7745c5c3_Err != nil {
305 return templ_7745c5c3_Err1168 return templ_7745c5c3_Err
306 }1169 }
M internal/ui/systemviews/config_view.go +55 -2
@@ -12,10 +12,12 @@ import (
1212 type configRenderData struct {
1313 Config port.SystemviewConfigPayload
1414 Keybindings any
15+ Notice string
16+ Error string
1517 }
1618
17-func configHTML(cfg port.SystemviewConfigPayload, keybindings any) string {
18- return mustRenderComponent(ConfigView(configRenderData{Config: cfg, Keybindings: keybindings}))
19+func configHTML(data configRenderData) string {
20+ return mustRenderComponent(ConfigView(data))
1921 }
2022
2123 func configKeybindingSummary(keybindings any) string {
@@ -94,6 +96,57 @@ func keybindingStatus(binding renderedKeybinding) string {
9496 return "default"
9597 }
9698
99+func keybindingKeysValue(keys []string) string {
100+ return strings.Join(keys, ", ")
101+}
102+
103+type searchShortcutRow struct {
104+ Key string
105+ URL string
106+ Description string
107+}
108+
109+func searchShortcutRows(shortcuts map[string]port.SearchShortcut) []searchShortcutRow {
110+ if len(shortcuts) == 0 {
111+ return nil
112+ }
113+ keys := make([]string, 0, len(shortcuts))
114+ for key := range shortcuts {
115+ keys = append(keys, key)
116+ }
117+ sort.Strings(keys)
118+ rows := make([]searchShortcutRow, 0, len(keys))
119+ for _, key := range keys {
120+ shortcut := shortcuts[key]
121+ rows = append(rows, searchShortcutRow{
122+ Key: key,
123+ URL: shortcut.URL,
124+ Description: shortcut.Description,
125+ })
126+ }
127+ return rows
128+}
129+
130+func formatConfigInt(value int) string {
131+ return fmt.Sprintf("%d", value)
132+}
133+
134+func formatConfigFloat(value float64) string {
135+ return fmt.Sprintf("%g", value)
136+}
137+
138+func colorSchemeSelected(current, option string) bool {
139+ return strings.EqualFold(strings.TrimSpace(current), option)
140+}
141+
142+func performanceProfileSelected(current, option string) bool {
143+ return strings.EqualFold(strings.TrimSpace(current), option)
144+}
145+
146+func engineIsWebKit(engine string) bool {
147+ return strings.EqualFold(strings.TrimSpace(engine), "webkit")
148+}
149+
97150 func flattenValues(value any) []kvPair {
98151 rows := make([]kvPair, 0)
99152 flattenValue(&rows, "", reflect.ValueOf(value))
@@ -12,10 +12,12 @@ import (
12 type configRenderData struct {12 type configRenderData struct {
13 Config port.SystemviewConfigPayload13 Config port.SystemviewConfigPayload
14 Keybindings any14 Keybindings any
15+ Notice string
16+ Error string
15 }17 }
16 18
17-func configHTML(cfg port.SystemviewConfigPayload, keybindings any) string {19+func configHTML(data configRenderData) string {
18- return mustRenderComponent(ConfigView(configRenderData{Config: cfg, Keybindings: keybindings}))20+ return mustRenderComponent(ConfigView(data))
19 }21 }
20 22
21 func configKeybindingSummary(keybindings any) string {23 func configKeybindingSummary(keybindings any) string {
@@ -94,6 +96,57 @@ func keybindingStatus(binding renderedKeybinding) string {
94 return "default"96 return "default"
95 }97 }
96 98
99+func keybindingKeysValue(keys []string) string {
100+ return strings.Join(keys, ", ")
101+}
102+
103+type searchShortcutRow struct {
104+ Key string
105+ URL string
106+ Description string
107+}
108+
109+func searchShortcutRows(shortcuts map[string]port.SearchShortcut) []searchShortcutRow {
110+ if len(shortcuts) == 0 {
111+ return nil
112+ }
113+ keys := make([]string, 0, len(shortcuts))
114+ for key := range shortcuts {
115+ keys = append(keys, key)
116+ }
117+ sort.Strings(keys)
118+ rows := make([]searchShortcutRow, 0, len(keys))
119+ for _, key := range keys {
120+ shortcut := shortcuts[key]
121+ rows = append(rows, searchShortcutRow{
122+ Key: key,
123+ URL: shortcut.URL,
124+ Description: shortcut.Description,
125+ })
126+ }
127+ return rows
128+}
129+
130+func formatConfigInt(value int) string {
131+ return fmt.Sprintf("%d", value)
132+}
133+
134+func formatConfigFloat(value float64) string {
135+ return fmt.Sprintf("%g", value)
136+}
137+
138+func colorSchemeSelected(current, option string) bool {
139+ return strings.EqualFold(strings.TrimSpace(current), option)
140+}
141+
142+func performanceProfileSelected(current, option string) bool {
143+ return strings.EqualFold(strings.TrimSpace(current), option)
144+}
145+
146+func engineIsWebKit(engine string) bool {
147+ return strings.EqualFold(strings.TrimSpace(engine), "webkit")
148+}
149+
97 func flattenValues(value any) []kvPair {150 func flattenValues(value any) []kvPair {
98 rows := make([]kvPair, 0)151 rows := make([]kvPair, 0)
99 flattenValue(&rows, "", reflect.ValueOf(value))152 flattenValue(&rows, "", reflect.ValueOf(value))
M internal/ui/systemviews/history_actions.go +11 -0
@@ -50,6 +50,17 @@ func (a *App) HandleDOMAction(ctx context.Context, event DOMAction) error {
5050 a.renderRouteError(err)
5151 }
5252 return a.mountRenderedHTML()
53+ case RouteConfig:
54+ a.configError = ""
55+ if err := a.handleConfigAction(ctx, event); err != nil {
56+ a.configNotice = ""
57+ a.configError = err.Error()
58+ }
59+ a.loadShellTheme(ctx)
60+ if err := a.loadConfigRoute(ctx); err != nil {
61+ a.renderRouteError(err)
62+ }
63+ return a.mountRenderedHTML()
5364 default:
5465 return nil
5566 }
@@ -50,6 +50,17 @@ func (a *App) HandleDOMAction(ctx context.Context, event DOMAction) error {
50 a.renderRouteError(err)50 a.renderRouteError(err)
51 }51 }
52 return a.mountRenderedHTML()52 return a.mountRenderedHTML()
53+ case RouteConfig:
54+ a.configError = ""
55+ if err := a.handleConfigAction(ctx, event); err != nil {
56+ a.configNotice = ""
57+ a.configError = err.Error()
58+ }
59+ a.loadShellTheme(ctx)
60+ if err := a.loadConfigRoute(ctx); err != nil {
61+ a.renderRouteError(err)
62+ }
63+ return a.mountRenderedHTML()
53 default:64 default:
54 return nil65 return nil
55 }66 }

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