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}