pacsea/state/
modal.rs

1//! Modal dialog state for the UI.
2
3use crate::state::types::{OptionalDepRow, PackageItem, Source};
4use std::collections::HashSet;
5
6/// What: Enumerates the high-level operations represented in the preflight
7/// workflow.
8///
9/// - Input: Selected by callers when presenting confirmation or preflight
10///   dialogs.
11/// - Output: Indicates whether the UI should prepare for an install or remove
12///   transaction.
13/// - Details: Drives copy, button labels, and logging in the preflight and
14///   execution flows.
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum PreflightAction {
17    /// Install packages action.
18    Install,
19    /// Remove packages action.
20    Remove,
21    /// Downgrade packages action.
22    Downgrade,
23}
24
25/// What: Purpose for password prompt.
26///
27/// Inputs:
28/// - Set when showing password prompt modal.
29///
30/// Output:
31/// - Used to customize prompt message and context.
32///
33/// Details:
34/// - Indicates which operation requires sudo authentication.
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum PasswordPurpose {
37    /// Installing packages.
38    Install,
39    /// Removing packages.
40    Remove,
41    /// Updating system.
42    Update,
43    /// Downgrading packages.
44    Downgrade,
45    /// Syncing file database.
46    FileSync,
47}
48
49/// What: Identifies which tab within the preflight modal is active.
50///
51/// - Input: Set by UI event handlers responding to user navigation.
52/// - Output: Informs the renderer which data set to display (summary, deps,
53///   files, etc.).
54/// - Details: Enables multi-step review of package operations without losing
55///   context between tabs.
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub enum PreflightTab {
58    /// Summary tab showing overview of package operations.
59    Summary,
60    /// Dependencies tab showing dependency analysis.
61    Deps,
62    /// Files tab showing file change analysis.
63    Files,
64    /// Services tab showing service impact analysis.
65    Services,
66    /// Sandbox tab showing sandbox analysis.
67    Sandbox,
68}
69
70/// Removal cascade strategy for `pacman` operations.
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub enum CascadeMode {
73    /// `pacman -R` – remove targets only.
74    Basic,
75    /// `pacman -Rs` – remove targets and orphaned dependencies.
76    Cascade,
77    /// `pacman -Rns` – cascade removal and prune configuration files.
78    CascadeWithConfigs,
79}
80
81impl CascadeMode {
82    /// Return the `pacman` flag sequence corresponding to this `CascadeMode`.
83    #[must_use]
84    pub const fn flag(self) -> &'static str {
85        match self {
86            Self::Basic => "-R",
87            Self::Cascade => "-Rs",
88            Self::CascadeWithConfigs => "-Rns",
89        }
90    }
91
92    /// Short text describing the effect of this `CascadeMode`.
93    #[must_use]
94    pub const fn description(self) -> &'static str {
95        match self {
96            Self::Basic => "targets only",
97            Self::Cascade => "remove dependents",
98            Self::CascadeWithConfigs => "dependents + configs",
99        }
100    }
101
102    /// Whether this `CascadeMode` allows removal when dependents exist.
103    #[must_use]
104    pub const fn allows_dependents(self) -> bool {
105        !matches!(self, Self::Basic)
106    }
107
108    /// Cycle to the next `CascadeMode`.
109    #[must_use]
110    pub const fn next(self) -> Self {
111        match self {
112            Self::Basic => Self::Cascade,
113            Self::Cascade => Self::CascadeWithConfigs,
114            Self::CascadeWithConfigs => Self::Basic,
115        }
116    }
117}
118
119/// Dependency information for a package in the preflight dependency view.
120#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
121pub struct DependencyInfo {
122    /// Package name.
123    pub name: String,
124    /// Required version constraint (e.g., ">=1.2.3" or "1.2.3").
125    pub version: String,
126    /// Current status of this dependency.
127    pub status: DependencyStatus,
128    /// Source repository or origin.
129    pub source: DependencySource,
130    /// Packages that require this dependency.
131    pub required_by: Vec<String>,
132    /// Packages that this dependency depends on (transitive deps).
133    pub depends_on: Vec<String>,
134    /// Whether this is a core repository package.
135    pub is_core: bool,
136    /// Whether this is a critical system package.
137    pub is_system: bool,
138}
139
140/// Summary statistics for reverse dependency analysis of removal targets.
141#[derive(Clone, Debug, Default)]
142pub struct ReverseRootSummary {
143    /// Package slated for removal.
144    pub package: String,
145    /// Number of packages that directly depend on this package (depth 1).
146    pub direct_dependents: usize,
147    /// Number of packages that depend on this package through other packages (depth ≥ 2).
148    pub transitive_dependents: usize,
149    /// Total number of dependents (direct + transitive).
150    pub total_dependents: usize,
151}
152
153/// Status of a dependency relative to the current system state.
154#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
155pub enum DependencyStatus {
156    /// Already installed and version matches requirement.
157    Installed {
158        /// Installed version of the package.
159        version: String,
160    },
161    /// Not installed, needs to be installed.
162    ToInstall,
163    /// Installed but outdated, needs upgrade.
164    ToUpgrade {
165        /// Current installed version.
166        current: String,
167        /// Required version for upgrade.
168        required: String,
169    },
170    /// Conflicts with existing packages.
171    Conflict {
172        /// Reason for the conflict.
173        reason: String,
174    },
175    /// Cannot be found in configured repositories or AUR.
176    Missing,
177}
178
179/// Source of a dependency package.
180#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
181pub enum DependencySource {
182    /// Official repository package.
183    Official {
184        /// Repository name (e.g., "core", "extra").
185        repo: String,
186    },
187    /// AUR package.
188    Aur,
189    /// Local package (not in repos).
190    Local,
191}
192
193/// What: Restart preference applied to an impacted `systemd` service.
194///
195/// Inputs:
196/// - Assigned automatically from heuristics or by user toggles within the Services tab.
197///
198/// Output:
199/// - Guides post-transaction actions responsible for restarting (or deferring) service units.
200///
201/// Details:
202/// - Provides a simplified binary choice: restart immediately or defer for later manual handling.
203#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
204pub enum ServiceRestartDecision {
205    /// Explicitly restart the unit after the transaction.
206    Restart,
207    /// Defer restarting the unit.
208    Defer,
209}
210
211/// What: Aggregated information about a `systemd` unit affected by the pending operation.
212///
213/// Inputs:
214/// - Populated by the service impact resolver which correlates package file lists and
215///   `systemctl` state.
216///
217/// Output:
218/// - Supplies UI rendering with package provenance, restart status, and the current user choice.
219///
220/// Details:
221/// - `providers` lists packages that ship the unit. `is_active` flags if the unit currently runs.
222///   `needs_restart` indicates detected impact. `recommended_decision` records the resolver default,
223///   and `restart_decision` reflects any user override applied in the UI.
224#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
225pub struct ServiceImpact {
226    /// Fully-qualified unit name (e.g., `sshd.service`).
227    pub unit_name: String,
228    /// Packages contributing this unit.
229    pub providers: Vec<String>,
230    /// Whether the unit is active (`systemctl is-active == active`).
231    pub is_active: bool,
232    /// Whether a restart is recommended because files/configs will change.
233    pub needs_restart: bool,
234    /// Resolver-suggested action prior to user adjustments.
235    pub recommended_decision: ServiceRestartDecision,
236    /// Restart decision currently applied to the unit (may differ from recommendation).
237    pub restart_decision: ServiceRestartDecision,
238}
239
240/// Type of file change in a package operation.
241#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
242pub enum FileChangeType {
243    /// File will be newly installed (not currently on system).
244    New,
245    /// File exists but will be replaced/updated.
246    Changed,
247    /// File will be removed (for Remove operations).
248    Removed,
249}
250
251/// Information about a file change in a package operation.
252#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
253pub struct FileChange {
254    /// Full path of the file.
255    pub path: String,
256    /// Type of change (new/changed/removed).
257    pub change_type: FileChangeType,
258    /// Package that owns this file.
259    pub package: String,
260    /// Whether this is a configuration file (under /etc or marked as backup).
261    pub is_config: bool,
262    /// Whether this file is predicted to create a .pacnew file (config conflict).
263    pub predicted_pacnew: bool,
264    /// Whether this file is predicted to create a .pacsave file (config removal).
265    pub predicted_pacsave: bool,
266}
267
268/// File information for a package in the preflight file view.
269#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
270pub struct PackageFileInfo {
271    /// Package name.
272    pub name: String,
273    /// List of file changes for this package.
274    pub files: Vec<FileChange>,
275    /// Total number of files (including directories).
276    pub total_count: usize,
277    /// Number of new files.
278    pub new_count: usize,
279    /// Number of changed files.
280    pub changed_count: usize,
281    /// Number of removed files.
282    pub removed_count: usize,
283    /// Number of configuration files.
284    pub config_count: usize,
285    /// Number of files predicted to create .pacnew files.
286    pub pacnew_candidates: usize,
287    /// Number of files predicted to create .pacsave files.
288    pub pacsave_candidates: usize,
289}
290
291/// What: Risk severity buckets used by the preflight summary header and messaging.
292///
293/// Inputs:
294/// - Assigned by the summary resolver based on aggregate risk score thresholds.
295///
296/// Output:
297/// - Guides color selection and descriptive labels for risk indicators across the UI.
298///
299/// Details:
300/// - Defaults to `Low` so callers without computed risk can render a safe baseline.
301#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
302pub enum RiskLevel {
303    /// Low risk level.
304    Low,
305    /// Medium risk level.
306    Medium,
307    /// High risk level.
308    High,
309}
310
311impl Default for RiskLevel {
312    /// What: Provide a baseline risk level when no assessment has been computed yet.
313    ///
314    /// Inputs: None.
315    ///
316    /// Output: Always returns `RiskLevel::Low`.
317    ///
318    /// Details:
319    /// - Keeps `Default` implementations for composite structs simple while biasing towards safety.
320    fn default() -> Self {
321        Self::Low
322    }
323}
324
325/// What: Aggregated chip metrics displayed in the Preflight header, execution sidebar, and post-summary.
326///
327/// Inputs:
328/// - Populated by the summary planner once package metadata and risk scores are available.
329///
330/// Output:
331/// - Supplies counts and byte deltas for UI components needing condensed statistics.
332///
333/// Details:
334/// - Stores signed install deltas so removals show negative values without additional conversion.
335#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
336pub struct PreflightHeaderChips {
337    /// Number of packages in the operation.
338    pub package_count: usize,
339    /// Total download size in bytes.
340    pub download_bytes: u64,
341    /// Net change in installed size in bytes (positive for installs, negative for removals).
342    pub install_delta_bytes: i64,
343    /// Number of AUR packages in the operation.
344    pub aur_count: usize,
345    /// Risk score (0-255) computed from various risk factors.
346    pub risk_score: u8,
347    /// Risk level category (Low/Medium/High).
348    pub risk_level: RiskLevel,
349}
350
351impl Default for PreflightHeaderChips {
352    /// What: Provide neutral header chip values prior to summary computation.
353    ///
354    /// Inputs: None.
355    ///
356    /// Output: Returns a struct with zeroed counters and low risk classification.
357    ///
358    /// Details:
359    /// - Facilitates cheap initialization for modals created before async planners finish.
360    fn default() -> Self {
361        Self {
362            package_count: 0,
363            download_bytes: 0,
364            install_delta_bytes: 0,
365            aur_count: 0,
366            risk_score: 0,
367            risk_level: RiskLevel::Low,
368        }
369    }
370}
371
372/// What: Version comparison details for a single package in the preflight summary.
373///
374/// Inputs:
375/// - Filled with installed and target versions, plus classification flags.
376///
377/// Output:
378/// - Enables the UI to display per-package version deltas, major bumps, and downgrade warnings.
379///
380/// Details:
381/// - Notes array allows the planner to surface auxiliary hints (e.g., pacnew prediction or service impacts).
382#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
383pub struct PreflightPackageSummary {
384    /// Package name.
385    pub name: String,
386    /// Package source (official/AUR/local).
387    pub source: Source,
388    /// Installed version, if present.
389    pub installed_version: Option<String>,
390    /// Target version to be installed.
391    pub target_version: String,
392    /// Whether the operation downgrades the package.
393    pub is_downgrade: bool,
394    /// Whether the update is a major version bump.
395    pub is_major_bump: bool,
396    /// Download size contribution for this package when available.
397    pub download_bytes: Option<u64>,
398    /// Net installed size delta contributed by this package (signed).
399    pub install_delta_bytes: Option<i64>,
400    /// Notes or warnings specific to this package.
401    pub notes: Vec<String>,
402}
403
404/// What: Comprehensive dataset backing the Preflight Summary tab.
405///
406/// Inputs:
407/// - Populated by summary resolution logic once package metadata, sizes, and risk heuristics are computed.
408///
409/// Output:
410/// - Delivers structured information for tab body rendering, risk callouts, and contextual notes.
411///
412/// Details:
413/// - `summary_notes` aggregates high-impact bullet points (e.g., kernel updates, pacnew predictions).
414#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
415pub struct PreflightSummaryData {
416    /// Per-package summaries for the operation.
417    pub packages: Vec<PreflightPackageSummary>,
418    /// Total number of packages represented in `packages`.
419    pub package_count: usize,
420    /// Number of AUR-sourced packages participating in the plan.
421    pub aur_count: usize,
422    /// Total download size for the plan.
423    pub download_bytes: u64,
424    /// Net install size delta for the plan (signed).
425    pub install_delta_bytes: i64,
426    /// Aggregate risk score (0-255).
427    pub risk_score: u8,
428    /// Aggregate risk level (Low/Medium/High).
429    pub risk_level: RiskLevel,
430    /// Reasons contributing to the risk score.
431    pub risk_reasons: Vec<String>,
432    /// Packages classified as major version bumps (e.g., 1.x -> 2.0).
433    pub major_bump_packages: Vec<String>,
434    /// Core/system packages flagged as high impact (kernel, glibc, etc.).
435    pub core_system_updates: Vec<String>,
436    /// Total predicted .pacnew files across all packages.
437    pub pacnew_candidates: usize,
438    /// Total predicted .pacsave files across all packages.
439    pub pacsave_candidates: usize,
440    /// Packages with configuration merge warnings (.pacnew expected).
441    pub config_warning_packages: Vec<String>,
442    /// Services likely requiring restart after the transaction.
443    pub service_restart_units: Vec<String>,
444    /// Free-form warnings assembled by the summary planner to highlight notable risks.
445    pub summary_warnings: Vec<String>,
446    /// Notes summarizing key items in the plan.
447    pub summary_notes: Vec<String>,
448}
449
450/// What: Captures all dialog state for the various modal overlays presented in
451/// the Pacsea TUI.
452///
453/// - Input: Mutated by event handlers in response to user actions or
454///   background updates.
455/// - Output: Drives conditional rendering and behavior of each modal type.
456/// - Details: Acts as a tagged union so only one modal can be active at a time
457///   while carrying the precise data needed for that modal's UI.
458#[derive(Debug, Clone, Default)]
459#[allow(clippy::large_enum_variant)]
460pub enum Modal {
461    /// No modal is currently displayed.
462    #[default]
463    None,
464    /// Informational alert with a non-interactive message.
465    Alert {
466        /// Alert message text.
467        message: String,
468    },
469    /// Loading indicator shown during background computation.
470    Loading {
471        /// Loading message text.
472        message: String,
473    },
474    /// Confirmation dialog for installing the given items.
475    ConfirmInstall {
476        /// Package items to install.
477        items: Vec<PackageItem>,
478    },
479    /// Confirmation dialog for reinstalling already installed packages.
480    ConfirmReinstall {
481        /// Packages that are already installed (shown in the confirmation dialog).
482        items: Vec<PackageItem>,
483        /// All packages to install (including both installed and not installed).
484        all_items: Vec<PackageItem>,
485        /// Header chip metrics for the operation.
486        header_chips: PreflightHeaderChips,
487    },
488    /// Confirmation dialog for batch updates that may cause dependency conflicts.
489    ConfirmBatchUpdate {
490        /// Package items to update.
491        items: Vec<PackageItem>,
492        /// Whether this is a dry-run operation.
493        dry_run: bool,
494    },
495    /// Confirmation dialog for continuing AUR update after pacman failed.
496    ConfirmAurUpdate {
497        /// Message explaining the situation.
498        message: String,
499    },
500    /// Preflight summary before executing any action.
501    Preflight {
502        /// Packages selected for the operation.
503        items: Vec<PackageItem>,
504        /// Action to perform (install/remove/downgrade).
505        action: PreflightAction,
506        /// Currently active preflight tab.
507        tab: PreflightTab,
508        /// Aggregated summary information for versions, sizes, and risk cues.
509        summary: Option<Box<PreflightSummaryData>>,
510        /// Scroll offset (lines) for the Summary tab content (mouse scrolling only).
511        summary_scroll: u16,
512        /// Header chip data shared across summary, execution, and post-summary screens.
513        header_chips: PreflightHeaderChips,
514        /// Resolved dependency information (populated when Deps tab is accessed).
515        dependency_info: Vec<DependencyInfo>,
516        /// Selected index in the dependency list (for navigation).
517        dep_selected: usize,
518        /// Set of dependency names with expanded tree nodes (for tree view).
519        dep_tree_expanded: HashSet<String>,
520        /// Error message from dependency resolution, if any.
521        deps_error: Option<String>,
522        /// File information (populated when Files tab is accessed).
523        file_info: Vec<PackageFileInfo>,
524        /// Selected index in the file list (for navigation).
525        file_selected: usize,
526        /// Set of package names with expanded file lists (for Files tab tree view).
527        file_tree_expanded: HashSet<String>,
528        /// Error message from file resolution, if any.
529        files_error: Option<String>,
530        /// Service impact information (populated when Services tab is accessed).
531        service_info: Vec<ServiceImpact>,
532        /// Selected index in the service impact list (for navigation).
533        service_selected: usize,
534        /// Whether service impacts have been resolved for the current session.
535        services_loaded: bool,
536        /// Error message from service resolution, if any.
537        services_error: Option<String>,
538        /// Sandbox information for AUR packages (populated when Sandbox tab is accessed).
539        sandbox_info: Vec<crate::logic::sandbox::SandboxInfo>,
540        /// Selected index in the sandbox display list (for navigation - can be package or dependency).
541        sandbox_selected: usize,
542        /// Set of package names with expanded dependency lists (for Sandbox tab tree view).
543        sandbox_tree_expanded: HashSet<String>,
544        /// Whether sandbox info has been resolved for the current session.
545        sandbox_loaded: bool,
546        /// Error message from sandbox resolution, if any.
547        sandbox_error: Option<String>,
548        /// Selected optional dependencies to install with their packages.
549        /// Maps package name -> set of selected optional dependency names.
550        selected_optdepends: std::collections::HashMap<String, std::collections::HashSet<String>>,
551        /// Current cascade removal strategy for this session.
552        cascade_mode: CascadeMode,
553        /// Cached reverse dependency report for Remove actions (populated during summary computation).
554        /// This avoids redundant resolution when switching to the Deps tab.
555        cached_reverse_deps_report: Option<crate::logic::deps::ReverseDependencyReport>,
556    },
557    /// Preflight execution screen with log and sticky sidebar.
558    PreflightExec {
559        /// Packages being processed.
560        items: Vec<PackageItem>,
561        /// Action being executed (install/remove/downgrade).
562        action: PreflightAction,
563        /// Tab to display while executing.
564        tab: PreflightTab,
565        /// Whether verbose logging is enabled.
566        verbose: bool,
567        /// Execution log lines.
568        log_lines: Vec<String>,
569        /// Whether the operation can be aborted.
570        abortable: bool,
571        /// Header chip metrics displayed in the sidebar.
572        header_chips: PreflightHeaderChips,
573        /// Execution result: `Some(true)` for success, `Some(false)` for failure, `None` if not yet completed.
574        success: Option<bool>,
575    },
576    /// Post-transaction summary with results and follow-ups.
577    PostSummary {
578        /// Whether the operation succeeded.
579        success: bool,
580        /// Number of files changed.
581        changed_files: usize,
582        /// Number of .pacnew files created.
583        pacnew_count: usize,
584        /// Number of .pacsave files created.
585        pacsave_count: usize,
586        /// Services pending restart.
587        services_pending: Vec<String>,
588        /// Snapshot label if created.
589        snapshot_label: Option<String>,
590    },
591    /// Help overlay with keybindings. Non-interactive; dismissed with Esc/Enter.
592    Help,
593    /// Confirmation dialog for removing the given items.
594    ConfirmRemove {
595        /// Package items to remove.
596        items: Vec<PackageItem>,
597    },
598    /// System update dialog with multi-select options and optional country.
599    SystemUpdate {
600        /// Whether to update Arch mirrors using reflector.
601        do_mirrors: bool,
602        /// Whether to update system packages via pacman.
603        do_pacman: bool,
604        /// Whether to force sync databases (pacman -Syyu instead of -Syu).
605        force_sync: bool,
606        /// Whether to update AUR packages via paru/yay.
607        do_aur: bool,
608        /// Whether to remove caches (pacman and AUR helper).
609        do_cache: bool,
610        /// Index into `countries` for the reflector `--country` argument.
611        country_idx: usize,
612        /// Available countries to choose from for reflector.
613        countries: Vec<String>,
614        /// Requested mirror count to fetch/rank.
615        mirror_count: u16,
616        /// Cursor row in the dialog (0..=4)
617        cursor: usize,
618    },
619    /// Arch Linux News: list of recent items with selection.
620    News {
621        /// Latest news feed items (Arch news, advisories, updates, comments).
622        items: Vec<crate::state::types::NewsFeedItem>,
623        /// Selected row index.
624        selected: usize,
625        /// Scroll offset (lines) for the news list.
626        scroll: u16,
627    },
628    /// Application announcement: markdown content displayed at startup.
629    Announcement {
630        /// Title to display in the modal header.
631        title: String,
632        /// Markdown content to display.
633        content: String,
634        /// Unique identifier for this announcement (version string or remote ID).
635        id: String,
636        /// Scroll offset (lines) for long content.
637        scroll: u16,
638    },
639    /// Available package updates: list of update entries with scroll support.
640    Updates {
641        /// Update entries with package name, old version, and new version.
642        entries: Vec<(String, String, String)>, // (name, old_version, new_version)
643        /// Scroll offset (lines) for the updates list.
644        scroll: u16,
645        /// Selected row index.
646        selected: usize,
647    },
648    /// TUI Optional Dependencies chooser: selectable rows with install status.
649    OptionalDeps {
650        /// Rows to display (pre-filtered by environment/distro).
651        rows: Vec<OptionalDepRow>,
652        /// Selected row index.
653        selected: usize,
654    },
655    /// Select which scans to run before executing the AUR scan.
656    ScanConfig {
657        /// Whether to run `ClamAV` (clamscan).
658        do_clamav: bool,
659        /// Whether to run Trivy filesystem scan.
660        do_trivy: bool,
661        /// Whether to run Semgrep static analysis.
662        do_semgrep: bool,
663        /// Whether to run `ShellCheck` on `PKGBUILD`/.install.
664        do_shellcheck: bool,
665        /// Whether to run `VirusTotal` hash lookups.
666        do_virustotal: bool,
667        /// Whether to run custom suspicious-pattern scan (PKGBUILD/.install).
668        do_custom: bool,
669        /// Whether to run aur-sleuth (LLM audit).
670        do_sleuth: bool,
671        /// Cursor row in the dialog.
672        cursor: usize,
673    },
674    /// Prompt to install `GNOME Terminal` at startup on GNOME when not present.
675    GnomeTerminalPrompt,
676    /// Setup dialog for `VirusTotal` API key.
677    VirusTotalSetup {
678        /// User-entered API key buffer.
679        input: String,
680        /// Cursor position within the input buffer.
681        cursor: usize,
682    },
683    /// Information dialog explaining the Import file format.
684    ImportHelp,
685    /// Setup dialog for startup news popup configuration.
686    NewsSetup {
687        /// Whether to show Arch news.
688        show_arch_news: bool,
689        /// Whether to show security advisories.
690        show_advisories: bool,
691        /// Whether to show AUR updates.
692        show_aur_updates: bool,
693        /// Whether to show AUR comments.
694        show_aur_comments: bool,
695        /// Whether to show official package updates.
696        show_pkg_updates: bool,
697        /// Maximum age of news items in days (7, 30, or 90).
698        max_age_days: Option<u32>,
699        /// Current cursor position (0-5 for toggles, 6-8 for date buttons).
700        cursor: usize,
701    },
702    /// Password prompt for sudo authentication.
703    PasswordPrompt {
704        /// Purpose of the password prompt.
705        purpose: PasswordPurpose,
706        /// Packages involved in the operation.
707        items: Vec<PackageItem>,
708        /// User input buffer for password.
709        input: String,
710        /// Cursor position within the input buffer.
711        cursor: usize,
712        /// Error message if password was incorrect.
713        error: Option<String>,
714    },
715}
716
717#[cfg(test)]
718mod tests {
719    #[test]
720    /// What: Confirm each `Modal` variant can be constructed and the `Default` implementation returns `Modal::None`.
721    ///
722    /// Inputs:
723    /// - No external inputs; instantiates representative variants directly inside the test.
724    ///
725    /// Output:
726    /// - Ensures `Default::default()` yields `Modal::None` and variant constructors remain stable.
727    ///
728    /// Details:
729    /// - Acts as a regression guard when fields or defaults change, catching compile-time or panicking construction paths.
730    fn modal_default_and_variants_construct() {
731        let m = super::Modal::default();
732        matches!(m, super::Modal::None);
733        let _ = super::Modal::Alert {
734            message: "hi".into(),
735        };
736        let _ = super::Modal::ConfirmInstall { items: Vec::new() };
737        let _ = super::Modal::ConfirmReinstall {
738            items: Vec::new(),
739            all_items: Vec::new(),
740            header_chips: crate::state::modal::PreflightHeaderChips::default(),
741        };
742        let _ = super::Modal::Help;
743        let _ = super::Modal::ConfirmRemove { items: Vec::new() };
744        let _ = super::Modal::SystemUpdate {
745            do_mirrors: true,
746            do_pacman: true,
747            force_sync: false,
748            do_aur: true,
749            do_cache: false,
750            country_idx: 0,
751            countries: vec!["US".into()],
752            mirror_count: 20,
753            cursor: 0,
754        };
755        let _ = super::Modal::News {
756            items: Vec::new(),
757            selected: 0,
758            scroll: 0,
759        };
760        let _ = super::Modal::OptionalDeps {
761            rows: Vec::new(),
762            selected: 0,
763        };
764        let _ = super::Modal::GnomeTerminalPrompt;
765        let _ = super::Modal::VirusTotalSetup {
766            input: String::new(),
767            cursor: 0,
768        };
769        let _ = super::Modal::ImportHelp;
770        let _ = super::Modal::PasswordPrompt {
771            purpose: super::PasswordPurpose::Install,
772            items: Vec::new(),
773            input: String::new(),
774            cursor: 0,
775            error: None,
776        };
777        let _ = super::Modal::Preflight {
778            items: Vec::new(),
779            action: super::PreflightAction::Install,
780            tab: super::PreflightTab::Summary,
781            summary: None,
782            summary_scroll: 0,
783            header_chips: super::PreflightHeaderChips::default(),
784            dependency_info: Vec::new(),
785            dep_selected: 0,
786            dep_tree_expanded: std::collections::HashSet::new(),
787            deps_error: None,
788            file_info: Vec::new(),
789            file_selected: 0,
790            file_tree_expanded: std::collections::HashSet::new(),
791            files_error: None,
792            service_info: Vec::new(),
793            service_selected: 0,
794            services_loaded: false,
795            services_error: None,
796            sandbox_info: Vec::new(),
797            sandbox_selected: 0,
798            sandbox_tree_expanded: std::collections::HashSet::new(),
799            sandbox_loaded: false,
800            sandbox_error: None,
801            selected_optdepends: std::collections::HashMap::new(),
802            cascade_mode: super::CascadeMode::Basic,
803            cached_reverse_deps_report: None,
804        };
805    }
806}