Skip to main content

par_term_config/config/config_struct/
mod.rs

1//! Core `Config` struct definition.
2//!
3//! This module contains the main configuration struct with all terminal,
4//! display, input, and feature settings.
5//!
6//! # Splitting Strategy
7//!
8//! Thematic field groups live in sibling `*_config.rs` sub-structs held here as
9//! `#[serde(flatten)]` members. Flattening is what makes the split a pure
10//! refactor: every field still serialises at the top level of `config.yaml`,
11//! indistinguishable from a direct struct field, so a config written by any
12//! release keeps loading unchanged.
13//!
14//! **New settings belong in the sub-struct for their area, not on `Config`.**
15//! The root keeps only the handful of fields below, and adding to it is how
16//! this file grew to 1,529 lines in the first place.
17//!
18//! Two rules the sub-structs must follow, both enforced by
19//! `tests/config_yaml_compat.rs`:
20//!
21//! - The member carries `#[serde(flatten)]`. Without it the group's keys nest
22//!   under the member name and vanish from every existing config file.
23//! - `Default` is hand-written from the same expressions as the field's
24//!   `#[serde(default = "…")]`, unless every field genuinely defaults to its
25//!   type's `Default`. Most do not — deriving resets non-zero durations,
26//!   opacities and `true` flags while compiling cleanly.
27//!
28//! # Still on the root
29//!
30//! Terminal size (`cols`/`rows`), the font family/metrics block, the window
31//! title, and a scattering of one-field settings (`screenshot_format`,
32//! `log_level`, `max_osc_data_length`, `command_history_max_entries`,
33//! `collapsed_settings_sections`, `dynamic_profile_sources`, the file-transfer
34//! pair, keybindings and snippets) plus the two `#[serde(skip)]` runtime
35//! security lists, which are computed state rather than configuration.
36
37mod agent_usage_config;
38mod ai_inspector_config;
39mod automation_config;
40mod background_config;
41mod badge_config;
42mod clipboard_config;
43mod command_separator_config;
44mod copy_mode_config;
45mod cursor_config;
46mod default_impl;
47mod font_config;
48mod global_shader_config;
49mod image_config;
50mod input_config;
51mod integration_config;
52mod mouse_config;
53mod notification_config;
54mod pane_config;
55mod power_config;
56mod progress_bar_config;
57mod rendering_config;
58mod scrollback_config;
59mod scrollbar_config;
60mod search_config;
61mod security_config;
62mod selection_config;
63mod semantic_history_config;
64mod session_log_config;
65mod session_restore_config;
66mod shader_overrides_config;
67mod shader_watch_config;
68mod shell_config;
69mod ssh_config;
70mod status_bar_config;
71mod tab_bar_colors_config;
72mod tab_config;
73mod theme_colors_config;
74mod tmux_config;
75mod unicode_config;
76mod update;
77mod window_config;
78mod window_placement_config;
79mod word_selection_config;
80
81pub use agent_usage_config::AgentUsageConfig;
82pub use ai_inspector_config::{AiInspectorConfig, AssistantInputHistoryMode};
83pub use automation_config::{AutomationConfig, PluginStateConfig};
84pub use background_config::BackgroundConfig;
85pub use badge_config::BadgeConfig;
86pub use clipboard_config::ClipboardConfig;
87pub use command_separator_config::CommandSeparatorConfig;
88pub use copy_mode_config::CopyModeConfig;
89pub use cursor_config::CursorConfig;
90pub use font_config::FontRenderingConfig;
91pub use global_shader_config::GlobalShaderConfig;
92pub use image_config::ImageConfig;
93pub use input_config::InputConfig;
94pub use integration_config::IntegrationConfig;
95pub use mouse_config::MouseConfig;
96pub use notification_config::NotificationConfig;
97pub use pane_config::PaneConfig;
98pub use power_config::PowerConfig;
99pub use progress_bar_config::ProgressBarConfig;
100pub use rendering_config::RenderingConfig;
101pub use scrollback_config::ScrollbackConfig;
102pub use scrollbar_config::ScrollbarConfig;
103pub use search_config::SearchConfig;
104pub use security_config::SecurityConfig;
105pub use selection_config::SelectionConfig;
106pub use semantic_history_config::SemanticHistoryConfig;
107pub use session_log_config::SessionLogConfig;
108pub use session_restore_config::SessionRestoreConfig;
109pub use shader_overrides_config::ShaderOverridesConfig;
110pub use shader_watch_config::ShaderWatchConfig;
111pub use shell_config::ShellConfig;
112pub use ssh_config::SshConfig;
113pub use status_bar_config::StatusBarConfig;
114pub use tab_bar_colors_config::TabBarColorsConfig;
115pub use tab_config::TabConfig;
116pub use theme_colors_config::ThemeColorsConfig;
117pub use tmux_config::TmuxConfig;
118pub use unicode_config::UnicodeConfig;
119pub use update::UpdateConfig;
120pub use window_config::WindowConfig;
121pub use window_placement_config::WindowPlacementConfig;
122pub use word_selection_config::WordSelectionConfig;
123
124use crate::snippets::{CustomActionConfig, SnippetConfig};
125use crate::types::{DownloadSaveLocation, FontRange, KeyBinding, LogLevel, ShellExitAction};
126use serde::{Deserialize, Serialize};
127
128/// Custom deserializer for `ShellExitAction` that supports backward compatibility.
129///
130/// Accepts either:
131/// - Boolean: `true` → `Close`, `false` → `Keep` (legacy format)
132/// - String enum: `"close"`, `"keep"`, `"restart_immediately"`, etc.
133pub(crate) fn deserialize_shell_exit_action<'de, D>(
134    deserializer: D,
135) -> Result<ShellExitAction, D::Error>
136where
137    D: serde::Deserializer<'de>,
138{
139    #[derive(Deserialize)]
140    #[serde(untagged)]
141    enum BoolOrAction {
142        Bool(bool),
143        Action(ShellExitAction),
144    }
145
146    match BoolOrAction::deserialize(deserializer)? {
147        BoolOrAction::Bool(true) => Ok(ShellExitAction::Close),
148        BoolOrAction::Bool(false) => Ok(ShellExitAction::Keep),
149        BoolOrAction::Action(action) => Ok(action),
150    }
151}
152
153/// Configuration for the terminal emulator
154/// Aligned with par-tui-term naming conventions for consistency
155#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct Config {
157    // ========================================================================
158    // Window & Display (GUI-specific)
159    // ========================================================================
160
161    // --- Terminal Size ---
162    /// Number of columns in the terminal
163    #[serde(default = "crate::defaults::cols")]
164    pub cols: usize,
165
166    /// Number of rows in the terminal
167    #[serde(default = "crate::defaults::rows")]
168    pub rows: usize,
169
170    // --- Font Settings ---
171    /// Font size in points
172    #[serde(default = "crate::defaults::font_size")]
173    pub font_size: f32,
174
175    /// Font family name (regular/normal weight)
176    #[serde(default = "crate::defaults::font_family")]
177    pub font_family: String,
178
179    /// Bold font family name (optional, defaults to font_family)
180    #[serde(default)]
181    pub font_family_bold: Option<String>,
182
183    /// Italic font family name (optional, defaults to font_family)
184    #[serde(default)]
185    pub font_family_italic: Option<String>,
186
187    /// Bold italic font family name (optional, defaults to font_family)
188    #[serde(default)]
189    pub font_family_bold_italic: Option<String>,
190
191    /// Custom font mappings for specific Unicode ranges
192    /// Format: Vec of (start_codepoint, end_codepoint, font_family_name)
193    /// Example: [(0x4E00, 0x9FFF, "Noto Sans CJK SC")] for CJK Unified Ideographs
194    #[serde(default)]
195    pub font_ranges: Vec<FontRange>,
196
197    /// Line height multiplier (1.0 = default/tight, 1.2 = comfortable, 1.5 = spacious)
198    #[serde(default = "crate::defaults::line_spacing")]
199    pub line_spacing: f32,
200
201    /// Character width multiplier (1.0 = default, values < 1.0 = narrow, values > 1.0 = wide)
202    #[serde(default = "crate::defaults::char_spacing")]
203    pub char_spacing: f32,
204
205    /// Enable text shaping for ligatures and complex scripts
206    /// When enabled, uses HarfBuzz for proper ligature, emoji, and complex script rendering
207    #[serde(default = "crate::defaults::text_shaping")]
208    pub enable_text_shaping: bool,
209
210    /// Enable ligatures (requires enable_text_shaping)
211    #[serde(default = "crate::defaults::bool_true")]
212    pub enable_ligatures: bool,
213
214    /// Enable kerning adjustments (requires enable_text_shaping)
215    #[serde(default = "crate::defaults::bool_true")]
216    pub enable_kerning: bool,
217
218    // --- Font Rendering Quality (extracted to FontRenderingConfig) ---
219    /// Font rendering quality settings: anti-aliasing, hinting, stroke weight, minimum contrast.
220    ///
221    /// Flattened into the top-level YAML so existing config files remain compatible.
222    /// Access via `config.font_rendering.font_antialias`, etc.
223    ///
224    /// See [`FontRenderingConfig`] for field documentation.
225    #[serde(flatten)]
226    pub font_rendering: FontRenderingConfig,
227
228    /// Window title
229    #[serde(default = "crate::defaults::window_title")]
230    pub window_title: String,
231
232    /// Allow applications to change the window title via OSC escape sequences
233    /// When false, the window title will always be the configured window_title
234    #[serde(default = "crate::defaults::bool_true")]
235    pub allow_title_change: bool,
236
237    // ========================================================================
238    // Frame Pacing & GPU
239    // ========================================================================
240    /// Frame rate target, VSync mode, GPU preference and output batching.
241    ///
242    /// Flattened into the top-level YAML so existing config files remain
243    /// compatible. See [`RenderingConfig`].
244    #[serde(flatten)]
245    pub rendering: RenderingConfig,
246
247    /// Window appearance settings (opacity, padding, decorations, blur, etc.)
248    /// Serialised flat at the top level via `#[serde(flatten)]` so that
249    /// existing YAML config files require no changes.
250    #[serde(flatten)]
251    pub window: WindowConfig,
252
253    // ========================================================================
254    // Window Placement
255    // ========================================================================
256    /// Window type, target monitor/Space, resize lock and window numbering.
257    ///
258    /// Flattened into the top-level YAML so existing config files remain
259    /// compatible. See [`WindowPlacementConfig`].
260    #[serde(flatten)]
261    pub placement: WindowPlacementConfig,
262
263    // ========================================================================
264    // Background Image & Transparency
265    // ========================================================================
266    /// Background image source and how transparency applies to cells and text.
267    ///
268    /// Flattened into the top-level YAML so existing config files remain
269    /// compatible. See [`BackgroundConfig`].
270    #[serde(flatten)]
271    pub background: BackgroundConfig,
272
273    // ========================================================================
274    // Inline Image Settings (Sixel, iTerm2, Kitty)
275    // ========================================================================
276    /// Inline image scaling and the terminal background source.
277    ///
278    /// Flattened into the top-level YAML so existing config files remain
279    /// compatible. See [`ImageConfig`].
280    #[serde(flatten)]
281    pub image: ImageConfig,
282
283    // ========================================================================
284    // File Transfer Settings
285    // ========================================================================
286    /// Default save location for downloaded files
287    #[serde(default)]
288    pub download_save_location: DownloadSaveLocation,
289
290    /// Last used download directory (persisted internally)
291    #[serde(default, skip_serializing_if = "Option::is_none")]
292    pub last_download_directory: Option<String>,
293
294    // ========================================================================
295    // Shader Settings (background + cursor) — extracted to GlobalShaderConfig
296    // ========================================================================
297    /// All `custom_shader_*` and `cursor_shader_*` settings.
298    ///
299    /// Flattened into the top-level YAML so existing config files remain compatible.
300    #[serde(flatten)]
301    pub shader: GlobalShaderConfig,
302
303    // ========================================================================
304    // Keyboard Input
305    // ========================================================================
306    /// Option/Alt key behaviour, modifier remapping and physical key positions.
307    ///
308    /// Flattened into the top-level YAML so existing config files remain
309    /// compatible. See [`InputConfig`].
310    #[serde(flatten)]
311    pub input: InputConfig,
312
313    // ========================================================================
314    // Selection & Clipboard
315    // ========================================================================
316    /// Copy-on-select, paste behaviour and dropped-file quoting.
317    ///
318    /// Flattened into the top-level YAML so existing config files remain
319    /// compatible. See [`SelectionConfig`].
320    #[serde(flatten)]
321    pub selection: SelectionConfig,
322
323    // ========================================================================
324    // Mouse — extracted to MouseConfig
325    // ========================================================================
326    /// Mouse behavior settings (see [`MouseConfig`]).
327    ///
328    /// Flattened into the top-level YAML so existing config files remain compatible.
329    #[serde(flatten)]
330    pub mouse: MouseConfig,
331
332    // ========================================================================
333    // Word Selection
334    // ========================================================================
335    /// Word boundary characters and pattern-based smart selection rules.
336    ///
337    /// Flattened into the top-level YAML so existing config files remain
338    /// compatible. See [`WordSelectionConfig`].
339    #[serde(flatten)]
340    pub word_selection: WordSelectionConfig,
341
342    // ========================================================================
343    // Copy Mode (vi-style keyboard-driven selection)
344    // ========================================================================
345    /// Vi-style copy mode settings (see [`CopyModeConfig`]).
346    #[serde(flatten)]
347    pub copy_mode: CopyModeConfig,
348
349    // ========================================================================
350    // Scrollback & Cursor
351    // ========================================================================
352    /// Scrollback buffer size settings (see [`ScrollbackConfig`]).
353    ///
354    /// Flattened into the top-level YAML so existing config files remain compatible.
355    #[serde(flatten)]
356    pub scrollback: ScrollbackConfig,
357
358    // ========================================================================
359    // Unicode Width Settings
360    // ========================================================================
361    /// Unicode character width and normalization settings (see [`UnicodeConfig`]).
362    #[serde(flatten)]
363    pub unicode: UnicodeConfig,
364
365    // ========================================================================
366    // Cursor — extracted to CursorConfig
367    // ========================================================================
368    /// Cursor appearance and behavior settings (see [`CursorConfig`]).
369    ///
370    /// Flattened into the top-level YAML so existing config files remain compatible.
371    #[serde(flatten)]
372    pub cursor: CursorConfig,
373
374    // ========================================================================
375    // Scrollbar
376    // ========================================================================
377    /// Scrollbar placement, size, colours, command marks and auto-hide.
378    ///
379    /// Flattened into the top-level YAML so existing config files remain
380    /// compatible. See [`ScrollbarConfig`].
381    #[serde(flatten)]
382    pub scrollbar: ScrollbarConfig,
383
384    // ========================================================================
385    // Theme & Colors
386    // ========================================================================
387    /// Terminal colour theme and the light/dark pair used by auto dark mode.
388    ///
389    /// Flattened into the top-level YAML so existing config files remain
390    /// compatible. See [`ThemeColorsConfig`].
391    #[serde(flatten)]
392    pub theme_colors: ThemeColorsConfig,
393
394    // ========================================================================
395    // Screenshot
396    // ========================================================================
397    /// File format for screenshots (png, jpeg, svg, html)
398    #[serde(default = "crate::defaults::screenshot_format")]
399    pub screenshot_format: String,
400
401    // ========================================================================
402    // Shell Behavior
403    // ========================================================================
404    /// Which shell runs, where it starts, what it is sent, and exit handling.
405    ///
406    /// Flattened into the top-level YAML so existing config files remain
407    /// compatible. See [`ShellConfig`].
408    #[serde(flatten)]
409    pub shell: ShellConfig,
410
411    // ========================================================================
412    // Semantic History
413    // ========================================================================
414    /// File path/URL detection, the editor that opens them, and link highlighting.
415    ///
416    /// Flattened into the top-level YAML so existing config files remain
417    /// compatible. See [`SemanticHistoryConfig`].
418    #[serde(flatten)]
419    pub semantic_history: SemanticHistoryConfig,
420
421    // ========================================================================
422    // Scrollbar (GUI-specific)
423    // ========================================================================
424    // ========================================================================
425    // Command Separator Lines
426    // ========================================================================
427    /// Horizontal separator lines drawn between shell commands.
428    ///
429    /// Flattened into the top-level YAML so existing config files remain
430    /// compatible. See [`CommandSeparatorConfig`].
431    #[serde(flatten)]
432    pub command_separator: CommandSeparatorConfig,
433
434    // ========================================================================
435    // Clipboard Sync Limits
436    // ========================================================================
437    /// Clipboard sync event retention and OSC 52 clipboard-set policy.
438    ///
439    /// Flattened into the top-level YAML so existing config files remain
440    /// compatible. See [`ClipboardConfig`].
441    #[serde(flatten)]
442    pub clipboard: ClipboardConfig,
443
444    // ========================================================================
445    // OSC Sequence Limits
446    // ========================================================================
447    /// Maximum total OSC data length in bytes before a sequence is rejected
448    /// by the core terminal (QA-012 memory-exhaustion guard). Must be large
449    /// enough for inline images (iTerm2/Kitty base64) if used.
450    #[serde(default = "crate::defaults::max_osc_data_length")]
451    pub max_osc_data_length: usize,
452
453    // ========================================================================
454    // Command History
455    // ========================================================================
456    /// Maximum number of commands to persist in fuzzy search history
457    #[serde(default = "crate::defaults::command_history_max_entries")]
458    pub command_history_max_entries: usize,
459
460    // ========================================================================
461    // Notifications — extracted to NotificationConfig
462    // ========================================================================
463    /// Bell, activity/silence alerts, anti-idle keep-alive, and OSC 9/777 buffer
464    /// settings (see [`NotificationConfig`]).
465    ///
466    /// Flattened into the top-level YAML so existing config files remain compatible.
467    #[serde(flatten)]
468    pub notifications: NotificationConfig,
469
470    // ========================================================================
471    // SSH Settings
472    // ========================================================================
473    /// SSH discovery and profile-switching settings (see [`SshConfig`]).
474    #[serde(flatten)]
475    pub ssh: SshConfig,
476
477    // ========================================================================
478    // Tab Settings
479    // ========================================================================
480    /// Tab style presets, tab bar placement and new-tab behaviour.
481    ///
482    /// Flattened into the top-level YAML so existing config files remain
483    /// compatible. See [`TabConfig`].
484    #[serde(flatten)]
485    pub tabs: TabConfig,
486
487    // ========================================================================
488    // Tab Bar Colors
489    // ========================================================================
490    /// Tab bar palette, dimming, sizing and border appearance.
491    ///
492    /// Flattened into the top-level YAML so existing config files remain
493    /// compatible. See [`TabBarColorsConfig`].
494    #[serde(flatten)]
495    pub tab_colors: TabBarColorsConfig,
496
497    // ========================================================================
498    // Split Pane Settings
499    // ========================================================================
500    /// Split-pane divider, padding, title bar and focus indicator settings.
501    ///
502    /// Flattened into the top-level YAML so existing config files remain
503    /// compatible. See [`PaneConfig`].
504    #[serde(flatten)]
505    pub panes: PaneConfig,
506
507    // ========================================================================
508    // tmux Integration
509    // ========================================================================
510    /// tmux control-mode integration settings.
511    ///
512    /// Flattened into the top-level YAML so existing config files remain
513    /// compatible. See [`TmuxConfig`].
514    #[serde(flatten)]
515    pub tmux: TmuxConfig,
516
517    // ========================================================================
518    // Focus/Blur Power Saving
519    // ========================================================================
520    /// Shader pausing and reduced frame rates when unfocused or inactive.
521    ///
522    /// Flattened into the top-level YAML so existing config files remain
523    /// compatible. See [`PowerConfig`].
524    #[serde(flatten)]
525    pub power: PowerConfig,
526
527    // ========================================================================
528    // Shader Hot Reload
529    // ========================================================================
530    /// Automatic reloading of shader files when they change on disk.
531    ///
532    /// Flattened into the top-level YAML so existing config files remain
533    /// compatible. See [`ShaderWatchConfig`].
534    #[serde(flatten)]
535    pub shader_watch: ShaderWatchConfig,
536
537    // ========================================================================
538    // Per-Shader Configuration Overrides
539    // ========================================================================
540    /// Per-file overrides for background and cursor shader settings.
541    ///
542    /// Flattened into the top-level YAML so existing config files remain
543    /// compatible. See [`ShaderOverridesConfig`].
544    #[serde(flatten)]
545    pub shader_overrides: ShaderOverridesConfig,
546
547    // ========================================================================
548    // Keybindings
549    // ========================================================================
550    /// Custom keybindings (checked before built-in shortcuts)
551    /// Format: key = "CmdOrCtrl+Shift+B", action = "toggle_background_shader"
552    #[serde(default = "crate::defaults::keybindings")]
553    pub keybindings: Vec<KeyBinding>,
554
555    /// Optional global prefix key for custom actions using per-action prefix chars.
556    /// Uses the same combo format as normal keybindings (for example "Ctrl+B").
557    /// Leave empty to disable prefix-mode custom actions.
558    #[serde(default = "crate::defaults::custom_action_prefix_key")]
559    pub custom_action_prefix_key: String,
560
561    // ========================================================================
562    // Shader & Shell Integration Installation
563    // ========================================================================
564    /// Install prompts and version tracking for bundled integrations.
565    ///
566    /// Flattened into the top-level YAML so existing config files remain
567    /// compatible. See [`IntegrationConfig`].
568    #[serde(flatten)]
569    pub integrations: IntegrationConfig,
570
571    // ========================================================================
572    // Update Checking
573    // ========================================================================
574    /// Configuration for automatic update checking
575    #[serde(flatten)]
576    pub updates: UpdateConfig,
577
578    // ========================================================================
579    // Window Arrangements & Session Restore
580    // ========================================================================
581    /// Startup arrangement restore, session restore and closed-tab undo.
582    ///
583    /// Flattened into the top-level YAML so existing config files remain
584    /// compatible. See [`SessionRestoreConfig`].
585    #[serde(flatten)]
586    pub session_restore: SessionRestoreConfig,
587
588    // ========================================================================
589    // Search Settings
590    // ========================================================================
591    /// Terminal search settings (see [`SearchConfig`]).
592    #[serde(flatten)]
593    pub search: SearchConfig,
594
595    // ========================================================================
596    // Session Logging
597    // ========================================================================
598    /// Automatic session recording: format, destination and redaction.
599    ///
600    /// Flattened into the top-level YAML so existing config files remain
601    /// compatible. See [`SessionLogConfig`].
602    #[serde(flatten)]
603    pub session_log: SessionLogConfig,
604
605    // ========================================================================
606    // Debug Logging
607    // ========================================================================
608    /// Log level for debug log file output.
609    /// Controls verbosity of `par_term_debug.log` in the system temp directory
610    /// (`$TMPDIR` on macOS, `/tmp` on Linux, `%TEMP%` on Windows).
611    /// Environment variable RUST_LOG and --log-level CLI flag take precedence.
612    #[serde(default)]
613    pub log_level: LogLevel,
614
615    // ========================================================================
616    // Badge Settings (iTerm2-style session labels)
617    // ========================================================================
618    /// Badge overlay text, colour, font and placement.
619    ///
620    /// Flattened into the top-level YAML so existing config files remain
621    /// compatible. See [`BadgeConfig`].
622    #[serde(flatten)]
623    pub badge: BadgeConfig,
624
625    // ========================================================================
626    // Status Bar Settings
627    // ========================================================================
628    /// Status bar settings (see [`StatusBarConfig`]).
629    ///
630    /// All `status_bar_*` fields are flattened here for YAML backward-compatibility.
631    #[serde(flatten)]
632    pub status_bar: StatusBarConfig,
633
634    // ========================================================================
635    // Agent Usage Settings
636    // ========================================================================
637    /// Agent usage panel settings (see [`AgentUsageConfig`]).
638    ///
639    /// All `agent_usage_*` fields are flattened here for YAML backward-compatibility.
640    #[serde(flatten)]
641    pub agent_usage: AgentUsageConfig,
642
643    // ========================================================================
644    // Progress Bar Settings (OSC 9;4 and OSC 934)
645    // ========================================================================
646    /// Progress bar overlay style, placement and state colours.
647    ///
648    /// Flattened into the top-level YAML so existing config files remain
649    /// compatible. See [`ProgressBarConfig`].
650    #[serde(flatten)]
651    pub progress_bar: ProgressBarConfig,
652
653    // ========================================================================
654    // Triggers & Automation
655    // ========================================================================
656    /// Regex triggers, coprocess definitions and external observer scripts.
657    ///
658    /// Flattened into the top-level YAML so existing config files remain
659    /// compatible. See [`AutomationConfig`].
660    #[serde(flatten)]
661    pub automation: AutomationConfig,
662
663    // ========================================================================
664    // Snippets & Actions
665    // ========================================================================
666    /// Text snippets for quick insertion
667    #[serde(default)]
668    pub snippets: Vec<SnippetConfig>,
669
670    /// Custom actions (shell commands, text insertion, key sequences)
671    #[serde(default)]
672    pub actions: Vec<CustomActionConfig>,
673
674    // ========================================================================
675    // UI State (persisted across sessions)
676    // ========================================================================
677    /// Settings window section IDs that have been toggled from their default collapse state.
678    /// Sections default to open unless specified otherwise; IDs in this set invert the default.
679    #[serde(default, skip_serializing_if = "Vec::is_empty")]
680    pub collapsed_settings_sections: Vec<String>,
681
682    // ========================================================================
683    // Dynamic Profile Sources
684    // ========================================================================
685    /// Remote URLs to fetch profile definitions from
686    #[serde(default, skip_serializing_if = "Vec::is_empty")]
687    pub dynamic_profile_sources: Vec<crate::profile::DynamicProfileSource>,
688
689    // ========================================================================
690    // Security
691    // ========================================================================
692    /// Environment variable substitution allowlist and plain-HTTP profile fetching.
693    ///
694    /// Flattened into the top-level YAML so existing config files remain
695    /// compatible. See [`SecurityConfig`].
696    #[serde(flatten)]
697    pub security: SecurityConfig,
698
699    // ========================================================================
700    // AI Inspector
701    // ========================================================================
702    /// AI Inspector side panel settings (see [`AiInspectorConfig`]).
703    ///
704    /// All `ai_inspector_*` fields are flattened here for YAML backward-compatibility.
705    #[serde(flatten)]
706    pub ai_inspector: AiInspectorConfig,
707
708    // ========================================================================
709    // Runtime security state (never serialized to disk)
710    // ========================================================================
711    /// Names of triggers that have `prompt_before_run: false` with dangerous
712    /// actions (`RunCommand` or `SendText`).
713    ///
714    /// Populated by `Config::warn_insecure_triggers` during config load.
715    /// The UI reads this list to display a persistent visual warning banner
716    /// so users are aware of the reduced security posture.
717    ///
718    /// This field is intentionally skipped by serde — it is computed at
719    /// runtime and never written to or read from the config file.
720    #[serde(skip)]
721    pub insecure_trigger_names: Vec<String>,
722
723    /// Names of triggers with `prompt_before_run: false` that are missing the
724    /// explicit `i_accept_the_risk: true` opt-in.
725    ///
726    /// Dangerous actions for these triggers are **blocked** at execution time.
727    /// Users must add `i_accept_the_risk: true` to each such trigger to permit
728    /// automatic execution without the confirmation dialog.
729    ///
730    /// This field is intentionally skipped by serde — it is computed at
731    /// runtime and never written to or read from the config file.
732    #[serde(skip)]
733    pub unaccepted_risk_trigger_names: Vec<String>,
734}
735
736#[cfg(test)]
737mod new_tab_position_config_tests {
738    use super::*;
739    use crate::NewTabPosition;
740
741    #[test]
742    fn new_tab_position_defaults_to_end() {
743        let config = Config::default();
744        assert_eq!(config.tabs.new_tab_position, NewTabPosition::End);
745    }
746
747    #[test]
748    fn new_tab_position_deserializes_from_yaml() {
749        let yaml = "new_tab_position: after_active";
750        let config: Config = serde_yaml_ng::from_str(yaml).unwrap();
751        assert_eq!(config.tabs.new_tab_position, NewTabPosition::AfterActive);
752    }
753
754    #[test]
755    fn config_without_new_tab_position_deserializes_to_default() {
756        // Existing configs that don't have this field must deserialize cleanly — zero migration.
757        let yaml = "tab_inherit_cwd: true";
758        let config: Config = serde_yaml_ng::from_str(yaml).unwrap();
759        assert_eq!(config.tabs.new_tab_position, NewTabPosition::End);
760    }
761}