v0.33.0 62a09f5

schema_provider.go RawHistoryFolder

1package config
2
3import (
4	"fmt"
5
6	"github.com/bnema/dumber/internal/domain/entity"
7)
8
9// Section names for grouping config keys.
10const (
11	SectionAppearance       = "Appearance"
12	SectionLogging          = "Logging"
13	SectionHistory          = "History"
14	SectionWorkspace        = "Workspace"
15	SectionSession          = "Session"
16	SectionOmnibox          = "Omnibox"
17	SectionClipboard        = "Clipboard"
18	SectionContentFiltering = "Content Filtering"
19	SectionRendering        = "Rendering"
20	SectionMedia            = "Media"
21	SectionPrivacy          = "Privacy"
22	SectionUpdate           = "Update"
23	SectionDmenu            = "Dmenu"
24	SectionDebug            = "Debug"
25	SectionPerformance      = "Performance"
26	SectionRuntime          = "Runtime"
27	SectionDatabase         = "Database"
28	SectionSearch           = "Search"
29	SectionDownloads        = "Downloads"
30)
31
32// SchemaProvider implements port.ConfigSchemaProvider.
33type SchemaProvider struct{}
34
35// NewSchemaProvider creates a new SchemaProvider.
36func NewSchemaProvider() *SchemaProvider {
37	return &SchemaProvider{}
38}
39
40// GetSchema returns all configuration keys with their metadata.
41func (p *SchemaProvider) GetSchema() []entity.ConfigKeyInfo {
42	defaults := DefaultConfig()
43
44	keys := make([]entity.ConfigKeyInfo, 0, 100)
45
46	// Appearance section
47	keys = append(keys, p.getAppearanceKeys(defaults)...)
48
49	// Logging section
50	keys = append(keys, p.getLoggingKeys(defaults)...)
51
52	// History section
53	keys = append(keys, p.getHistoryKeys(defaults)...)
54
55	// Search section
56	keys = append(keys, p.getSearchKeys(defaults)...)
57
58	// Dmenu section
59	keys = append(keys, p.getDmenuKeys(defaults)...)
60
61	// Workspace section
62	keys = append(keys, p.getWorkspaceKeys(defaults)...)
63
64	// Session section
65	keys = append(keys, p.getSessionKeys(defaults)...)
66
67	// Omnibox section
68	keys = append(keys, p.getOmniboxKeys(defaults)...)
69
70	// Clipboard section
71	keys = append(keys, p.getClipboardKeys(defaults)...)
72
73	// Content Filtering section
74	keys = append(keys, p.getContentFilteringKeys(defaults)...)
75
76	// Rendering section
77	keys = append(keys, p.getRenderingKeys(defaults)...)
78
79	// Media section
80	keys = append(keys, p.getMediaKeys(defaults)...)
81
82	// Privacy section
83	keys = append(keys, p.getPrivacyKeys(defaults)...)
84
85	// Update section
86	keys = append(keys, p.getUpdateKeys(defaults)...)
87
88	// Debug section
89	keys = append(keys, p.getDebugKeys(defaults)...)
90
91	// Performance section
92	keys = append(keys, p.getPerformanceKeys(defaults)...)
93
94	// Runtime section
95	keys = append(keys, p.getRuntimeKeys(defaults)...)
96
97	// Database section
98	keys = append(keys, p.getDatabaseKeys()...)
99
100	// Downloads section
101	keys = append(keys, p.getDownloadsKeys(defaults)...)
102
103	return keys
104}
105
106func (*SchemaProvider) getAppearanceKeys(defaults *Config) []entity.ConfigKeyInfo {
107	return []entity.ConfigKeyInfo{
108		{
109			Key:         "appearance.color_scheme",
110			Type:        "string",
111			Default:     defaults.Appearance.ColorScheme,
112			Description: "Theme preference (follows system by default)",
113			Values:      []string{"default", "prefer-dark", "prefer-light"},
114			Section:     SectionAppearance,
115		},
116		{
117			Key:         "appearance.default_font_size",
118			Type:        "int",
119			Default:     fmt.Sprintf("%d", defaults.Appearance.DefaultFontSize),
120			Description: "Default font size in CSS pixels",
121			Range:       "1-72",
122			Section:     SectionAppearance,
123		},
124		{
125			Key:         "appearance.sans_font",
126			Type:        "string",
127			Default:     defaults.Appearance.SansFont,
128			Description: "Sans-serif font family for web pages",
129			Section:     SectionAppearance,
130		},
131		{
132			Key:         "appearance.serif_font",
133			Type:        "string",
134			Default:     defaults.Appearance.SerifFont,
135			Description: "Serif font family for web pages",
136			Section:     SectionAppearance,
137		},
138		{
139			Key:         "appearance.monospace_font",
140			Type:        "string",
141			Default:     defaults.Appearance.MonospaceFont,
142			Description: "Monospace font family for code",
143			Section:     SectionAppearance,
144		},
145		{
146			Key:         "appearance.gtk_font",
147			Type:        "string",
148			Default:     defaults.Appearance.GtkFont,
149			Description: "Native GTK UI font family for app chrome",
150			Section:     SectionAppearance,
151		},
152		{
153			Key:         "default_webpage_zoom",
154			Type:        "float64",
155			Default:     fmt.Sprintf("%.1f", defaults.DefaultWebpageZoom),
156			Description: "Default zoom level for web pages (1.0 = 100%%)",
157			Range:       "0.1-5.0",
158			Section:     SectionAppearance,
159		},
160		{
161			Key:         "default_ui_scale",
162			Type:        "float64",
163			Default:     fmt.Sprintf("%.1f", defaults.DefaultUIScale),
164			Description: "UI scale multiplier for GTK widgets",
165			Range:       "0.5-3.0",
166			Section:     SectionAppearance,
167		},
168		{
169			Key:         "sidebar_width",
170			Type:        "int",
171			Default:     fmt.Sprintf("%d", defaults.SidebarWidth),
172			Description: "Preferred width (px) for the history sidebar. 0 = default",
173			Range:       "0 or 280-380",
174			Section:     SectionAppearance,
175		},
176		{
177			Key:         "appearance.light_palette.*",
178			Type:        "string",
179			Default:     "(hex colors)",
180			Description: "Light theme color palette (background, surface, surface_variant, text, muted, accent, border)",
181			Section:     SectionAppearance,
182		},
183		{
184			Key:         "appearance.dark_palette.*",
185			Type:        "string",
186			Default:     "(hex colors)",
187			Description: "Dark theme color palette (background, surface, surface_variant, text, muted, accent, border)",
188			Section:     SectionAppearance,
189		},
190		{
191			Key:         "appearance.external_theme.enabled",
192			Type:        "bool",
193			Default:     fmt.Sprintf("%t", defaults.Appearance.ExternalTheme.Enabled),
194			Description: "Enable loading theme settings from an external provider",
195			Section:     SectionAppearance,
196		},
197		{
198			Key:         "appearance.external_theme.provider",
199			Type:        "string",
200			Default:     defaults.Appearance.ExternalTheme.Provider,
201			Description: "External theme provider",
202			Values:      []string{"noctalia"},
203			Section:     SectionAppearance,
204		},
205		{
206			Key:         "appearance.external_theme.format",
207			Type:        "string",
208			Default:     defaults.Appearance.ExternalTheme.Format,
209			Description: "External theme file format",
210			Values:      []string{"colors-json", "dumber-json"},
211			Section:     SectionAppearance,
212		},
213		{
214			Key:         "appearance.external_theme.path",
215			Type:        "string",
216			Default:     defaults.Appearance.ExternalTheme.Path,
217			Description: "Path to the external theme file",
218			Section:     SectionAppearance,
219		},
220	}
221}
222
223func (*SchemaProvider) getLoggingKeys(defaults *Config) []entity.ConfigKeyInfo {
224	return []entity.ConfigKeyInfo{
225		{
226			Key:         "logging.level",
227			Type:        "string",
228			Default:     defaults.Logging.Level,
229			Description: "Log verbosity level",
230			Values:      []string{"trace", "debug", "info", "warn", "error", "fatal"},
231			Section:     SectionLogging,
232		},
233		{
234			Key:         "logging.format",
235			Type:        "string",
236			Default:     defaults.Logging.Format,
237			Description: "Log output format",
238			Values:      []string{"text", "json", "console"},
239			Section:     SectionLogging,
240		},
241		{
242			Key:         "logging.max_age",
243			Type:        "int",
244			Default:     fmt.Sprintf("%d", defaults.Logging.MaxAge),
245			Description: "Maximum age of log files in days",
246			Range:       ">=0",
247			Section:     SectionLogging,
248		},
249		{
250			Key:         "logging.max_files",
251			Type:        "int",
252			Default:     fmt.Sprintf("%d", defaults.Logging.MaxFiles),
253			Description: "Maximum number of session log files to keep (0 disables count-based cleanup)",
254			Range:       ">=0",
255			Section:     SectionLogging,
256		},
257		{
258			Key:         "logging.log_dir",
259			Type:        "string",
260			Default:     defaults.Logging.LogDir,
261			Description: "Directory for log files",
262			Section:     SectionLogging,
263		},
264		{
265			Key:         "logging.enable_file_log",
266			Type:        "bool",
267			Default:     fmt.Sprintf("%t", defaults.Logging.EnableFileLog),
268			Description: "Enable logging to file",
269			Section:     SectionLogging,
270		},
271		{
272			Key:         "logging.capture_console",
273			Type:        "bool",
274			Default:     fmt.Sprintf("%t", defaults.Logging.CaptureConsole),
275			Description: "Capture browser console messages to log",
276			Section:     SectionLogging,
277		},
278		{
279			Key:         "logging.capture_gtk_logs",
280			Type:        "bool",
281			Default:     fmt.Sprintf("%t", defaults.Logging.CaptureGTKLogs),
282			Description: "Capture GTK and GLib log messages",
283			Section:     SectionLogging,
284		},
285	}
286}
287
288func (*SchemaProvider) getHistoryKeys(defaults *Config) []entity.ConfigKeyInfo {
289	return []entity.ConfigKeyInfo{
290		{
291			Key:         "history.max_entries",
292			Type:        "int",
293			Default:     fmt.Sprintf("%d", defaults.History.MaxEntries),
294			Description: "Maximum number of history entries to keep",
295			Range:       ">=0",
296			Section:     SectionHistory,
297		},
298		{
299			Key:         "history.retention_period_days",
300			Type:        "int",
301			Default:     fmt.Sprintf("%d", defaults.History.RetentionPeriodDays),
302			Description: "Number of days to retain history",
303			Range:       ">=0",
304			Section:     SectionHistory,
305		},
306		{
307			Key:         "history.cleanup_interval_days",
308			Type:        "int",
309			Default:     fmt.Sprintf("%d", defaults.History.CleanupIntervalDays),
310			Description: "Days between automatic history cleanup",
311			Range:       ">=0",
312			Section:     SectionHistory,
313		},
314	}
315}
316
317func (*SchemaProvider) getSearchKeys(defaults *Config) []entity.ConfigKeyInfo {
318	return []entity.ConfigKeyInfo{
319		{
320			Key:         "default_search_engine",
321			Type:        "string",
322			Default:     defaults.DefaultSearchEngine,
323			Description: "Default search engine URL (must contain %s placeholder)",
324			Section:     SectionSearch,
325		},
326		{
327			Key:         "search_shortcuts.<key>",
328			Type:        "object",
329			Default:     "(see defaults)",
330			Description: "Search shortcuts map with url and description fields",
331			Section:     SectionSearch,
332		},
333	}
334}
335
336func (*SchemaProvider) getDmenuKeys(defaults *Config) []entity.ConfigKeyInfo {
337	return []entity.ConfigKeyInfo{
338		{
339			Key:         "dmenu.max_history_days",
340			Type:        "int",
341			Default:     fmt.Sprintf("%d", defaults.Dmenu.MaxHistoryDays),
342			Description: "Number of days of history to show in dmenu (0 = all)",
343			Range:       ">=0",
344			Section:     SectionDmenu,
345		},
346		{
347			Key:         "dmenu.show_visit_count",
348			Type:        "bool",
349			Default:     fmt.Sprintf("%t", defaults.Dmenu.ShowVisitCount),
350			Description: "Show visit count in dmenu entries",
351			Section:     SectionDmenu,
352		},
353		{
354			Key:         "dmenu.show_last_visited",
355			Type:        "bool",
356			Default:     fmt.Sprintf("%t", defaults.Dmenu.ShowLastVisited),
357			Description: "Show last visited date in dmenu entries",
358			Section:     SectionDmenu,
359		},
360		{
361			Key:         "dmenu.history_prefix",
362			Type:        "string",
363			Default:     defaults.Dmenu.HistoryPrefix,
364			Description: "Prefix for history entries in dmenu",
365			Section:     SectionDmenu,
366		},
367		{
368			Key:         "dmenu.shortcut_prefix",
369			Type:        "string",
370			Default:     defaults.Dmenu.ShortcutPrefix,
371			Description: "Prefix for search shortcuts in dmenu",
372			Section:     SectionDmenu,
373		},
374		{
375			Key:         "dmenu.url_prefix",
376			Type:        "string",
377			Default:     defaults.Dmenu.URLPrefix,
378			Description: "Prefix for URL entries in dmenu",
379			Section:     SectionDmenu,
380		},
381		{
382			Key:         "dmenu.date_format",
383			Type:        "string",
384			Default:     defaults.Dmenu.DateFormat,
385			Description: "Go time format for dates in dmenu",
386			Section:     SectionDmenu,
387		},
388		{
389			Key:         "dmenu.sort_by_visit_count",
390			Type:        "bool",
391			Default:     fmt.Sprintf("%t", defaults.Dmenu.SortByVisitCount),
392			Description: "Sort dmenu entries by visit count",
393			Section:     SectionDmenu,
394		},
395	}
396}
397
398func (*SchemaProvider) getWorkspaceKeys(defaults *Config) []entity.ConfigKeyInfo {
399	return []entity.ConfigKeyInfo{
400		{
401			Key:         "workspace.new_pane_url",
402			Type:        "string",
403			Default:     defaults.Workspace.NewPaneURL,
404			Description: "URL loaded when creating a new pane",
405			Section:     SectionWorkspace,
406		},
407		{
408			Key:         "workspace.tab_bar_position",
409			Type:        "string",
410			Default:     defaults.Workspace.TabBarPosition,
411			Description: "Position of the tab bar",
412			Values:      []string{"top", "bottom"},
413			Section:     SectionWorkspace,
414		},
415		{
416			Key:         "workspace.hide_tab_bar_when_single_tab",
417			Type:        "bool",
418			Default:     fmt.Sprintf("%t", defaults.Workspace.HideTabBarWhenSingleTab),
419			Description: "Hide tab bar when only one tab exists",
420			Section:     SectionWorkspace,
421		},
422		{
423			Key:         "workspace.switch_to_tab_on_move",
424			Type:        "bool",
425			Default:     fmt.Sprintf("%t", defaults.Workspace.SwitchToTabOnMove),
426			Description: "Switch focus to tab when moving pane to it",
427			Section:     SectionWorkspace,
428		},
429		// Keymap preset
430		{
431			Key:         "workspace.keymap",
432			Type:        "string",
433			Default:     string(defaults.Workspace.Keymap),
434			Description: "Mode activation style: zellij (one shortcut per mode) or tmux (single prefix key)",
435			Values:      []string{string(KeymapZellij), string(KeymapTmux)},
436			Section:     SectionWorkspace,
437		},
438		{
439			Key:         "workspace.prefix_mode.activation_shortcut",
440			Type:        "string",
441			Default:     defaults.Workspace.PrefixMode.ActivationShortcut,
442			Description: "Prefix key used when workspace.keymap is tmux",
443			Section:     SectionWorkspace,
444		},
445		{
446			Key:         "workspace.prefix_mode.timeout_ms",
447			Type:        "int",
448			Default:     fmt.Sprintf("%d", defaults.Workspace.PrefixMode.TimeoutMilliseconds),
449			Description: "Prefix timeout in milliseconds (0 = no timeout)",
450			Range:       ">=0",
451			Section:     SectionWorkspace,
452		},
453		{
454			Key:         "workspace.prefix_mode.actions.<action>",
455			Type:        "object",
456			Default:     "(see defaults)",
457			Description: "One-shot actions available after the prefix key",
458			Section:     SectionWorkspace,
459		},
460		// Pane mode
461		{
462			Key:         "workspace.pane_mode.activation_shortcut",
463			Type:        "string",
464			Default:     defaults.Workspace.PaneMode.ActivationShortcut,
465			Description: "Shortcut to enter pane mode",
466			Section:     SectionWorkspace,
467		},
468		{
469			Key:         "workspace.pane_mode.timeout_ms",
470			Type:        "int",
471			Default:     fmt.Sprintf("%d", defaults.Workspace.PaneMode.TimeoutMilliseconds),
472			Description: "Pane mode timeout in milliseconds (0 = no timeout)",
473			Range:       ">=0",
474			Section:     SectionWorkspace,
475		},
476		{
477			Key:         "workspace.pane_mode.actions.<action>",
478			Type:        "[]string",
479			Default:     "(see defaults)",
480			Description: "Key bindings for pane mode actions",
481			Section:     SectionWorkspace,
482		},
483		// Tab mode
484		{
485			Key:         "workspace.tab_mode.activation_shortcut",
486			Type:        "string",
487			Default:     defaults.Workspace.TabMode.ActivationShortcut,
488			Description: "Shortcut to enter tab mode",
489			Section:     SectionWorkspace,
490		},
491		{
492			Key:         "workspace.tab_mode.timeout_ms",
493			Type:        "int",
494			Default:     fmt.Sprintf("%d", defaults.Workspace.TabMode.TimeoutMilliseconds),
495			Description: "Tab mode timeout in milliseconds (0 = no timeout)",
496			Range:       ">=0",
497			Section:     SectionWorkspace,
498		},
499		{
500			Key:         "workspace.tab_mode.actions.<action>",
501			Type:        "[]string",
502			Default:     "(see defaults)",
503			Description: "Key bindings for tab mode actions",
504			Section:     SectionWorkspace,
505		},
506		// Vim mode
507		{
508			Key:         "workspace.vim_mode.activation_shortcut",
509			Type:        "string",
510			Default:     defaults.Workspace.VimMode.ActivationShortcut,
511			Description: "Shortcut to enter vim scroll mode",
512			Section:     SectionWorkspace,
513		},
514		{
515			Key:         "workspace.vim_mode.timeout_ms",
516			Type:        "int",
517			Default:     fmt.Sprintf("%d", defaults.Workspace.VimMode.TimeoutMilliseconds),
518			Description: "Vim mode timeout in milliseconds (0 = no timeout)",
519			Range:       ">=0",
520			Section:     SectionWorkspace,
521		},
522		{
523			Key:         "workspace.vim_mode.sequence_timeout_ms",
524			Type:        "int",
525			Default:     fmt.Sprintf("%d", defaults.Workspace.VimMode.SequenceTimeoutMilliseconds),
526			Description: "Vim mode multi-key sequence timeout in milliseconds (0 = immediate ambiguity resolution)",
527			Range:       ">=0",
528			Section:     SectionWorkspace,
529		},
530		{
531			Key:         "workspace.vim_mode.preload_accessibility",
532			Type:        "bool",
533			Default:     fmt.Sprintf("%t", defaults.Workspace.VimMode.PreloadAccessibility),
534			Description: "Pre-enable browser accessibility when a WebView is shown (default false; enable on Vim Mode enter otherwise)",
535			Section:     SectionWorkspace,
536		},
537		{
538			Key:         "workspace.vim_mode.actions.<action>",
539			Type:        "object",
540			Default:     "(see defaults)",
541			Description: "Vim mode action object with keys ([]string) and desc (string)",
542			Section:     SectionWorkspace,
543		},
544		// Resize mode
545		{
546			Key:         "workspace.resize_mode.activation_shortcut",
547			Type:        "string",
548			Default:     defaults.Workspace.ResizeMode.ActivationShortcut,
549			Description: "Shortcut to enter resize mode",
550			Section:     SectionWorkspace,
551		},
552		{
553			Key:         "workspace.resize_mode.timeout_ms",
554			Type:        "int",
555			Default:     fmt.Sprintf("%d", defaults.Workspace.ResizeMode.TimeoutMilliseconds),
556			Description: "Resize mode timeout in milliseconds (0 = no timeout)",
557			Range:       ">=0",
558			Section:     SectionWorkspace,
559		},
560		{
561			Key:         "workspace.resize_mode.step_percent",
562			Type:        "float64",
563			Default:     fmt.Sprintf("%.1f", defaults.Workspace.ResizeMode.StepPercent),
564			Description: "Percentage to resize panes per step",
565			Section:     SectionWorkspace,
566		},
567		{
568			Key:         "workspace.resize_mode.min_pane_percent",
569			Type:        "float64",
570			Default:     fmt.Sprintf("%.1f", defaults.Workspace.ResizeMode.MinPanePercent),
571			Description: "Minimum pane size as percentage",
572			Section:     SectionWorkspace,
573		},
574		{
575			Key:         "workspace.resize_mode.actions.<action>",
576			Type:        "[]string",
577			Default:     "(see defaults)",
578			Description: "Key bindings for resize mode actions",
579			Section:     SectionWorkspace,
580		},
581		// Global shortcuts - managed via dedicated keybindings UI
582		{
583			Key:         "workspace.shortcuts.actions",
584			Type:        "object",
585			Default:     "(see defaults)",
586			Description: "Global keyboard shortcuts (use Keybindings tab to edit)",
587			Section:     SectionWorkspace,
588		},
589		// Floating pane
590		{
591			Key:         "workspace.floating_pane.width_pct",
592			Type:        "float64",
593			Default:     fmt.Sprintf("%.2f", defaults.Workspace.FloatingPane.WidthPct),
594			Description: "Floating pane width as a fraction of workspace width",
595			Range:       "(0,1]",
596			Section:     SectionWorkspace,
597		},
598		{
599			Key:         "workspace.floating_pane.height_pct",
600			Type:        "float64",
601			Default:     fmt.Sprintf("%.2f", defaults.Workspace.FloatingPane.HeightPct),
602			Description: "Floating pane height as a fraction of workspace height",
603			Range:       "(0,1]",
604			Section:     SectionWorkspace,
605		},
606		{
607			Key:         "workspace.floating_pane.profiles.<name>",
608			Type:        "object",
609			Default:     "(empty)",
610			Description: "Named floating pane URL profiles with keys, url, and optional desc",
611			Section:     SectionWorkspace,
612		},
613		// External links
614		{
615			Key:         "workspace.external_links.behavior",
616			Type:        "string",
617			Default:     string(defaults.Workspace.ExternalLinks.Behavior),
618			Description: "How URLs opened from external applications are placed",
619			Values:      []string{"windowed", "tabbed", "split", "stacked"},
620			Section:     SectionWorkspace,
621		},
622		{
623			Key:         "workspace.external_links.placement",
624			Type:        "string",
625			Default:     string(defaults.Workspace.ExternalLinks.Placement),
626			Description: "Placement direction for split external links",
627			Values:      []string{"right", "left", "top", "bottom"},
628			Section:     SectionWorkspace,
629		},
630		// Browsing contexts
631		{
632			Key:         "workspace.browsing_contexts.behavior",
633			Type:        "string",
634			Default:     string(defaults.Workspace.BrowsingContexts.Behavior),
635			Description: "How to handle JavaScript popup windows",
636			Values:      []string{"split", "stacked", "tabbed", "windowed"},
637			Section:     SectionWorkspace,
638		},
639		{
640			Key:         "workspace.browsing_contexts.placement",
641			Type:        "string",
642			Default:     defaults.Workspace.BrowsingContexts.Placement,
643			Description: "Placement direction for split popups",
644			Values:      []string{"right", "left", "top", "bottom"},
645			Section:     SectionWorkspace,
646		},
647		{
648			Key:         "workspace.browsing_contexts.blank_target_behavior",
649			Type:        "string",
650			Default:     defaults.Workspace.BrowsingContexts.BlankTargetBehavior,
651			Description: "How to handle target=\"_blank\" links",
652			Values:      []string{"split", "stacked", "tabbed"},
653			Section:     SectionWorkspace,
654		},
655		{
656			Key:         "workspace.browsing_contexts.open_in_new_pane",
657			Type:        "bool",
658			Default:     fmt.Sprintf("%t", defaults.Workspace.BrowsingContexts.OpenInNewPane),
659			Description: "Open popups in workspace instead of blocking",
660			Section:     SectionWorkspace,
661		},
662		{
663			Key:         "workspace.browsing_contexts.follow_pane_context",
664			Type:        "bool",
665			Default:     fmt.Sprintf("%t", defaults.Workspace.BrowsingContexts.FollowPaneContext),
666			Description: "Popup placement follows parent pane context",
667			Section:     SectionWorkspace,
668		},
669		{
670			Key:         "workspace.browsing_contexts.enable_smart_detection",
671			Type:        "bool",
672			Default:     fmt.Sprintf("%t", defaults.Workspace.BrowsingContexts.EnableSmartDetection),
673			Description: "Use window properties to detect popup intent",
674			Section:     SectionWorkspace,
675		},
676		{
677			Key:         "workspace.browsing_contexts.oauth_auto_close",
678			Type:        "bool",
679			Default:     fmt.Sprintf("%t", defaults.Workspace.BrowsingContexts.OAuthAutoClose),
680			Description: "Auto-close OAuth popups after success",
681			Section:     SectionWorkspace,
682		},
683		// Styling
684		{
685			Key:         "workspace.styling.border_width",
686			Type:        "int",
687			Default:     fmt.Sprintf("%d", defaults.Workspace.Styling.BorderWidth),
688			Description: "Active pane border width in pixels",
689			Range:       ">=0",
690			Section:     SectionWorkspace,
691		},
692		{
693			Key:         "workspace.styling.border_color",
694			Type:        "string",
695			Default:     defaults.Workspace.Styling.BorderColor,
696			Description: "Active pane border color (CSS or @theme variable)",
697			Section:     SectionWorkspace,
698		},
699		{
700			Key:         "workspace.styling.mode_border_width",
701			Type:        "int",
702			Default:     fmt.Sprintf("%d", defaults.Workspace.Styling.ModeBorderWidth),
703			Description: "Reserved; mode frame borders currently use fixed widths",
704			Range:       ">=0",
705			Section:     SectionWorkspace,
706		},
707		{
708			Key:         "workspace.styling.pane_mode_color",
709			Type:        "string",
710			Default:     defaults.Workspace.Styling.PaneModeColor,
711			Description: "Color for pane mode indicator",
712			Section:     SectionWorkspace,
713		},
714		{
715			Key:         "workspace.styling.tab_mode_color",
716			Type:        "string",
717			Default:     defaults.Workspace.Styling.TabModeColor,
718			Description: "Color for tab mode indicator",
719			Section:     SectionWorkspace,
720		},
721		{
722			Key:         "workspace.styling.session_mode_color",
723			Type:        "string",
724			Default:     defaults.Workspace.Styling.SessionModeColor,
725			Description: "Color for session mode indicator",
726			Section:     SectionWorkspace,
727		},
728		{
729			Key: "workspace.styling.vim_mode_color", Type: "string", Default: defaults.Workspace.Styling.VimModeColor,
730			Description: "Color for Vim mode indicator", Section: SectionWorkspace,
731		},
732		{
733			Key:         "workspace.styling.resize_mode_color",
734			Type:        "string",
735			Default:     defaults.Workspace.Styling.ResizeModeColor,
736			Description: "Color for resize mode indicator",
737			Section:     SectionWorkspace,
738		},
739		{
740			Key:         "workspace.styling.mode_indicator_toaster_enabled",
741			Type:        "bool",
742			Default:     fmt.Sprintf("%t", defaults.Workspace.Styling.ModeIndicatorToasterEnabled),
743			Description: "Show the bottom-left mode toaster while a mode is active (kept as the legend header while the legend is not off)",
744			Section:     SectionWorkspace,
745		},
746		{
747			Key:         "workspace.styling.mode_legend",
748			Type:        "string",
749			Default:     defaults.Workspace.Styling.ModeLegend,
750			Description: "Mode legend visibility",
751			Values:      []string{"always", "delay", "off"},
752			Section:     SectionWorkspace,
753		},
754		{
755			Key:         "workspace.styling.mode_legend_delay_ms",
756			Type:        "int",
757			Default:     fmt.Sprintf("%d", defaults.Workspace.Styling.ModeLegendDelayMs),
758			Description: "Delay before showing the mode legend in milliseconds",
759			Range:       ">=0",
760			Section:     SectionWorkspace,
761		},
762		{
763			Key: "workspace.styling.mode_legend_linger", Type: "bool", Default: fmt.Sprintf("%t", defaults.Workspace.Styling.ModeLegendLinger),
764			Description: "Keep the legend visible, read-only, after its mode ends while the pointer is over it, until the next key press or click.",
765			Section:     SectionWorkspace,
766		},
767		{
768			Key:         "workspace.styling.mode_legend_animations",
769			Type:        "bool",
770			Default:     fmt.Sprintf("%t", defaults.Workspace.Styling.ModeLegendAnimations),
771			Description: "Animate the mode legend and pressed keycaps",
772			Section:     SectionWorkspace,
773		},
774		{
775			Key:         "workspace.styling.mode_legend_collapsed",
776			Type:        "bool",
777			Default:     fmt.Sprintf("%t", defaults.Workspace.Styling.ModeLegendCollapsed),
778			Description: "Collapse the mode legend to its header; toggled by clicking the mode toaster and shared by all modes and windows",
779			Section:     SectionWorkspace,
780		},
781		{
782			Key:         "workspace.styling.transition_duration",
783			Type:        "int",
784			Default:     fmt.Sprintf("%d", defaults.Workspace.Styling.TransitionDuration),
785			Description: "Border animation duration in milliseconds",
786			Range:       ">=0",
787			Section:     SectionWorkspace,
788		},
789	}
790}
791
792func (*SchemaProvider) getSessionKeys(defaults *Config) []entity.ConfigKeyInfo {
793	return []entity.ConfigKeyInfo{
794		{
795			Key:         "session.auto_restore",
796			Type:        "bool",
797			Default:     fmt.Sprintf("%t", defaults.Session.AutoRestore),
798			Description: "Automatically restore last session on startup",
799			Section:     SectionSession,
800		},
801		{
802			Key:         "session.snapshot_interval_ms",
803			Type:        "int",
804			Default:     fmt.Sprintf("%d", defaults.Session.SnapshotIntervalMs),
805			Description: "Minimum interval between session snapshots",
806			Range:       ">=0",
807			Section:     SectionSession,
808		},
809		{
810			Key:         "session.max_exited_sessions",
811			Type:        "int",
812			Default:     fmt.Sprintf("%d", defaults.Session.MaxExitedSessions),
813			Description: "Maximum number of exited sessions to keep",
814			Range:       ">=0",
815			Section:     SectionSession,
816		},
817		{
818			Key:         "session.max_exited_session_age_days",
819			Type:        "int",
820			Default:     fmt.Sprintf("%d", defaults.Session.MaxExitedSessionAgeDays),
821			Description: "Maximum age of exited sessions in days",
822			Range:       ">=0",
823			Section:     SectionSession,
824		},
825		{
826			Key:         "session.session_mode.activation_shortcut",
827			Type:        "string",
828			Default:     defaults.Session.SessionMode.ActivationShortcut,
829			Description: "Shortcut to enter session mode",
830			Section:     SectionSession,
831		},
832		{
833			Key:         "session.session_mode.timeout_ms",
834			Type:        "int",
835			Default:     fmt.Sprintf("%d", defaults.Session.SessionMode.TimeoutMilliseconds),
836			Description: "Session mode timeout in milliseconds",
837			Range:       ">=0",
838			Section:     SectionSession,
839		},
840		{
841			Key:         "session.session_mode.actions.<action>",
842			Type:        "[]string",
843			Default:     "(see defaults)",
844			Description: "Key bindings for session mode actions",
845			Section:     SectionSession,
846		},
847	}
848}
849
850func (*SchemaProvider) getOmniboxKeys(defaults *Config) []entity.ConfigKeyInfo {
851	return []entity.ConfigKeyInfo{
852		{
853			Key:         "omnibox.style",
854			Type:        "string",
855			Default:     defaults.Omnibox.Style,
856			Description: "Visual treatment for the omnibox",
857			Values:      []string{"multiplexer", "command", "minimal"},
858			Section:     SectionOmnibox,
859		},
860		{
861			Key:         "omnibox.initial_behavior",
862			Type:        "string",
863			Default:     string(defaults.Omnibox.InitialBehavior),
864			Description: "What to show when omnibox opens with empty input",
865			Values: []string{
866				string(OmniboxInitialBehaviorRecent),
867				string(OmniboxInitialBehaviorMostVisited),
868				string(OmniboxInitialBehaviorNone),
869			},
870			Section: SectionOmnibox,
871		},
872		{
873			Key:         "omnibox.max_history_days",
874			Type:        "int",
875			Default:     fmt.Sprintf("%d", defaults.Omnibox.MaxHistoryDays),
876			Description: "Days of omnibox suggestion history (typed and initial); 0 = all history, never deletes",
877			Range:       ">=0",
878			Section:     SectionOmnibox,
879		},
880		{
881			Key:         "omnibox.auto_open_on_new_pane",
882			Type:        "bool",
883			Default:     fmt.Sprintf("%t", defaults.Omnibox.AutoOpenOnNewPane),
884			Description: "Auto-open omnibox when creating new pane",
885			Section:     SectionOmnibox,
886		},
887	}
888}
889
890func (*SchemaProvider) getContentFilteringKeys(defaults *Config) []entity.ConfigKeyInfo {
891	return []entity.ConfigKeyInfo{
892		{
893			Key:         "content_filtering.enabled",
894			Type:        "bool",
895			Default:     fmt.Sprintf("%t", defaults.ContentFiltering.Enabled),
896			Description: "Enable ad blocking and content filtering",
897			Section:     SectionContentFiltering,
898		},
899		{
900			Key:         "content_filtering.auto_update",
901			Type:        "bool",
902			Default:     fmt.Sprintf("%t", defaults.ContentFiltering.AutoUpdate),
903			Description: "Auto-update filter lists from GitHub",
904			Section:     SectionContentFiltering,
905		},
906	}
907}
908
909func (*SchemaProvider) getClipboardKeys(defaults *Config) []entity.ConfigKeyInfo {
910	return []entity.ConfigKeyInfo{
911		{
912			Key:         "clipboard.auto_copy_on_selection",
913			Type:        "bool",
914			Default:     fmt.Sprintf("%t", defaults.Clipboard.AutoCopyOnSelection),
915			Description: "Auto-copy selected text to clipboard (zellij-style)",
916			Section:     SectionClipboard,
917		},
918	}
919}
920
921func (*SchemaProvider) getRenderingKeys(defaults *Config) []entity.ConfigKeyInfo {
922	return []entity.ConfigKeyInfo{
923		{
924			Key:         "engine.type",
925			Type:        "string",
926			Default:     defaults.Engine.Type,
927			Description: "Browser engine selection; CEF is the default and WebKitGTK is the fallback",
928			Values:      []string{"cef", "webkit"},
929			Section:     SectionRendering,
930		},
931		{
932			Key:         "engine.webkit.gsk_renderer",
933			Type:        "string",
934			Default:     string(defaults.Engine.WebKit.GSKRenderer),
935			Description: "GTK Scene Kit renderer selection",
936			Values:      []string{"auto", "opengl", "vulkan", "cairo"},
937			Section:     SectionRendering,
938		},
939		{
940			Key:         "engine.webkit.disable_dmabuf_renderer",
941			Type:        "bool",
942			Default:     fmt.Sprintf("%t", defaults.Engine.WebKit.DisableDMABufRenderer),
943			Description: "Disable DMA-BUF accelerated renderer",
944			Section:     SectionRendering,
945		},
946		{
947			Key:         "engine.webkit.force_compositing_mode",
948			Type:        "bool",
949			Default:     fmt.Sprintf("%t", defaults.Engine.WebKit.ForceCompositingMode),
950			Description: "Force accelerated compositing",
951			Section:     SectionRendering,
952		},
953		{
954			Key:         "engine.webkit.disable_compositing_mode",
955			Type:        "bool",
956			Default:     fmt.Sprintf("%t", defaults.Engine.WebKit.DisableCompositingMode),
957			Description: "Disable accelerated compositing",
958			Section:     SectionRendering,
959		},
960		{
961			Key:         "engine.webkit.disable_mipmaps",
962			Type:        "bool",
963			Default:     fmt.Sprintf("%t", defaults.Engine.WebKit.DisableMipmaps),
964			Description: "Disable mipmap generation",
965			Section:     SectionRendering,
966		},
967		{
968			Key:         "engine.webkit.prefer_gl",
969			Type:        "bool",
970			Default:     fmt.Sprintf("%t", defaults.Engine.WebKit.PreferGL),
971			Description: "Force OpenGL over GLES",
972			Section:     SectionRendering,
973		},
974		{
975			Key:         "engine.webkit.draw_compositing_indicators",
976			Type:        "bool",
977			Default:     fmt.Sprintf("%t", defaults.Engine.WebKit.DrawCompositingIndicators),
978			Description: "Show compositing layer borders",
979			Section:     SectionRendering,
980		},
981		{
982			Key:         "engine.webkit.show_fps",
983			Type:        "bool",
984			Default:     fmt.Sprintf("%t", defaults.Engine.WebKit.ShowFPS),
985			Description: "Display WebKit FPS counter",
986			Section:     SectionRendering,
987		},
988		{
989			Key:         "engine.webkit.sample_memory",
990			Type:        "bool",
991			Default:     fmt.Sprintf("%t", defaults.Engine.WebKit.SampleMemory),
992			Description: "Enable memory sampling",
993			Section:     SectionRendering,
994		},
995		{
996			Key:         "engine.webkit.debug_frames",
997			Type:        "bool",
998			Default:     fmt.Sprintf("%t", defaults.Engine.WebKit.DebugFrames),
999			Description: "Enable frame timing debug output",
1000			Section:     SectionRendering,
1001		},
1002	}
1003}
1004
1005func (*SchemaProvider) getMediaKeys(defaults *Config) []entity.ConfigKeyInfo {
1006	return []entity.ConfigKeyInfo{
1007		{
1008			Key:         "media.hardware_decoding",
1009			Type:        "string",
1010			Default:     string(defaults.Media.HardwareDecodingMode),
1011			Description: "Hardware vs software video decoding",
1012			Values:      []string{"auto", "force", "disable"},
1013			Section:     SectionMedia,
1014		},
1015		{
1016			Key:         "media.prefer_av1",
1017			Type:        "bool",
1018			Default:     fmt.Sprintf("%t", defaults.Media.PreferAV1),
1019			Description: "Prioritize AV1 codec when available",
1020			Section:     SectionMedia,
1021		},
1022		{
1023			Key:         "media.show_diagnostics",
1024			Type:        "bool",
1025			Default:     fmt.Sprintf("%t", defaults.Media.ShowDiagnosticsOnStartup),
1026			Description: "Show media capability warnings at startup",
1027			Section:     SectionMedia,
1028		},
1029		{
1030			Key:         "engine.webkit.force_vsync",
1031			Type:        "bool",
1032			Default:     fmt.Sprintf("%t", defaults.Engine.WebKit.ForceVSync),
1033			Description: "Force VSync for WebKit fallback video playback",
1034			Section:     SectionMedia,
1035		},
1036		{
1037			Key:         "engine.webkit.gl_rendering_mode",
1038			Type:        "string",
1039			Default:     string(defaults.Engine.WebKit.GLRenderingMode),
1040			Description: "WebKit fallback GStreamer/OpenGL API selection",
1041			Values:      []string{"auto", "gles2", "gl3", "none"},
1042			Section:     SectionMedia,
1043		},
1044		{
1045			Key:         "engine.webkit.gstreamer_debug_level",
1046			Type:        "int",
1047			Default:     fmt.Sprintf("%d", defaults.Engine.WebKit.GStreamerDebugLevel),
1048			Description: "WebKit fallback GStreamer debug verbosity",
1049			Range:       "0-5",
1050			Section:     SectionMedia,
1051		},
1052	}
1053}
1054
1055func (*SchemaProvider) getPrivacyKeys(defaults *Config) []entity.ConfigKeyInfo {
1056	return []entity.ConfigKeyInfo{
1057		{
1058			Key:         "engine.cookie_policy",
1059			Type:        "string",
1060			Default:     string(defaults.Engine.CookiePolicy),
1061			Description: "Cookie acceptance policy",
1062			Values:      []string{"always", "no_third_party", "never"},
1063			Section:     SectionPrivacy,
1064		},
1065		{
1066			Key:         "engine.webkit.itp_enabled",
1067			Type:        "bool",
1068			Default:     fmt.Sprintf("%t", defaults.Engine.WebKit.ITPEnabled),
1069			Description: "Enable WebKit fallback Intelligent Tracking Prevention",
1070			Section:     SectionPrivacy,
1071		},
1072	}
1073}
1074
1075func (*SchemaProvider) getUpdateKeys(defaults *Config) []entity.ConfigKeyInfo {
1076	return []entity.ConfigKeyInfo{
1077		{
1078			Key:         "update.enable_on_startup",
1079			Type:        "bool",
1080			Default:     fmt.Sprintf("%t", defaults.Update.EnableOnStartup),
1081			Description: "Check for updates on browser startup",
1082			Section:     SectionUpdate,
1083		},
1084		{
1085			Key:         "update.auto_download",
1086			Type:        "bool",
1087			Default:     fmt.Sprintf("%t", defaults.Update.AutoDownload),
1088			Description: "Auto-download updates in background",
1089			Section:     SectionUpdate,
1090		},
1091		{
1092			Key:         "update.notify_on_new_settings",
1093			Type:        "bool",
1094			Default:     fmt.Sprintf("%t", defaults.Update.NotifyOnNewSettings),
1095			Description: "Show toast when new config settings available",
1096			Section:     SectionUpdate,
1097		},
1098	}
1099}
1100
1101func (*SchemaProvider) getDebugKeys(defaults *Config) []entity.ConfigKeyInfo {
1102	return []entity.ConfigKeyInfo{
1103		{
1104			Key:         "debug.enable_devtools",
1105			Type:        "bool",
1106			Default:     fmt.Sprintf("%t", defaults.Debug.EnableDevTools),
1107			Description: "Enable browser developer tools (F12)",
1108			Section:     SectionDebug,
1109		},
1110		{
1111			Key:         "engine.cef.log_file",
1112			Type:        "string",
1113			Default:     defaults.Engine.CEF.LogFile,
1114			Description: "Path to the CEF runtime log file written during startup and browser process execution",
1115			Section:     SectionDebug,
1116		},
1117		{
1118			Key:         "engine.cef.log_severity",
1119			Type:        "int32",
1120			Default:     fmt.Sprintf("%d", defaults.Engine.CEF.LogSeverity),
1121			Description: "CEF internal log verbosity (0=default, 1=verbose, 2=info, 3=warning, 4=error, 99=disable)",
1122			Section:     SectionDebug,
1123		},
1124		{
1125			Key:         "engine.cef.trace_handlers",
1126			Type:        "bool",
1127			Default:     fmt.Sprintf("%t", defaults.Engine.CEF.TraceHandlers),
1128			Description: "Trace purego-cef handler wrapping and refcount activity",
1129			Section:     SectionDebug,
1130		},
1131		{
1132			Key:         "engine.cef.enable_audio_handler",
1133			Type:        "bool",
1134			Default:     fmt.Sprintf("%t", defaults.Engine.CEF.EnableAudioHandler),
1135			Description: "Enable the experimental CEF audio handler bridge",
1136			Section:     SectionDebug,
1137		},
1138	}
1139}
1140
1141func (*SchemaProvider) getPerformanceKeys(defaults *Config) []entity.ConfigKeyInfo {
1142	return []entity.ConfigKeyInfo{
1143		{
1144			Key:         "engine.profile",
1145			Type:        "string",
1146			Default:     string(defaults.Engine.Profile),
1147			Description: "Performance profile preset (custom enables manual tuning)",
1148			Values:      []string{"default", "lite", "balanced", "max", "custom"},
1149			Section:     SectionPerformance,
1150		},
1151		{
1152			Key:         "engine.zoom_cache_size",
1153			Type:        "int",
1154			Default:     fmt.Sprintf("%d", defaults.Engine.ZoomCacheSize),
1155			Description: "Number of domain zoom levels to cache",
1156			Range:       ">=0",
1157			Section:     SectionPerformance,
1158		},
1159		{
1160			Key:         "engine.pool_prewarm_count",
1161			Type:        "int",
1162			Default:     fmt.Sprintf("%d", defaults.Engine.PoolPrewarmCount),
1163			Description: "WebViews to pre-create at startup",
1164			Range:       ">=0",
1165			Section:     SectionPerformance,
1166		},
1167		{
1168			Key:         "engine.cef.render_stack",
1169			Type:        "string",
1170			Default:     string(defaults.Engine.CEF.CEFRenderStack()),
1171			Description: "CEF GPU render stack",
1172			Values:      []string{"vulkan", "egl"},
1173			Section:     SectionPerformance,
1174		},
1175		{
1176			Key:         "engine.cef.adaptive_windowless_frame_rate",
1177			Type:        "bool",
1178			Default:     fmt.Sprintf("%t", defaults.Engine.CEF.AdaptiveWindowlessFrameRate),
1179			Description: "Adapt CEF OSR frame rate to the active Wayland monitor refresh rate when no explicit windowless_frame_rate is set",
1180			Section:     SectionPerformance,
1181		},
1182		{
1183			Key:         "engine.cef.windowless_frame_rate",
1184			Type:        "int32",
1185			Default:     fmt.Sprintf("%d", defaults.Engine.CEF.WindowlessFrameRate),
1186			Description: "Explicit maximum off-screen frame rate for the CEF engine; 0 enables adaptive mode when configured",
1187			Range:       ">=0",
1188			Section:     SectionPerformance,
1189		},
1190		{
1191			Key:         "engine.cef.windowless_frame_rate_max",
1192			Type:        "int32",
1193			Default:     fmt.Sprintf("%d", defaults.Engine.CEF.CEFWindowlessFrameRateMax()),
1194			Description: "Hard cap for adaptive CEF OSR frame rate; 0 uses the built-in default cap",
1195			Range:       ">=0",
1196			Section:     SectionPerformance,
1197		},
1198		{
1199			Key:         "engine.cef.idle_runtime_timeout_ms",
1200			Type:        "int",
1201			Default:     fmt.Sprintf("%d", defaults.Engine.CEF.IdleRuntimeTimeoutMs),
1202			Description: "Bounded opt-in CEF runtime residency in milliseconds after the last window closes; 0 disables, changes require restart",
1203			Range:       "0..300000",
1204			Section:     SectionPerformance,
1205		},
1206		{
1207			Key:         "engine.cef.input.scroll_wheel_multiplier",
1208			Type:        "float64",
1209			Default:     fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.ScrollWheelMultiplier),
1210			Description: "CEF mouse wheel scroll delta multiplier",
1211			Range:       ">0",
1212			Section:     SectionPerformance,
1213		},
1214		{
1215			Key:         "engine.cef.input.scroll_precise_multiplier",
1216			Type:        "float64",
1217			Default:     fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.ScrollPreciseMultiplier),
1218			Description: "CEF precise/surface scroll delta multiplier for touchpads and high-resolution wheels",
1219			Range:       ">0",
1220			Section:     SectionPerformance,
1221		},
1222		{
1223			Key:         "engine.cef.input.scroll_horizontal_multiplier",
1224			Type:        "float64",
1225			Default:     fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.ScrollHorizontalMultiplier),
1226			Description: "CEF horizontal scroll delta multiplier",
1227			Range:       ">0",
1228			Section:     SectionPerformance,
1229		},
1230		{
1231			Key:         "engine.cef.input.scroll_vertical_multiplier",
1232			Type:        "float64",
1233			Default:     fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.ScrollVerticalMultiplier),
1234			Description: "CEF vertical scroll delta multiplier; touchpad vertical speed uses this together with scroll_precise_multiplier",
1235			Range:       ">0",
1236			Section:     SectionPerformance,
1237		},
1238		{
1239			Key:         "engine.cef.input.scroll_max_delta",
1240			Type:        "int32",
1241			Default:     fmt.Sprintf("%d", defaults.Engine.CEF.Input.ScrollMaxDelta),
1242			Description: "CEF maximum absolute scroll delta after scaling; 0 disables clamping",
1243			Range:       ">=0",
1244			Section:     SectionPerformance,
1245		},
1246		{
1247			Key:         "engine.cef.input.scroll_touchpad_inertia",
1248			Type:        "bool",
1249			Default:     fmt.Sprintf("%t", defaults.Engine.CEF.Input.ScrollTouchpadInertia),
1250			Description: "Enable direct touchpad tracking with exponential release decay for CEF views",
1251			Section:     SectionPerformance,
1252		},
1253		{
1254			Key:         "engine.cef.input.scroll_wheel_smoothing",
1255			Type:        "bool",
1256			Default:     fmt.Sprintf("%t", defaults.Engine.CEF.Input.ScrollWheelSmoothing),
1257			Description: "Interpolate accepted CEF wheel impulses across frames without a long coast",
1258			Section:     SectionPerformance,
1259		},
1260		{
1261			Key:         "engine.cef.input.touchpad_navigation_enabled",
1262			Type:        "bool",
1263			Default:     fmt.Sprintf("%t", defaults.Engine.CEF.Input.TouchpadNavigationEnabled),
1264			Description: "Enable deliberate two-finger touchpad swipe back/forward navigation for CEF views",
1265			Section:     SectionPerformance,
1266		},
1267		{
1268			Key:         "engine.cef.input.touchpad_navigation_min_delta",
1269			Type:        "float64",
1270			Default:     fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.TouchpadNavigationMinDelta),
1271			Description: "Minimum accumulated horizontal touchpad swipe delta required before release triggers CEF back/forward navigation",
1272			Range:       ">0",
1273			Section:     SectionPerformance,
1274		},
1275		{
1276			Key:     "engine.cef.input.touchpad_navigation_max_vertical_ratio",
1277			Type:    "float64",
1278			Default: fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.TouchpadNavigationMaxVerticalRatio),
1279			Description: "Maximum vertical-to-horizontal delta ratio allowed for CEF touchpad back/forward swipes; " +
1280				"does not affect vertical scroll speed",
1281			Range:   ">0",
1282			Section: SectionPerformance,
1283		},
1284		{
1285			Key:         "engine.webkit.skia_cpu_painting_threads",
1286			Type:        "int",
1287			Default:     fmt.Sprintf("%d", defaults.Engine.WebKit.SkiaCPUPaintingThreads),
1288			Description: "Skia CPU rendering threads (0=unset, custom profile only)",
1289			Range:       ">=0",
1290			Section:     SectionPerformance,
1291		},
1292		{
1293			Key:         "engine.webkit.skia_gpu_painting_threads",
1294			Type:        "int",
1295			Default:     fmt.Sprintf("%d", defaults.Engine.WebKit.SkiaGPUPaintingThreads),
1296			Description: "Skia GPU rendering threads (-1=unset, 0=disable, custom profile only)",
1297			Range:       ">=-1",
1298			Section:     SectionPerformance,
1299		},
1300		{
1301			Key:         "engine.webkit.skia_enable_cpu_rendering",
1302			Type:        "bool",
1303			Default:     fmt.Sprintf("%t", defaults.Engine.WebKit.SkiaEnableCPURendering),
1304			Description: "Force CPU rendering (custom profile only)",
1305			Section:     SectionPerformance,
1306		},
1307		{
1308			Key:         "engine.webkit.web_process_memory_limit_mb",
1309			Type:        "int",
1310			Default:     fmt.Sprintf("%d", defaults.Engine.WebKit.WebProcessMemoryLimitMB),
1311			Description: "Web process memory limit in MB (0=unset, custom profile only)",
1312			Range:       ">=0",
1313			Section:     SectionPerformance,
1314		},
1315		{
1316			Key:         "engine.webkit.web_process_memory_poll_interval_sec",
1317			Type:        "float64",
1318			Default:     fmt.Sprintf("%.1f", defaults.Engine.WebKit.WebProcessMemoryPollIntervalSec),
1319			Description: "Memory check interval in seconds (0=default 30s, custom profile only)",
1320			Range:       ">=0",
1321			Section:     SectionPerformance,
1322		},
1323		{
1324			Key:         "engine.webkit.web_process_memory_conservative_threshold",
1325			Type:        "float64",
1326			Default:     fmt.Sprintf("%.2f", defaults.Engine.WebKit.WebProcessMemoryConservativeThreshold),
1327			Description: "Conservative memory cleanup threshold (0-1, custom profile only)",
1328			Range:       "0-1",
1329			Section:     SectionPerformance,
1330		},
1331		{
1332			Key:         "engine.webkit.web_process_memory_strict_threshold",
1333			Type:        "float64",
1334			Default:     fmt.Sprintf("%.2f", defaults.Engine.WebKit.WebProcessMemoryStrictThreshold),
1335			Description: "Strict memory cleanup threshold (0-1, custom profile only)",
1336			Range:       "0-1",
1337			Section:     SectionPerformance,
1338		},
1339		{
1340			Key:         "engine.webkit.network_process_memory_limit_mb",
1341			Type:        "int",
1342			Default:     fmt.Sprintf("%d", defaults.Engine.WebKit.NetworkProcessMemoryLimitMB),
1343			Description: "Network process memory limit in MB (0=unset, custom profile only)",
1344			Range:       ">=0",
1345			Section:     SectionPerformance,
1346		},
1347		{
1348			Key:         "engine.webkit.network_process_memory_poll_interval_sec",
1349			Type:        "float64",
1350			Default:     fmt.Sprintf("%.1f", defaults.Engine.WebKit.NetworkProcessMemoryPollIntervalSec),
1351			Description: "Network memory check interval in seconds (custom profile only)",
1352			Range:       ">=0",
1353			Section:     SectionPerformance,
1354		},
1355		{
1356			Key:         "engine.webkit.network_process_memory_conservative_threshold",
1357			Type:        "float64",
1358			Default:     fmt.Sprintf("%.2f", defaults.Engine.WebKit.NetworkProcessMemoryConservativeThreshold),
1359			Description: "Network conservative memory threshold (0-1, custom profile only)",
1360			Range:       "0-1",
1361			Section:     SectionPerformance,
1362		},
1363		{
1364			Key:         "engine.webkit.network_process_memory_strict_threshold",
1365			Type:        "float64",
1366			Default:     fmt.Sprintf("%.2f", defaults.Engine.WebKit.NetworkProcessMemoryStrictThreshold),
1367			Description: "Network strict memory threshold (0-1, custom profile only)",
1368			Range:       "0-1",
1369			Section:     SectionPerformance,
1370		},
1371	}
1372}
1373
1374func (*SchemaProvider) getRuntimeKeys(defaults *Config) []entity.ConfigKeyInfo {
1375	return []entity.ConfigKeyInfo{
1376		{
1377			Key:     "engine.cef.cef_dir",
1378			Type:    "string",
1379			Default: defaults.Engine.CEF.CEFDir,
1380			Description: "Optional CEF runtime directory containing libcef.so and Resources; overrides the default " +
1381				"runtime discovery order (CEF_DIR env, /usr/lib/cef, ~/.local/share/cef)",
1382			Section: SectionRuntime,
1383		},
1384		{
1385			Key:         "engine.webkit.prefix",
1386			Type:        "string",
1387			Default:     defaults.Engine.WebKit.Prefix,
1388			Description: "Custom runtime prefix for WebKitGTK/GTK",
1389			Section:     SectionRuntime,
1390		},
1391	}
1392}
1393
1394func (*SchemaProvider) getDatabaseKeys() []entity.ConfigKeyInfo {
1395	// Get actual default path for display
1396	dbPath := "$XDG_DATA_HOME/" + appName + "/" + databaseName
1397	if path, err := GetDatabaseFile(); err == nil {
1398		dbPath = path
1399	}
1400
1401	return []entity.ConfigKeyInfo{
1402		{
1403			Key:         "database.path",
1404			Type:        "string",
1405			Default:     dbPath,
1406			Description: "Path to SQLite database file",
1407			Section:     SectionDatabase,
1408		},
1409	}
1410}
1411
1412func (*SchemaProvider) getDownloadsKeys(_ *Config) []entity.ConfigKeyInfo {
1413	return []entity.ConfigKeyInfo{
1414		{
1415			Key:         "downloads.path",
1416			Type:        "string",
1417			Default:     "(empty = $XDG_DOWNLOAD_DIR or ~/Downloads)",
1418			Description: "Directory where downloads are saved",
1419			Section:     SectionDownloads,
1420		},
1421	}
1422}

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