v0.29.0 21b3150

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	SectionUpdate           = "Update"
22	SectionDmenu            = "Dmenu"
23	SectionDebug            = "Debug"
24	SectionPerformance      = "Performance"
25	SectionRuntime          = "Runtime"
26	SectionDatabase         = "Database"
27	SectionSearch           = "Search"
28	SectionDownloads        = "Downloads"
29)
30
31// SchemaProvider implements port.ConfigSchemaProvider.
32type SchemaProvider struct{}
33
34// NewSchemaProvider creates a new SchemaProvider.
35func NewSchemaProvider() *SchemaProvider {
36	return &SchemaProvider{}
37}
38
39// GetSchema returns all configuration keys with their metadata.
40func (p *SchemaProvider) GetSchema() []entity.ConfigKeyInfo {
41	defaults := DefaultConfig()
42
43	keys := make([]entity.ConfigKeyInfo, 0, 100)
44
45	// Appearance section
46	keys = append(keys, p.getAppearanceKeys(defaults)...)
47
48	// Logging section
49	keys = append(keys, p.getLoggingKeys(defaults)...)
50
51	// History section
52	keys = append(keys, p.getHistoryKeys(defaults)...)
53
54	// Search section
55	keys = append(keys, p.getSearchKeys(defaults)...)
56
57	// Dmenu section
58	keys = append(keys, p.getDmenuKeys(defaults)...)
59
60	// Workspace section
61	keys = append(keys, p.getWorkspaceKeys(defaults)...)
62
63	// Session section
64	keys = append(keys, p.getSessionKeys(defaults)...)
65
66	// Omnibox section
67	keys = append(keys, p.getOmniboxKeys(defaults)...)
68
69	// Clipboard section
70	keys = append(keys, p.getClipboardKeys(defaults)...)
71
72	// Content Filtering section
73	keys = append(keys, p.getContentFilteringKeys(defaults)...)
74
75	// Rendering section
76	keys = append(keys, p.getRenderingKeys(defaults)...)
77
78	// Media section
79	keys = append(keys, p.getMediaKeys(defaults)...)
80
81	// Update section
82	keys = append(keys, p.getUpdateKeys(defaults)...)
83
84	// Debug section
85	keys = append(keys, p.getDebugKeys(defaults)...)
86
87	// Performance section
88	keys = append(keys, p.getPerformanceKeys(defaults)...)
89
90	// Runtime section
91	keys = append(keys, p.getRuntimeKeys(defaults)...)
92
93	// Database section
94	keys = append(keys, p.getDatabaseKeys()...)
95
96	// Downloads section
97	keys = append(keys, p.getDownloadsKeys(defaults)...)
98
99	return keys
100}
101
102func (*SchemaProvider) getAppearanceKeys(defaults *Config) []entity.ConfigKeyInfo {
103	return []entity.ConfigKeyInfo{
104		{
105			Key:         "appearance.color_scheme",
106			Type:        "string",
107			Default:     defaults.Appearance.ColorScheme,
108			Description: "Theme preference (follows system by default)",
109			Values:      []string{"default", "prefer-dark", "prefer-light"},
110			Section:     SectionAppearance,
111		},
112		{
113			Key:         "appearance.default_font_size",
114			Type:        "int",
115			Default:     fmt.Sprintf("%d", defaults.Appearance.DefaultFontSize),
116			Description: "Default font size in CSS pixels",
117			Range:       "1-72",
118			Section:     SectionAppearance,
119		},
120		{
121			Key:         "appearance.sans_font",
122			Type:        "string",
123			Default:     defaults.Appearance.SansFont,
124			Description: "Sans-serif font family for web pages",
125			Section:     SectionAppearance,
126		},
127		{
128			Key:         "appearance.serif_font",
129			Type:        "string",
130			Default:     defaults.Appearance.SerifFont,
131			Description: "Serif font family for web pages",
132			Section:     SectionAppearance,
133		},
134		{
135			Key:         "appearance.monospace_font",
136			Type:        "string",
137			Default:     defaults.Appearance.MonospaceFont,
138			Description: "Monospace font family for code",
139			Section:     SectionAppearance,
140		},
141		{
142			Key:         "appearance.gtk_font",
143			Type:        "string",
144			Default:     defaults.Appearance.GtkFont,
145			Description: "Native GTK UI font family for app chrome",
146			Section:     SectionAppearance,
147		},
148		{
149			Key:         "default_webpage_zoom",
150			Type:        "float64",
151			Default:     fmt.Sprintf("%.1f", defaults.DefaultWebpageZoom),
152			Description: "Default zoom level for web pages (1.0 = 100%%)",
153			Range:       "0.1-5.0",
154			Section:     SectionAppearance,
155		},
156		{
157			Key:         "default_ui_scale",
158			Type:        "float64",
159			Default:     fmt.Sprintf("%.1f", defaults.DefaultUIScale),
160			Description: "UI scale multiplier for GTK widgets",
161			Range:       "0.5-3.0",
162			Section:     SectionAppearance,
163		},
164		{
165			Key:         "appearance.light_palette.*",
166			Type:        "string",
167			Default:     "(hex colors)",
168			Description: "Light theme color palette (background, surface, surface_variant, text, muted, accent, border)",
169			Section:     SectionAppearance,
170		},
171		{
172			Key:         "appearance.dark_palette.*",
173			Type:        "string",
174			Default:     "(hex colors)",
175			Description: "Dark theme color palette (background, surface, surface_variant, text, muted, accent, border)",
176			Section:     SectionAppearance,
177		},
178	}
179}
180
181func (*SchemaProvider) getLoggingKeys(defaults *Config) []entity.ConfigKeyInfo {
182	return []entity.ConfigKeyInfo{
183		{
184			Key:         "logging.level",
185			Type:        "string",
186			Default:     defaults.Logging.Level,
187			Description: "Log verbosity level",
188			Values:      []string{"trace", "debug", "info", "warn", "error", "fatal"},
189			Section:     SectionLogging,
190		},
191		{
192			Key:         "logging.format",
193			Type:        "string",
194			Default:     defaults.Logging.Format,
195			Description: "Log output format",
196			Values:      []string{"text", "json", "console"},
197			Section:     SectionLogging,
198		},
199		{
200			Key:         "logging.max_age",
201			Type:        "int",
202			Default:     fmt.Sprintf("%d", defaults.Logging.MaxAge),
203			Description: "Maximum age of log files in days",
204			Range:       ">=0",
205			Section:     SectionLogging,
206		},
207		{
208			Key:         "logging.max_files",
209			Type:        "int",
210			Default:     fmt.Sprintf("%d", defaults.Logging.MaxFiles),
211			Description: "Maximum number of session log files to keep (0 disables count-based cleanup)",
212			Range:       ">=0",
213			Section:     SectionLogging,
214		},
215		{
216			Key:         "logging.log_dir",
217			Type:        "string",
218			Default:     defaults.Logging.LogDir,
219			Description: "Directory for log files",
220			Section:     SectionLogging,
221		},
222		{
223			Key:         "logging.enable_file_log",
224			Type:        "bool",
225			Default:     fmt.Sprintf("%t", defaults.Logging.EnableFileLog),
226			Description: "Enable logging to file",
227			Section:     SectionLogging,
228		},
229		{
230			Key:         "logging.capture_console",
231			Type:        "bool",
232			Default:     fmt.Sprintf("%t", defaults.Logging.CaptureConsole),
233			Description: "Capture browser console messages to log",
234			Section:     SectionLogging,
235		},
236	}
237}
238
239func (*SchemaProvider) getHistoryKeys(defaults *Config) []entity.ConfigKeyInfo {
240	return []entity.ConfigKeyInfo{
241		{
242			Key:         "history.max_entries",
243			Type:        "int",
244			Default:     fmt.Sprintf("%d", defaults.History.MaxEntries),
245			Description: "Maximum number of history entries to keep",
246			Range:       ">=0",
247			Section:     SectionHistory,
248		},
249		{
250			Key:         "history.retention_period_days",
251			Type:        "int",
252			Default:     fmt.Sprintf("%d", defaults.History.RetentionPeriodDays),
253			Description: "Number of days to retain history",
254			Range:       ">=0",
255			Section:     SectionHistory,
256		},
257		{
258			Key:         "history.cleanup_interval_days",
259			Type:        "int",
260			Default:     fmt.Sprintf("%d", defaults.History.CleanupIntervalDays),
261			Description: "Days between automatic history cleanup",
262			Range:       ">=0",
263			Section:     SectionHistory,
264		},
265	}
266}
267
268func (*SchemaProvider) getSearchKeys(defaults *Config) []entity.ConfigKeyInfo {
269	return []entity.ConfigKeyInfo{
270		{
271			Key:         "default_search_engine",
272			Type:        "string",
273			Default:     defaults.DefaultSearchEngine,
274			Description: "Default search engine URL (must contain %s placeholder)",
275			Section:     SectionSearch,
276		},
277		{
278			Key:         "search_shortcuts.<key>",
279			Type:        "object",
280			Default:     "(see defaults)",
281			Description: "Search shortcuts map with url and description fields",
282			Section:     SectionSearch,
283		},
284	}
285}
286
287func (*SchemaProvider) getDmenuKeys(defaults *Config) []entity.ConfigKeyInfo {
288	return []entity.ConfigKeyInfo{
289		{
290			Key:         "dmenu.max_history_days",
291			Type:        "int",
292			Default:     fmt.Sprintf("%d", defaults.Dmenu.MaxHistoryDays),
293			Description: "Number of days of history to show in dmenu (0 = all)",
294			Range:       ">=0",
295			Section:     SectionDmenu,
296		},
297		{
298			Key:         "dmenu.show_visit_count",
299			Type:        "bool",
300			Default:     fmt.Sprintf("%t", defaults.Dmenu.ShowVisitCount),
301			Description: "Show visit count in dmenu entries",
302			Section:     SectionDmenu,
303		},
304		{
305			Key:         "dmenu.show_last_visited",
306			Type:        "bool",
307			Default:     fmt.Sprintf("%t", defaults.Dmenu.ShowLastVisited),
308			Description: "Show last visited date in dmenu entries",
309			Section:     SectionDmenu,
310		},
311		{
312			Key:         "dmenu.history_prefix",
313			Type:        "string",
314			Default:     defaults.Dmenu.HistoryPrefix,
315			Description: "Prefix for history entries in dmenu",
316			Section:     SectionDmenu,
317		},
318		{
319			Key:         "dmenu.shortcut_prefix",
320			Type:        "string",
321			Default:     defaults.Dmenu.ShortcutPrefix,
322			Description: "Prefix for search shortcuts in dmenu",
323			Section:     SectionDmenu,
324		},
325		{
326			Key:         "dmenu.url_prefix",
327			Type:        "string",
328			Default:     defaults.Dmenu.URLPrefix,
329			Description: "Prefix for URL entries in dmenu",
330			Section:     SectionDmenu,
331		},
332		{
333			Key:         "dmenu.date_format",
334			Type:        "string",
335			Default:     defaults.Dmenu.DateFormat,
336			Description: "Go time format for dates in dmenu",
337			Section:     SectionDmenu,
338		},
339		{
340			Key:         "dmenu.sort_by_visit_count",
341			Type:        "bool",
342			Default:     fmt.Sprintf("%t", defaults.Dmenu.SortByVisitCount),
343			Description: "Sort dmenu entries by visit count",
344			Section:     SectionDmenu,
345		},
346	}
347}
348
349func (*SchemaProvider) getWorkspaceKeys(defaults *Config) []entity.ConfigKeyInfo {
350	return []entity.ConfigKeyInfo{
351		{
352			Key:         "workspace.new_pane_url",
353			Type:        "string",
354			Default:     defaults.Workspace.NewPaneURL,
355			Description: "URL loaded when creating a new pane",
356			Section:     SectionWorkspace,
357		},
358		{
359			Key:         "workspace.tab_bar_position",
360			Type:        "string",
361			Default:     defaults.Workspace.TabBarPosition,
362			Description: "Position of the tab bar",
363			Values:      []string{"top", "bottom"},
364			Section:     SectionWorkspace,
365		},
366		{
367			Key:         "workspace.hide_tab_bar_when_single_tab",
368			Type:        "bool",
369			Default:     fmt.Sprintf("%t", defaults.Workspace.HideTabBarWhenSingleTab),
370			Description: "Hide tab bar when only one tab exists",
371			Section:     SectionWorkspace,
372		},
373		{
374			Key:         "workspace.switch_to_tab_on_move",
375			Type:        "bool",
376			Default:     fmt.Sprintf("%t", defaults.Workspace.SwitchToTabOnMove),
377			Description: "Switch focus to tab when moving pane to it",
378			Section:     SectionWorkspace,
379		},
380		// Pane mode
381		{
382			Key:         "workspace.pane_mode.activation_shortcut",
383			Type:        "string",
384			Default:     defaults.Workspace.PaneMode.ActivationShortcut,
385			Description: "Shortcut to enter pane mode",
386			Section:     SectionWorkspace,
387		},
388		{
389			Key:         "workspace.pane_mode.timeout_ms",
390			Type:        "int",
391			Default:     fmt.Sprintf("%d", defaults.Workspace.PaneMode.TimeoutMilliseconds),
392			Description: "Pane mode timeout in milliseconds (0 = no timeout)",
393			Range:       ">=0",
394			Section:     SectionWorkspace,
395		},
396		{
397			Key:         "workspace.pane_mode.actions.<action>",
398			Type:        "[]string",
399			Default:     "(see defaults)",
400			Description: "Key bindings for pane mode actions",
401			Section:     SectionWorkspace,
402		},
403		// Tab mode
404		{
405			Key:         "workspace.tab_mode.activation_shortcut",
406			Type:        "string",
407			Default:     defaults.Workspace.TabMode.ActivationShortcut,
408			Description: "Shortcut to enter tab mode",
409			Section:     SectionWorkspace,
410		},
411		{
412			Key:         "workspace.tab_mode.timeout_ms",
413			Type:        "int",
414			Default:     fmt.Sprintf("%d", defaults.Workspace.TabMode.TimeoutMilliseconds),
415			Description: "Tab mode timeout in milliseconds (0 = no timeout)",
416			Range:       ">=0",
417			Section:     SectionWorkspace,
418		},
419		{
420			Key:         "workspace.tab_mode.actions.<action>",
421			Type:        "[]string",
422			Default:     "(see defaults)",
423			Description: "Key bindings for tab mode actions",
424			Section:     SectionWorkspace,
425		},
426		// Resize mode
427		{
428			Key:         "workspace.resize_mode.activation_shortcut",
429			Type:        "string",
430			Default:     defaults.Workspace.ResizeMode.ActivationShortcut,
431			Description: "Shortcut to enter resize mode",
432			Section:     SectionWorkspace,
433		},
434		{
435			Key:         "workspace.resize_mode.timeout_ms",
436			Type:        "int",
437			Default:     fmt.Sprintf("%d", defaults.Workspace.ResizeMode.TimeoutMilliseconds),
438			Description: "Resize mode timeout in milliseconds (0 = no timeout)",
439			Range:       ">=0",
440			Section:     SectionWorkspace,
441		},
442		{
443			Key:         "workspace.resize_mode.step_percent",
444			Type:        "float64",
445			Default:     fmt.Sprintf("%.1f", defaults.Workspace.ResizeMode.StepPercent),
446			Description: "Percentage to resize panes per step",
447			Section:     SectionWorkspace,
448		},
449		{
450			Key:         "workspace.resize_mode.min_pane_percent",
451			Type:        "float64",
452			Default:     fmt.Sprintf("%.1f", defaults.Workspace.ResizeMode.MinPanePercent),
453			Description: "Minimum pane size as percentage",
454			Section:     SectionWorkspace,
455		},
456		{
457			Key:         "workspace.resize_mode.actions.<action>",
458			Type:        "[]string",
459			Default:     "(see defaults)",
460			Description: "Key bindings for resize mode actions",
461			Section:     SectionWorkspace,
462		},
463		// Global shortcuts - managed via dedicated keybindings UI
464		{
465			Key:         "workspace.shortcuts.actions",
466			Type:        "object",
467			Default:     "(see defaults)",
468			Description: "Global keyboard shortcuts (use Keybindings tab to edit)",
469			Section:     SectionWorkspace,
470		},
471		// Floating pane
472		{
473			Key:         "workspace.floating_pane.width_pct",
474			Type:        "float64",
475			Default:     fmt.Sprintf("%.2f", defaults.Workspace.FloatingPane.WidthPct),
476			Description: "Floating pane width as a fraction of workspace width",
477			Range:       "(0,1]",
478			Section:     SectionWorkspace,
479		},
480		{
481			Key:         "workspace.floating_pane.height_pct",
482			Type:        "float64",
483			Default:     fmt.Sprintf("%.2f", defaults.Workspace.FloatingPane.HeightPct),
484			Description: "Floating pane height as a fraction of workspace height",
485			Range:       "(0,1]",
486			Section:     SectionWorkspace,
487		},
488		{
489			Key:         "workspace.floating_pane.profiles.<name>",
490			Type:        "object",
491			Default:     "(empty)",
492			Description: "Named floating pane URL profiles with keys, url, and optional desc",
493			Section:     SectionWorkspace,
494		},
495		// Browsing contexts
496		{
497			Key:         "workspace.browsing_contexts.behavior",
498			Type:        "string",
499			Default:     string(defaults.Workspace.BrowsingContexts.Behavior),
500			Description: "How to handle JavaScript popup windows",
501			Values:      []string{"split", "stacked", "tabbed", "windowed"},
502			Section:     SectionWorkspace,
503		},
504		{
505			Key:         "workspace.browsing_contexts.placement",
506			Type:        "string",
507			Default:     defaults.Workspace.BrowsingContexts.Placement,
508			Description: "Placement direction for split popups",
509			Values:      []string{"right", "left", "top", "bottom"},
510			Section:     SectionWorkspace,
511		},
512		{
513			Key:         "workspace.browsing_contexts.blank_target_behavior",
514			Type:        "string",
515			Default:     defaults.Workspace.BrowsingContexts.BlankTargetBehavior,
516			Description: "How to handle target=\"_blank\" links",
517			Values:      []string{"split", "stacked", "tabbed"},
518			Section:     SectionWorkspace,
519		},
520		{
521			Key:         "workspace.browsing_contexts.open_in_new_pane",
522			Type:        "bool",
523			Default:     fmt.Sprintf("%t", defaults.Workspace.BrowsingContexts.OpenInNewPane),
524			Description: "Open popups in workspace instead of blocking",
525			Section:     SectionWorkspace,
526		},
527		{
528			Key:         "workspace.browsing_contexts.follow_pane_context",
529			Type:        "bool",
530			Default:     fmt.Sprintf("%t", defaults.Workspace.BrowsingContexts.FollowPaneContext),
531			Description: "Popup placement follows parent pane context",
532			Section:     SectionWorkspace,
533		},
534		{
535			Key:         "workspace.browsing_contexts.enable_smart_detection",
536			Type:        "bool",
537			Default:     fmt.Sprintf("%t", defaults.Workspace.BrowsingContexts.EnableSmartDetection),
538			Description: "Use window properties to detect popup intent",
539			Section:     SectionWorkspace,
540		},
541		{
542			Key:         "workspace.browsing_contexts.oauth_auto_close",
543			Type:        "bool",
544			Default:     fmt.Sprintf("%t", defaults.Workspace.BrowsingContexts.OAuthAutoClose),
545			Description: "Auto-close OAuth popups after success",
546			Section:     SectionWorkspace,
547		},
548		// Styling
549		{
550			Key:         "workspace.styling.border_width",
551			Type:        "int",
552			Default:     fmt.Sprintf("%d", defaults.Workspace.Styling.BorderWidth),
553			Description: "Active pane border width in pixels",
554			Range:       ">=0",
555			Section:     SectionWorkspace,
556		},
557		{
558			Key:         "workspace.styling.border_color",
559			Type:        "string",
560			Default:     defaults.Workspace.Styling.BorderColor,
561			Description: "Active pane border color (CSS or @theme variable)",
562			Section:     SectionWorkspace,
563		},
564		{
565			Key:         "workspace.styling.mode_border_width",
566			Type:        "int",
567			Default:     fmt.Sprintf("%d", defaults.Workspace.Styling.ModeBorderWidth),
568			Description: "Border width for modal mode indicators",
569			Range:       ">=0",
570			Section:     SectionWorkspace,
571		},
572		{
573			Key:         "workspace.styling.pane_mode_color",
574			Type:        "string",
575			Default:     defaults.Workspace.Styling.PaneModeColor,
576			Description: "Color for pane mode indicator",
577			Section:     SectionWorkspace,
578		},
579		{
580			Key:         "workspace.styling.tab_mode_color",
581			Type:        "string",
582			Default:     defaults.Workspace.Styling.TabModeColor,
583			Description: "Color for tab mode indicator",
584			Section:     SectionWorkspace,
585		},
586		{
587			Key:         "workspace.styling.session_mode_color",
588			Type:        "string",
589			Default:     defaults.Workspace.Styling.SessionModeColor,
590			Description: "Color for session mode indicator",
591			Section:     SectionWorkspace,
592		},
593		{
594			Key:         "workspace.styling.resize_mode_color",
595			Type:        "string",
596			Default:     defaults.Workspace.Styling.ResizeModeColor,
597			Description: "Color for resize mode indicator",
598			Section:     SectionWorkspace,
599		},
600		{
601			Key:         "workspace.styling.mode_indicator_toaster_enabled",
602			Type:        "bool",
603			Default:     fmt.Sprintf("%t", defaults.Workspace.Styling.ModeIndicatorToasterEnabled),
604			Description: "Show toaster notification for modal modes",
605			Section:     SectionWorkspace,
606		},
607		{
608			Key:         "workspace.styling.transition_duration",
609			Type:        "int",
610			Default:     fmt.Sprintf("%d", defaults.Workspace.Styling.TransitionDuration),
611			Description: "Border animation duration in milliseconds",
612			Range:       ">=0",
613			Section:     SectionWorkspace,
614		},
615	}
616}
617
618func (*SchemaProvider) getSessionKeys(defaults *Config) []entity.ConfigKeyInfo {
619	return []entity.ConfigKeyInfo{
620		{
621			Key:         "session.auto_restore",
622			Type:        "bool",
623			Default:     fmt.Sprintf("%t", defaults.Session.AutoRestore),
624			Description: "Automatically restore last session on startup",
625			Section:     SectionSession,
626		},
627		{
628			Key:         "session.snapshot_interval_ms",
629			Type:        "int",
630			Default:     fmt.Sprintf("%d", defaults.Session.SnapshotIntervalMs),
631			Description: "Minimum interval between session snapshots",
632			Range:       ">=0",
633			Section:     SectionSession,
634		},
635		{
636			Key:         "session.max_exited_sessions",
637			Type:        "int",
638			Default:     fmt.Sprintf("%d", defaults.Session.MaxExitedSessions),
639			Description: "Maximum number of exited sessions to keep",
640			Range:       ">=0",
641			Section:     SectionSession,
642		},
643		{
644			Key:         "session.max_exited_session_age_days",
645			Type:        "int",
646			Default:     fmt.Sprintf("%d", defaults.Session.MaxExitedSessionAgeDays),
647			Description: "Maximum age of exited sessions in days",
648			Range:       ">=0",
649			Section:     SectionSession,
650		},
651		{
652			Key:         "session.session_mode.activation_shortcut",
653			Type:        "string",
654			Default:     defaults.Session.SessionMode.ActivationShortcut,
655			Description: "Shortcut to enter session mode",
656			Section:     SectionSession,
657		},
658		{
659			Key:         "session.session_mode.timeout_ms",
660			Type:        "int",
661			Default:     fmt.Sprintf("%d", defaults.Session.SessionMode.TimeoutMilliseconds),
662			Description: "Session mode timeout in milliseconds",
663			Range:       ">=0",
664			Section:     SectionSession,
665		},
666		{
667			Key:         "session.session_mode.actions.<action>",
668			Type:        "[]string",
669			Default:     "(see defaults)",
670			Description: "Key bindings for session mode actions",
671			Section:     SectionSession,
672		},
673	}
674}
675
676func (*SchemaProvider) getOmniboxKeys(defaults *Config) []entity.ConfigKeyInfo {
677	return []entity.ConfigKeyInfo{
678		{
679			Key:         "omnibox.initial_behavior",
680			Type:        "string",
681			Default:     string(defaults.Omnibox.InitialBehavior),
682			Description: "What to show when omnibox opens with empty input",
683			Values: []string{
684				string(OmniboxInitialBehaviorRecent),
685				string(OmniboxInitialBehaviorMostVisited),
686				string(OmniboxInitialBehaviorNone),
687			},
688			Section: SectionOmnibox,
689		},
690		{
691			Key:         "omnibox.most_visited_days",
692			Type:        "int",
693			Default:     fmt.Sprintf("%d", defaults.Omnibox.MostVisitedDays),
694			Description: "Days of history to consider for most_visited (0 = all history)",
695			Range:       ">=0",
696			Section:     SectionOmnibox,
697		},
698		{
699			Key:         "omnibox.auto_open_on_new_pane",
700			Type:        "bool",
701			Default:     fmt.Sprintf("%t", defaults.Omnibox.AutoOpenOnNewPane),
702			Description: "Auto-open omnibox when creating new pane",
703			Section:     SectionOmnibox,
704		},
705	}
706}
707
708func (*SchemaProvider) getContentFilteringKeys(defaults *Config) []entity.ConfigKeyInfo {
709	return []entity.ConfigKeyInfo{
710		{
711			Key:         "content_filtering.enabled",
712			Type:        "bool",
713			Default:     fmt.Sprintf("%t", defaults.ContentFiltering.Enabled),
714			Description: "Enable ad blocking and content filtering",
715			Section:     SectionContentFiltering,
716		},
717		{
718			Key:         "content_filtering.auto_update",
719			Type:        "bool",
720			Default:     fmt.Sprintf("%t", defaults.ContentFiltering.AutoUpdate),
721			Description: "Auto-update filter lists from GitHub",
722			Section:     SectionContentFiltering,
723		},
724	}
725}
726
727func (*SchemaProvider) getClipboardKeys(defaults *Config) []entity.ConfigKeyInfo {
728	return []entity.ConfigKeyInfo{
729		{
730			Key:         "clipboard.auto_copy_on_selection",
731			Type:        "bool",
732			Default:     fmt.Sprintf("%t", defaults.Clipboard.AutoCopyOnSelection),
733			Description: "Auto-copy selected text to clipboard (zellij-style)",
734			Section:     SectionClipboard,
735		},
736	}
737}
738
739func (*SchemaProvider) getRenderingKeys(defaults *Config) []entity.ConfigKeyInfo {
740	return []entity.ConfigKeyInfo{
741		{
742			Key:         "engine.webkit.gsk_renderer",
743			Type:        "string",
744			Default:     string(defaults.Engine.WebKit.GSKRenderer),
745			Description: "GTK Scene Kit renderer selection",
746			Values:      []string{"auto", "opengl", "vulkan", "cairo"},
747			Section:     SectionRendering,
748		},
749		{
750			Key:         "engine.webkit.disable_dmabuf_renderer",
751			Type:        "bool",
752			Default:     fmt.Sprintf("%t", defaults.Engine.WebKit.DisableDMABufRenderer),
753			Description: "Disable DMA-BUF accelerated renderer",
754			Section:     SectionRendering,
755		},
756		{
757			Key:         "engine.webkit.force_compositing_mode",
758			Type:        "bool",
759			Default:     fmt.Sprintf("%t", defaults.Engine.WebKit.ForceCompositingMode),
760			Description: "Force accelerated compositing",
761			Section:     SectionRendering,
762		},
763		{
764			Key:         "engine.webkit.disable_compositing_mode",
765			Type:        "bool",
766			Default:     fmt.Sprintf("%t", defaults.Engine.WebKit.DisableCompositingMode),
767			Description: "Disable accelerated compositing",
768			Section:     SectionRendering,
769		},
770		{
771			Key:         "engine.webkit.disable_mipmaps",
772			Type:        "bool",
773			Default:     fmt.Sprintf("%t", defaults.Engine.WebKit.DisableMipmaps),
774			Description: "Disable mipmap generation",
775			Section:     SectionRendering,
776		},
777		{
778			Key:         "engine.webkit.prefer_gl",
779			Type:        "bool",
780			Default:     fmt.Sprintf("%t", defaults.Engine.WebKit.PreferGL),
781			Description: "Force OpenGL over GLES",
782			Section:     SectionRendering,
783		},
784		{
785			Key:         "engine.webkit.draw_compositing_indicators",
786			Type:        "bool",
787			Default:     fmt.Sprintf("%t", defaults.Engine.WebKit.DrawCompositingIndicators),
788			Description: "Show compositing layer borders",
789			Section:     SectionRendering,
790		},
791		{
792			Key:         "engine.webkit.show_fps",
793			Type:        "bool",
794			Default:     fmt.Sprintf("%t", defaults.Engine.WebKit.ShowFPS),
795			Description: "Display WebKit FPS counter",
796			Section:     SectionRendering,
797		},
798		{
799			Key:         "engine.webkit.sample_memory",
800			Type:        "bool",
801			Default:     fmt.Sprintf("%t", defaults.Engine.WebKit.SampleMemory),
802			Description: "Enable memory sampling",
803			Section:     SectionRendering,
804		},
805		{
806			Key:         "engine.webkit.debug_frames",
807			Type:        "bool",
808			Default:     fmt.Sprintf("%t", defaults.Engine.WebKit.DebugFrames),
809			Description: "Enable frame timing debug output",
810			Section:     SectionRendering,
811		},
812	}
813}
814
815func (*SchemaProvider) getMediaKeys(defaults *Config) []entity.ConfigKeyInfo {
816	return []entity.ConfigKeyInfo{
817		{
818			Key:         "media.hardware_decoding",
819			Type:        "string",
820			Default:     string(defaults.Media.HardwareDecodingMode),
821			Description: "Hardware vs software video decoding",
822			Values:      []string{"auto", "force", "disable"},
823			Section:     SectionMedia,
824		},
825		{
826			Key:         "media.prefer_av1",
827			Type:        "bool",
828			Default:     fmt.Sprintf("%t", defaults.Media.PreferAV1),
829			Description: "Prioritize AV1 codec when available",
830			Section:     SectionMedia,
831		},
832		{
833			Key:         "media.show_diagnostics",
834			Type:        "bool",
835			Default:     fmt.Sprintf("%t", defaults.Media.ShowDiagnosticsOnStartup),
836			Description: "Show media capability warnings at startup",
837			Section:     SectionMedia,
838		},
839	}
840}
841
842func (*SchemaProvider) getUpdateKeys(defaults *Config) []entity.ConfigKeyInfo {
843	return []entity.ConfigKeyInfo{
844		{
845			Key:         "update.enable_on_startup",
846			Type:        "bool",
847			Default:     fmt.Sprintf("%t", defaults.Update.EnableOnStartup),
848			Description: "Check for updates on browser startup",
849			Section:     SectionUpdate,
850		},
851		{
852			Key:         "update.auto_download",
853			Type:        "bool",
854			Default:     fmt.Sprintf("%t", defaults.Update.AutoDownload),
855			Description: "Auto-download updates in background",
856			Section:     SectionUpdate,
857		},
858		{
859			Key:         "update.notify_on_new_settings",
860			Type:        "bool",
861			Default:     fmt.Sprintf("%t", defaults.Update.NotifyOnNewSettings),
862			Description: "Show toast when new config settings available",
863			Section:     SectionUpdate,
864		},
865	}
866}
867
868func (*SchemaProvider) getDebugKeys(defaults *Config) []entity.ConfigKeyInfo {
869	return []entity.ConfigKeyInfo{
870		{
871			Key:         "debug.enable_devtools",
872			Type:        "bool",
873			Default:     fmt.Sprintf("%t", defaults.Debug.EnableDevTools),
874			Description: "Enable browser developer tools (F12)",
875			Section:     SectionDebug,
876		},
877		{
878			Key:         "engine.cef.log_file",
879			Type:        "string",
880			Default:     defaults.Engine.CEF.LogFile,
881			Description: "Path to the CEF runtime log file written during startup and browser process execution",
882			Section:     SectionDebug,
883		},
884		{
885			Key:         "engine.cef.log_severity",
886			Type:        "int32",
887			Default:     fmt.Sprintf("%d", defaults.Engine.CEF.LogSeverity),
888			Description: "CEF internal log verbosity (0=default, 1=verbose, 2=info, 3=warning, 4=error, 99=disable)",
889			Section:     SectionDebug,
890		},
891		{
892			Key:         "engine.cef.trace_handlers",
893			Type:        "bool",
894			Default:     fmt.Sprintf("%t", defaults.Engine.CEF.TraceHandlers),
895			Description: "Trace purego-cef handler wrapping and refcount activity",
896			Section:     SectionDebug,
897		},
898		{
899			Key:         "engine.cef.enable_audio_handler",
900			Type:        "bool",
901			Default:     fmt.Sprintf("%t", defaults.Engine.CEF.EnableAudioHandler),
902			Description: "Enable the experimental CEF audio handler bridge",
903			Section:     SectionDebug,
904		},
905	}
906}
907
908func (*SchemaProvider) getPerformanceKeys(defaults *Config) []entity.ConfigKeyInfo {
909	return []entity.ConfigKeyInfo{
910		{
911			Key:         "engine.profile",
912			Type:        "string",
913			Default:     string(defaults.Engine.Profile),
914			Description: "Performance profile preset (custom enables manual tuning)",
915			Values:      []string{"default", "lite", "balanced", "max", "custom"},
916			Section:     SectionPerformance,
917		},
918		{
919			Key:         "engine.zoom_cache_size",
920			Type:        "int",
921			Default:     fmt.Sprintf("%d", defaults.Engine.ZoomCacheSize),
922			Description: "Number of domain zoom levels to cache",
923			Range:       ">=0",
924			Section:     SectionPerformance,
925		},
926		{
927			Key:         "engine.pool_prewarm_count",
928			Type:        "int",
929			Default:     fmt.Sprintf("%d", defaults.Engine.PoolPrewarmCount),
930			Description: "WebViews to pre-create at startup",
931			Range:       ">=0",
932			Section:     SectionPerformance,
933		},
934		{
935			Key:         "engine.cef.render_stack",
936			Type:        "string",
937			Default:     string(defaults.Engine.CEF.CEFRenderStack()),
938			Description: "CEF GPU render stack",
939			Values:      []string{"vulkan", "egl"},
940			Section:     SectionPerformance,
941		},
942		{
943			Key:         "engine.cef.adaptive_windowless_frame_rate",
944			Type:        "bool",
945			Default:     fmt.Sprintf("%t", defaults.Engine.CEF.AdaptiveWindowlessFrameRate),
946			Description: "Adapt CEF OSR frame rate to the active Wayland monitor refresh rate when no explicit windowless_frame_rate is set",
947			Section:     SectionPerformance,
948		},
949		{
950			Key:         "engine.cef.windowless_frame_rate",
951			Type:        "int32",
952			Default:     fmt.Sprintf("%d", defaults.Engine.CEF.WindowlessFrameRate),
953			Description: "Explicit maximum off-screen frame rate for the CEF engine; 0 enables adaptive mode when configured",
954			Range:       ">=0",
955			Section:     SectionPerformance,
956		},
957		{
958			Key:         "engine.cef.windowless_frame_rate_max",
959			Type:        "int32",
960			Default:     fmt.Sprintf("%d", defaults.Engine.CEF.CEFWindowlessFrameRateMax()),
961			Description: "Hard cap for adaptive CEF OSR frame rate; 0 uses the built-in default cap",
962			Range:       ">=0",
963			Section:     SectionPerformance,
964		},
965		{
966			Key:         "engine.webkit.skia_cpu_painting_threads",
967			Type:        "int",
968			Default:     fmt.Sprintf("%d", defaults.Engine.WebKit.SkiaCPUPaintingThreads),
969			Description: "Skia CPU rendering threads (0=unset, custom profile only)",
970			Range:       ">=0",
971			Section:     SectionPerformance,
972		},
973		{
974			Key:         "engine.webkit.skia_gpu_painting_threads",
975			Type:        "int",
976			Default:     fmt.Sprintf("%d", defaults.Engine.WebKit.SkiaGPUPaintingThreads),
977			Description: "Skia GPU rendering threads (-1=unset, 0=disable, custom profile only)",
978			Range:       ">=-1",
979			Section:     SectionPerformance,
980		},
981		{
982			Key:         "engine.webkit.skia_enable_cpu_rendering",
983			Type:        "bool",
984			Default:     fmt.Sprintf("%t", defaults.Engine.WebKit.SkiaEnableCPURendering),
985			Description: "Force CPU rendering (custom profile only)",
986			Section:     SectionPerformance,
987		},
988		{
989			Key:         "engine.webkit.web_process_memory_limit_mb",
990			Type:        "int",
991			Default:     fmt.Sprintf("%d", defaults.Engine.WebKit.WebProcessMemoryLimitMB),
992			Description: "Web process memory limit in MB (0=unset, custom profile only)",
993			Range:       ">=0",
994			Section:     SectionPerformance,
995		},
996		{
997			Key:         "engine.webkit.web_process_memory_poll_interval_sec",
998			Type:        "float64",
999			Default:     fmt.Sprintf("%.1f", defaults.Engine.WebKit.WebProcessMemoryPollIntervalSec),
1000			Description: "Memory check interval in seconds (0=default 30s, custom profile only)",
1001			Range:       ">=0",
1002			Section:     SectionPerformance,
1003		},
1004		{
1005			Key:         "engine.webkit.web_process_memory_conservative_threshold",
1006			Type:        "float64",
1007			Default:     fmt.Sprintf("%.2f", defaults.Engine.WebKit.WebProcessMemoryConservativeThreshold),
1008			Description: "Conservative memory cleanup threshold (0-1, custom profile only)",
1009			Range:       "0-1",
1010			Section:     SectionPerformance,
1011		},
1012		{
1013			Key:         "engine.webkit.web_process_memory_strict_threshold",
1014			Type:        "float64",
1015			Default:     fmt.Sprintf("%.2f", defaults.Engine.WebKit.WebProcessMemoryStrictThreshold),
1016			Description: "Strict memory cleanup threshold (0-1, custom profile only)",
1017			Range:       "0-1",
1018			Section:     SectionPerformance,
1019		},
1020		{
1021			Key:         "engine.webkit.network_process_memory_limit_mb",
1022			Type:        "int",
1023			Default:     fmt.Sprintf("%d", defaults.Engine.WebKit.NetworkProcessMemoryLimitMB),
1024			Description: "Network process memory limit in MB (0=unset, custom profile only)",
1025			Range:       ">=0",
1026			Section:     SectionPerformance,
1027		},
1028		{
1029			Key:         "engine.webkit.network_process_memory_poll_interval_sec",
1030			Type:        "float64",
1031			Default:     fmt.Sprintf("%.1f", defaults.Engine.WebKit.NetworkProcessMemoryPollIntervalSec),
1032			Description: "Network memory check interval in seconds (custom profile only)",
1033			Range:       ">=0",
1034			Section:     SectionPerformance,
1035		},
1036		{
1037			Key:         "engine.webkit.network_process_memory_conservative_threshold",
1038			Type:        "float64",
1039			Default:     fmt.Sprintf("%.2f", defaults.Engine.WebKit.NetworkProcessMemoryConservativeThreshold),
1040			Description: "Network conservative memory threshold (0-1, custom profile only)",
1041			Range:       "0-1",
1042			Section:     SectionPerformance,
1043		},
1044		{
1045			Key:         "engine.webkit.network_process_memory_strict_threshold",
1046			Type:        "float64",
1047			Default:     fmt.Sprintf("%.2f", defaults.Engine.WebKit.NetworkProcessMemoryStrictThreshold),
1048			Description: "Network strict memory threshold (0-1, custom profile only)",
1049			Range:       "0-1",
1050			Section:     SectionPerformance,
1051		},
1052	}
1053}
1054
1055func (*SchemaProvider) getRuntimeKeys(defaults *Config) []entity.ConfigKeyInfo {
1056	return []entity.ConfigKeyInfo{
1057		{
1058			Key:     "engine.cef.cef_dir",
1059			Type:    "string",
1060			Default: defaults.Engine.CEF.CEFDir,
1061			Description: "Optional CEF runtime directory containing libcef.so and Resources; overrides the default " +
1062				"runtime discovery order (CEF_DIR env, /usr/lib/cef, ~/.local/share/cef)",
1063			Section: SectionRuntime,
1064		},
1065		{
1066			Key:         "engine.webkit.prefix",
1067			Type:        "string",
1068			Default:     defaults.Engine.WebKit.Prefix,
1069			Description: "Custom runtime prefix for WebKitGTK/GTK",
1070			Section:     SectionRuntime,
1071		},
1072	}
1073}
1074
1075func (*SchemaProvider) getDatabaseKeys() []entity.ConfigKeyInfo {
1076	// Get actual default path for display
1077	dbPath := "$XDG_DATA_HOME/" + appName + "/" + databaseName
1078	if path, err := GetDatabaseFile(); err == nil {
1079		dbPath = path
1080	}
1081
1082	return []entity.ConfigKeyInfo{
1083		{
1084			Key:         "database.path",
1085			Type:        "string",
1086			Default:     dbPath,
1087			Description: "Path to SQLite database file",
1088			Section:     SectionDatabase,
1089		},
1090	}
1091}
1092
1093func (*SchemaProvider) getDownloadsKeys(_ *Config) []entity.ConfigKeyInfo {
1094	return []entity.ConfigKeyInfo{
1095		{
1096			Key:         "downloads.path",
1097			Type:        "string",
1098			Default:     "(empty = $XDG_DOWNLOAD_DIR or ~/Downloads)",
1099			Description: "Directory where downloads are saved",
1100			Section:     SectionDownloads,
1101		},
1102	}
1103}

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