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