detached 2c68850

feat(config): add session persistence and mode settings

commit
2c68850e297318b6d0ba3e951a472839927c572a
author
bnema · · 2025-12-24 21:27 UTC
committer
brice · · 2025-12-25 05:18 UTC
parent
0ded357
signature
unsigned
2 files · +84 -7
M internal/infrastructure/config/defaults.go +35 -7
@@ -26,6 +26,13 @@ const (
2626 defaultTabBarPosition = "bottom"
2727 defaultPopupPlacement = "right"
2828
29+ // Session defaults
30+ defaultSessionActivationShortcut = "ctrl+o"
31+ defaultSessionTimeoutMilliseconds = 3000
32+ defaultSnapshotIntervalMs = 5000
33+ defaultMaxExitedSessions = 10
34+ defaultMaxListedSessions = 50
35+
2936 // Workspace styling defaults
3037 // Active pane border (overlay)
3138 defaultBorderWidth = 1
@@ -39,6 +46,10 @@ const (
3946 defaultTabModeBorderWidth = 4
4047 defaultTabModeBorderColor = "#FFA500" // Orange for tab mode indicator
4148
49+ // Session mode border (Ctrl+O - overlay)
50+ defaultSessionModeBorderWidth = 4
51+ defaultSessionModeBorderColor = "#9B59B6" // Purple for session mode indicator
52+
4253 // Other styling
4354 defaultTransitionDuration = 120
4455 defaultUIScale = 1.0 // UI scale multiplier (1.0 = 100%, 1.2 = 120%)
@@ -180,13 +191,15 @@ func DefaultConfig() *Config {
180191 OAuthAutoClose: true, // Auto-close OAuth popups on success
181192 },
182193 Styling: WorkspaceStylingConfig{
183- BorderWidth: defaultBorderWidth,
184- BorderColor: defaultBorderColor,
185- PaneModeBorderWidth: defaultPaneModeBorderWidth,
186- PaneModeBorderColor: defaultPaneModeBorderColor,
187- TabModeBorderWidth: defaultTabModeBorderWidth,
188- TabModeBorderColor: defaultTabModeBorderColor,
189- TransitionDuration: defaultTransitionDuration,
194+ BorderWidth: defaultBorderWidth,
195+ BorderColor: defaultBorderColor,
196+ PaneModeBorderWidth: defaultPaneModeBorderWidth,
197+ PaneModeBorderColor: defaultPaneModeBorderColor,
198+ TabModeBorderWidth: defaultTabModeBorderWidth,
199+ TabModeBorderColor: defaultTabModeBorderColor,
200+ SessionModeBorderWidth: defaultSessionModeBorderWidth,
201+ SessionModeBorderColor: defaultSessionModeBorderColor,
202+ TransitionDuration: defaultTransitionDuration,
190203 },
191204 },
192205 ContentFiltering: ContentFilteringConfig{
@@ -196,6 +209,21 @@ func DefaultConfig() *Config {
196209 Omnibox: OmniboxConfig{
197210 InitialBehavior: defaultOmniboxInitialBehavior,
198211 },
212+ Session: SessionConfig{
213+ AutoRestore: false,
214+ SnapshotIntervalMs: defaultSnapshotIntervalMs,
215+ MaxExitedSessions: defaultMaxExitedSessions,
216+ MaxListedSessions: defaultMaxListedSessions,
217+ SessionMode: SessionModeConfig{
218+ ActivationShortcut: defaultSessionActivationShortcut,
219+ TimeoutMilliseconds: defaultSessionTimeoutMilliseconds,
220+ Actions: map[string][]string{
221+ "session-manager": {"w"},
222+ "confirm": {"enter"},
223+ "cancel": {"escape"},
224+ },
225+ },
226+ },
199227 Media: MediaConfig{
200228 HardwareDecodingMode: HardwareDecodingAuto, // auto allows sw fallback
201229 PreferAV1: false, // Don't force codec preference, let site choose
@@ -26,6 +26,13 @@ const (
26 defaultTabBarPosition = "bottom"26 defaultTabBarPosition = "bottom"
27 defaultPopupPlacement = "right"27 defaultPopupPlacement = "right"
28 28
29+ // Session defaults
30+ defaultSessionActivationShortcut = "ctrl+o"
31+ defaultSessionTimeoutMilliseconds = 3000
32+ defaultSnapshotIntervalMs = 5000
33+ defaultMaxExitedSessions = 10
34+ defaultMaxListedSessions = 50
35+
29 // Workspace styling defaults36 // Workspace styling defaults
30 // Active pane border (overlay)37 // Active pane border (overlay)
31 defaultBorderWidth = 138 defaultBorderWidth = 1
@@ -39,6 +46,10 @@ const (
39 defaultTabModeBorderWidth = 446 defaultTabModeBorderWidth = 4
40 defaultTabModeBorderColor = "#FFA500" // Orange for tab mode indicator47 defaultTabModeBorderColor = "#FFA500" // Orange for tab mode indicator
41 48
49+ // Session mode border (Ctrl+O - overlay)
50+ defaultSessionModeBorderWidth = 4
51+ defaultSessionModeBorderColor = "#9B59B6" // Purple for session mode indicator
52+
42 // Other styling53 // Other styling
43 defaultTransitionDuration = 12054 defaultTransitionDuration = 120
44 defaultUIScale = 1.0 // UI scale multiplier (1.0 = 100%, 1.2 = 120%)55 defaultUIScale = 1.0 // UI scale multiplier (1.0 = 100%, 1.2 = 120%)
@@ -180,13 +191,15 @@ func DefaultConfig() *Config {
180 OAuthAutoClose: true, // Auto-close OAuth popups on success191 OAuthAutoClose: true, // Auto-close OAuth popups on success
181 },192 },
182 Styling: WorkspaceStylingConfig{193 Styling: WorkspaceStylingConfig{
183- BorderWidth: defaultBorderWidth,194+ BorderWidth: defaultBorderWidth,
184- BorderColor: defaultBorderColor,195+ BorderColor: defaultBorderColor,
185- PaneModeBorderWidth: defaultPaneModeBorderWidth,196+ PaneModeBorderWidth: defaultPaneModeBorderWidth,
186- PaneModeBorderColor: defaultPaneModeBorderColor,197+ PaneModeBorderColor: defaultPaneModeBorderColor,
187- TabModeBorderWidth: defaultTabModeBorderWidth,198+ TabModeBorderWidth: defaultTabModeBorderWidth,
188- TabModeBorderColor: defaultTabModeBorderColor,199+ TabModeBorderColor: defaultTabModeBorderColor,
189- TransitionDuration: defaultTransitionDuration,200+ SessionModeBorderWidth: defaultSessionModeBorderWidth,
201+ SessionModeBorderColor: defaultSessionModeBorderColor,
202+ TransitionDuration: defaultTransitionDuration,
190 },203 },
191 },204 },
192 ContentFiltering: ContentFilteringConfig{205 ContentFiltering: ContentFilteringConfig{
@@ -196,6 +209,21 @@ func DefaultConfig() *Config {
196 Omnibox: OmniboxConfig{209 Omnibox: OmniboxConfig{
197 InitialBehavior: defaultOmniboxInitialBehavior,210 InitialBehavior: defaultOmniboxInitialBehavior,
198 },211 },
212+ Session: SessionConfig{
213+ AutoRestore: false,
214+ SnapshotIntervalMs: defaultSnapshotIntervalMs,
215+ MaxExitedSessions: defaultMaxExitedSessions,
216+ MaxListedSessions: defaultMaxListedSessions,
217+ SessionMode: SessionModeConfig{
218+ ActivationShortcut: defaultSessionActivationShortcut,
219+ TimeoutMilliseconds: defaultSessionTimeoutMilliseconds,
220+ Actions: map[string][]string{
221+ "session-manager": {"w"},
222+ "confirm": {"enter"},
223+ "cancel": {"escape"},
224+ },
225+ },
226+ },
199 Media: MediaConfig{227 Media: MediaConfig{
200 HardwareDecodingMode: HardwareDecodingAuto, // auto allows sw fallback228 HardwareDecodingMode: HardwareDecodingAuto, // auto allows sw fallback
201 PreferAV1: false, // Don't force codec preference, let site choose229 PreferAV1: false, // Don't force codec preference, let site choose
M internal/infrastructure/config/schema.go +49 -0
@@ -17,6 +17,8 @@ type Config struct {
1717 DefaultUIScale float64 `mapstructure:"default_ui_scale" yaml:"default_ui_scale" toml:"default_ui_scale"`
1818 // Workspace defines workspace, pane, and tab handling behavior.
1919 Workspace WorkspaceConfig `mapstructure:"workspace" yaml:"workspace" toml:"workspace"`
20+ // Session controls session persistence and restoration.
21+ Session SessionConfig `mapstructure:"session" yaml:"session" toml:"session"`
2022 // ContentFiltering controls ad blocking and content filtering
2123 ContentFiltering ContentFilteringConfig `mapstructure:"content_filtering" yaml:"content_filtering" toml:"content_filtering"`
2224 // Omnibox controls the omnibox behavior (initial history display)
@@ -401,6 +403,53 @@ type WorkspaceStylingConfig struct {
401403 // Defaults to "#FFA500" (orange) if not set - MUST be different from PaneModeBorderColor
402404 TabModeBorderColor string `mapstructure:"tab_mode_border_color" yaml:"tab_mode_border_color" toml:"tab_mode_border_color" json:"tab_mode_border_color"` //nolint:lll // struct tags must stay on one line
403405
406+ // SessionModeBorderWidth in pixels for session mode indicator border (Ctrl+O overlay)
407+ SessionModeBorderWidth int `mapstructure:"session_mode_border_width" yaml:"session_mode_border_width" toml:"session_mode_border_width" json:"session_mode_border_width"` //nolint:lll // struct tags must stay on one line
408+ // SessionModeBorderColor for the session mode indicator border (CSS color value or theme variable)
409+ // Defaults to "#9B59B6" (purple) if not set - MUST be different from other mode colors
410+ SessionModeBorderColor string `mapstructure:"session_mode_border_color" yaml:"session_mode_border_color" toml:"session_mode_border_color" json:"session_mode_border_color"` //nolint:lll // struct tags must stay on one line
411+
404412 // TransitionDuration in milliseconds for border animations
405413 TransitionDuration int `mapstructure:"transition_duration" yaml:"transition_duration" toml:"transition_duration" json:"transition_duration"` //nolint:lll // struct tags must stay on one line
406414 }
415+
416+// SessionConfig holds session persistence settings.
417+type SessionConfig struct {
418+ // AutoRestore automatically restores the last session on startup.
419+ // Default: false
420+ AutoRestore bool `mapstructure:"auto_restore" yaml:"auto_restore" toml:"auto_restore" json:"auto_restore"`
421+
422+ // SnapshotIntervalMs is the minimum interval between snapshots in milliseconds.
423+ // Default: 5000
424+ SnapshotIntervalMs int `mapstructure:"snapshot_interval_ms" yaml:"snapshot_interval_ms" toml:"snapshot_interval_ms" json:"snapshot_interval_ms"` //nolint:lll // struct tags must stay on one line
425+
426+ // MaxExitedSessions is the maximum number of exited sessions to keep.
427+ // Default: 10
428+ MaxExitedSessions int `mapstructure:"max_exited_sessions" yaml:"max_exited_sessions" toml:"max_exited_sessions" json:"max_exited_sessions"` //nolint:lll // struct tags must stay on one line
429+
430+ // MaxListedSessions is the maximum number of sessions to list in CLI/TUI.
431+ // Default: 50
432+ MaxListedSessions int `mapstructure:"max_listed_sessions" yaml:"max_listed_sessions" toml:"max_listed_sessions" json:"max_listed_sessions"` //nolint:lll // struct tags must stay on one line
433+
434+ // SessionMode defines modal behavior for session management (Ctrl+O).
435+ SessionMode SessionModeConfig `mapstructure:"session_mode" yaml:"session_mode" toml:"session_mode" json:"session_mode"`
436+}
437+
438+// SessionModeConfig defines modal behavior for session management.
439+type SessionModeConfig struct {
440+ ActivationShortcut string `mapstructure:"activation_shortcut" yaml:"activation_shortcut" toml:"activation_shortcut" json:"activation_shortcut"` //nolint:lll // struct tags must stay on one line
441+ TimeoutMilliseconds int `mapstructure:"timeout_ms" yaml:"timeout_ms" toml:"timeout_ms" json:"timeout_ms"`
442+ Actions map[string][]string `mapstructure:"actions" yaml:"actions" toml:"actions" json:"actions"`
443+}
444+
445+// GetKeyBindings returns an inverted map for O(1) key→action lookup.
446+// This is built from the action→keys structure in the config.
447+func (s *SessionModeConfig) GetKeyBindings() map[string]string {
448+ keyToAction := make(map[string]string)
449+ for action, keys := range s.Actions {
450+ for _, key := range keys {
451+ keyToAction[key] = action
452+ }
453+ }
454+ return keyToAction
455+}
@@ -17,6 +17,8 @@ type Config struct {
17 DefaultUIScale float64 `mapstructure:"default_ui_scale" yaml:"default_ui_scale" toml:"default_ui_scale"`17 DefaultUIScale float64 `mapstructure:"default_ui_scale" yaml:"default_ui_scale" toml:"default_ui_scale"`
18 // Workspace defines workspace, pane, and tab handling behavior.18 // Workspace defines workspace, pane, and tab handling behavior.
19 Workspace WorkspaceConfig `mapstructure:"workspace" yaml:"workspace" toml:"workspace"`19 Workspace WorkspaceConfig `mapstructure:"workspace" yaml:"workspace" toml:"workspace"`
20+ // Session controls session persistence and restoration.
21+ Session SessionConfig `mapstructure:"session" yaml:"session" toml:"session"`
20 // ContentFiltering controls ad blocking and content filtering22 // ContentFiltering controls ad blocking and content filtering
21 ContentFiltering ContentFilteringConfig `mapstructure:"content_filtering" yaml:"content_filtering" toml:"content_filtering"`23 ContentFiltering ContentFilteringConfig `mapstructure:"content_filtering" yaml:"content_filtering" toml:"content_filtering"`
22 // Omnibox controls the omnibox behavior (initial history display)24 // Omnibox controls the omnibox behavior (initial history display)
@@ -401,6 +403,53 @@ type WorkspaceStylingConfig struct {
401 // Defaults to "#FFA500" (orange) if not set - MUST be different from PaneModeBorderColor403 // Defaults to "#FFA500" (orange) if not set - MUST be different from PaneModeBorderColor
402 TabModeBorderColor string `mapstructure:"tab_mode_border_color" yaml:"tab_mode_border_color" toml:"tab_mode_border_color" json:"tab_mode_border_color"` //nolint:lll // struct tags must stay on one line404 TabModeBorderColor string `mapstructure:"tab_mode_border_color" yaml:"tab_mode_border_color" toml:"tab_mode_border_color" json:"tab_mode_border_color"` //nolint:lll // struct tags must stay on one line
403 405
406+ // SessionModeBorderWidth in pixels for session mode indicator border (Ctrl+O overlay)
407+ SessionModeBorderWidth int `mapstructure:"session_mode_border_width" yaml:"session_mode_border_width" toml:"session_mode_border_width" json:"session_mode_border_width"` //nolint:lll // struct tags must stay on one line
408+ // SessionModeBorderColor for the session mode indicator border (CSS color value or theme variable)
409+ // Defaults to "#9B59B6" (purple) if not set - MUST be different from other mode colors
410+ SessionModeBorderColor string `mapstructure:"session_mode_border_color" yaml:"session_mode_border_color" toml:"session_mode_border_color" json:"session_mode_border_color"` //nolint:lll // struct tags must stay on one line
411+
404 // TransitionDuration in milliseconds for border animations412 // TransitionDuration in milliseconds for border animations
405 TransitionDuration int `mapstructure:"transition_duration" yaml:"transition_duration" toml:"transition_duration" json:"transition_duration"` //nolint:lll // struct tags must stay on one line413 TransitionDuration int `mapstructure:"transition_duration" yaml:"transition_duration" toml:"transition_duration" json:"transition_duration"` //nolint:lll // struct tags must stay on one line
406 }414 }
415+
416+// SessionConfig holds session persistence settings.
417+type SessionConfig struct {
418+ // AutoRestore automatically restores the last session on startup.
419+ // Default: false
420+ AutoRestore bool `mapstructure:"auto_restore" yaml:"auto_restore" toml:"auto_restore" json:"auto_restore"`
421+
422+ // SnapshotIntervalMs is the minimum interval between snapshots in milliseconds.
423+ // Default: 5000
424+ SnapshotIntervalMs int `mapstructure:"snapshot_interval_ms" yaml:"snapshot_interval_ms" toml:"snapshot_interval_ms" json:"snapshot_interval_ms"` //nolint:lll // struct tags must stay on one line
425+
426+ // MaxExitedSessions is the maximum number of exited sessions to keep.
427+ // Default: 10
428+ MaxExitedSessions int `mapstructure:"max_exited_sessions" yaml:"max_exited_sessions" toml:"max_exited_sessions" json:"max_exited_sessions"` //nolint:lll // struct tags must stay on one line
429+
430+ // MaxListedSessions is the maximum number of sessions to list in CLI/TUI.
431+ // Default: 50
432+ MaxListedSessions int `mapstructure:"max_listed_sessions" yaml:"max_listed_sessions" toml:"max_listed_sessions" json:"max_listed_sessions"` //nolint:lll // struct tags must stay on one line
433+
434+ // SessionMode defines modal behavior for session management (Ctrl+O).
435+ SessionMode SessionModeConfig `mapstructure:"session_mode" yaml:"session_mode" toml:"session_mode" json:"session_mode"`
436+}
437+
438+// SessionModeConfig defines modal behavior for session management.
439+type SessionModeConfig struct {
440+ ActivationShortcut string `mapstructure:"activation_shortcut" yaml:"activation_shortcut" toml:"activation_shortcut" json:"activation_shortcut"` //nolint:lll // struct tags must stay on one line
441+ TimeoutMilliseconds int `mapstructure:"timeout_ms" yaml:"timeout_ms" toml:"timeout_ms" json:"timeout_ms"`
442+ Actions map[string][]string `mapstructure:"actions" yaml:"actions" toml:"actions" json:"actions"`
443+}
444+
445+// GetKeyBindings returns an inverted map for O(1) key→action lookup.
446+// This is built from the action→keys structure in the config.
447+func (s *SessionModeConfig) GetKeyBindings() map[string]string {
448+ keyToAction := make(map[string]string)
449+ for action, keys := range s.Actions {
450+ for _, key := range keys {
451+ keyToAction[key] = action
452+ }
453+ }
454+ return keyToAction
455+}

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