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 ( | |||
| 26 | 26 | defaultTabBarPosition = "bottom" | |
| 27 | 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 | 36 | // Workspace styling defaults | |
| 30 | 37 | // Active pane border (overlay) | |
| 31 | 38 | defaultBorderWidth = 1 | |
| @@ -39,6 +46,10 @@ const ( | |||
| 39 | 46 | defaultTabModeBorderWidth = 4 | |
| 40 | 47 | 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 | 53 | // Other styling | |
| 43 | 54 | defaultTransitionDuration = 120 | |
| 44 | 55 | defaultUIScale = 1.0 // UI scale multiplier (1.0 = 100%, 1.2 = 120%) | |
| @@ -180,13 +191,15 @@ func DefaultConfig() *Config { | |||
| 180 | 191 | OAuthAutoClose: true, // Auto-close OAuth popups on success | |
| 181 | 192 | }, | |
| 182 | 193 | 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, | |
| 190 | 203 | }, | |
| 191 | 204 | }, | |
| 192 | 205 | ContentFiltering: ContentFilteringConfig{ | |
| @@ -196,6 +209,21 @@ func DefaultConfig() *Config { | |||
| 196 | 209 | Omnibox: OmniboxConfig{ | |
| 197 | 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 | 227 | Media: MediaConfig{ | |
| 200 | 228 | HardwareDecodingMode: HardwareDecodingAuto, // auto allows sw fallback | |
| 201 | 229 | 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 defaults | 36 | // Workspace styling defaults | ||
| 30 | // Active pane border (overlay) | 37 | // Active pane border (overlay) | ||
| 31 | defaultBorderWidth = 1 | 38 | defaultBorderWidth = 1 | ||
| @@ -39,6 +46,10 @@ const ( | |||||
| 39 | defaultTabModeBorderWidth = 4 | 46 | defaultTabModeBorderWidth = 4 | ||
| 40 | defaultTabModeBorderColor = "#FFA500" // Orange for tab mode indicator | 47 | 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 styling | 53 | // Other styling | ||
| 43 | defaultTransitionDuration = 120 | 54 | 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 success | 191 | 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 fallback | 228 | HardwareDecodingMode: HardwareDecodingAuto, // auto allows sw fallback | ||
| 201 | PreferAV1: false, // Don't force codec preference, let site choose | 229 | 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 { | |||
| 17 | 17 | DefaultUIScale float64 `mapstructure:"default_ui_scale" yaml:"default_ui_scale" toml:"default_ui_scale"` | |
| 18 | 18 | // Workspace defines workspace, pane, and tab handling behavior. | |
| 19 | 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 | 22 | // ContentFiltering controls ad blocking and content filtering | |
| 21 | 23 | ContentFiltering ContentFilteringConfig `mapstructure:"content_filtering" yaml:"content_filtering" toml:"content_filtering"` | |
| 22 | 24 | // Omnibox controls the omnibox behavior (initial history display) | |
| @@ -401,6 +403,53 @@ type WorkspaceStylingConfig struct { | |||
| 401 | 403 | // Defaults to "#FFA500" (orange) if not set - MUST be different from PaneModeBorderColor | |
| 402 | 404 | 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 | 412 | // TransitionDuration in milliseconds for border animations | |
| 405 | 413 | 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 | + | } | |
| @@ -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 filtering | 22 | // 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 PaneModeBorderColor | 403 | // 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 line | 404 | 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 animations | 412 | // 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 line | 413 | 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 | + | } | |||