Skip to main content

pacsea/state/app_state/
mod.rs

1//! Central `AppState` container, split out from the monolithic module.
2
3use lru::LruCache;
4use ratatui::widgets::ListState;
5use std::{
6    collections::HashMap, collections::HashSet, collections::VecDeque, path::PathBuf, time::Instant,
7};
8
9use crate::sources::VoteAction;
10use crate::state::config_editor::ConfigEditorState;
11use crate::state::modal::{
12    CascadeMode, Modal, PreflightAction, RepoOverlapApplyPending, RepositoriesModalResume,
13    ServiceImpact,
14};
15use crate::state::types::{
16    AppMode, ArchStatusColor, Focus, InstalledPackagesMode, NewsFeedItem, NewsReadFilter,
17    NewsSortMode, PackageDetails, PackageItem, RightPaneFocus, SortMode,
18};
19use crate::theme::KeyMap;
20
21mod constants;
22mod default_impl;
23mod defaults;
24mod defaults_cache;
25mod methods;
26
27#[cfg(test)]
28mod tests;
29
30pub use constants::{FileSyncResult, RECENT_CAPACITY, recent_capacity};
31
32/// What: UI-facing live vote-state for an AUR package.
33///
34/// Details:
35/// - `Unknown`: no live check requested yet.
36/// - `Loading`: background check is currently in flight.
37/// - `Voted`: current user has voted for the package.
38/// - `NotVoted`: current user has not voted for the package.
39/// - `Error`: last check failed with a short user-facing reason.
40#[derive(Clone, Debug, PartialEq, Eq)]
41pub enum AurVoteStateUi {
42    /// No vote-state has been requested yet.
43    Unknown,
44    /// Vote-state request is currently running in background.
45    Loading,
46    /// Current user has voted for the package.
47    Voted,
48    /// Current user has not voted for the package.
49    NotVoted,
50    /// Vote-state request failed.
51    Error(String),
52}
53
54/// What: Execution status for PKGBUILD static checks in the preview panel.
55#[derive(Clone, Copy, Debug, PartialEq, Eq)]
56pub enum PkgbuildCheckStatus {
57    /// No checks have been requested yet.
58    Idle,
59    /// Checks are currently running in a background worker.
60    Running,
61    /// Checks completed and data is available for rendering.
62    Complete,
63}
64
65/// What: Supported static checker tool names for PKGBUILD preview checks.
66#[derive(Clone, Copy, Debug, PartialEq, Eq)]
67pub enum PkgbuildCheckTool {
68    /// `shellcheck` output.
69    Shellcheck,
70    /// `namcap` output.
71    Namcap,
72}
73
74/// What: Severity of a parsed PKGBUILD check finding.
75#[derive(Clone, Copy, Debug, PartialEq, Eq)]
76pub enum PkgbuildCheckSeverity {
77    /// High-confidence error that should be fixed.
78    Error,
79    /// Warning that likely needs manual review.
80    Warning,
81    /// Informational note from checker output.
82    Info,
83}
84
85/// What: Parsed finding line for PKGBUILD static check output.
86#[derive(Clone, Debug, PartialEq, Eq)]
87pub struct PkgbuildCheckFinding {
88    /// Tool that produced the finding.
89    pub tool: PkgbuildCheckTool,
90    /// Parsed severity level.
91    pub severity: PkgbuildCheckSeverity,
92    /// Optional line number in PKGBUILD, if parseable.
93    pub line: Option<u32>,
94    /// User-facing message to show in the findings list.
95    pub message: String,
96}
97
98/// What: Raw execution result for an individual PKGBUILD checker tool.
99#[derive(Clone, Debug, PartialEq, Eq)]
100pub struct PkgbuildToolRawResult {
101    /// Tool name.
102    pub tool: PkgbuildCheckTool,
103    /// Whether the tool binary was available on `PATH`.
104    pub available: bool,
105    /// Exit code when executed.
106    pub exit_code: Option<i32>,
107    /// Whether execution timed out.
108    pub timed_out: bool,
109    /// Exact command string executed (or would be executed in dry-run).
110    pub command: String,
111    /// Captured stdout.
112    pub stdout: String,
113    /// Captured stderr.
114    pub stderr: String,
115}
116
117/// Global application state shared by the event, networking, and UI layers.
118///
119/// This structure is mutated frequently in response to input and background
120/// updates. Certain subsets are persisted to disk to preserve user context
121/// across runs (e.g., recent searches, details cache, install list).
122#[derive(Debug)]
123#[allow(clippy::struct_excessive_bools)]
124pub struct AppState {
125    /// Current top-level mode (package management, news feed, or config editor).
126    pub app_mode: AppMode,
127    /// Persistent integrated config editor state used while in `AppMode::ConfigEditor`.
128    pub config_editor_state: Box<ConfigEditorState>,
129    /// Current search input text.
130    pub input: String,
131    /// Current search results, most relevant first.
132    pub results: Vec<PackageItem>,
133    /// Unfiltered results as last received from the search worker.
134    pub all_results: Vec<PackageItem>,
135    /// Backup of results when toggling to installed-only view.
136    pub results_backup_for_toggle: Option<Vec<PackageItem>>,
137    /// Index into `results` that is currently highlighted.
138    pub selected: usize,
139    /// Details for the currently highlighted result.
140    pub details: PackageDetails,
141    /// List selection state for the search results list.
142    pub list_state: ListState,
143    /// Active modal dialog, if any.
144    pub modal: Modal,
145    /// Previous modal state (used to restore when closing help/alert modals).
146    pub previous_modal: Option<Modal>,
147    /// If `true`, show install steps without executing side effects.
148    pub dry_run: bool,
149    // Recent searches
150    /// Previously executed queries stored as an LRU cache (keyed case-insensitively).
151    pub recent: LruCache<String, String>,
152    /// List selection state for the Recent pane.
153    pub history_state: ListState,
154    /// Which pane is currently focused.
155    pub focus: Focus,
156    /// Timestamp of the last input edit, used for debouncing or throttling.
157    pub last_input_change: Instant,
158    /// Last value persisted for the input field, to avoid redundant writes.
159    pub last_saved_value: Option<String>,
160    // Persisted recent searches
161    /// Path where recent searches are persisted as JSON.
162    pub recent_path: PathBuf,
163    /// Dirty flag indicating `recent` needs to be saved.
164    pub recent_dirty: bool,
165
166    // Search coordination
167    /// Identifier of the latest query whose results are being displayed.
168    pub latest_query_id: u64,
169    /// Next query identifier to allocate.
170    pub next_query_id: u64,
171    // Search result cache
172    /// Cached search query text (None if cache is empty or invalid).
173    pub search_cache_query: Option<String>,
174    /// Whether fuzzy mode was used for cached query.
175    pub search_cache_fuzzy: bool,
176    /// Cached search results (None if cache is empty or invalid).
177    pub search_cache_results: Option<Vec<PackageItem>>,
178    // Details cache
179    /// Cache of details keyed by package name.
180    pub details_cache: HashMap<String, PackageDetails>,
181    /// Path where the details cache is persisted as JSON.
182    pub cache_path: PathBuf,
183    /// Dirty flag indicating `details_cache` needs to be saved.
184    pub cache_dirty: bool,
185
186    // News read/unread tracking (persisted)
187    /// Set of Arch news item URLs the user has marked as read.
188    pub news_read_urls: std::collections::HashSet<String>,
189    /// Path where the read news URLs are persisted as JSON.
190    pub news_read_path: PathBuf,
191    /// Dirty flag indicating `news_read_urls` needs to be saved.
192    pub news_read_dirty: bool,
193    /// Set of news feed item IDs the user has marked as read.
194    pub news_read_ids: std::collections::HashSet<String>,
195    /// Path where the read news IDs are persisted as JSON.
196    pub news_read_ids_path: PathBuf,
197    /// Dirty flag indicating `news_read_ids` needs to be saved.
198    pub news_read_ids_dirty: bool,
199    /// News feed items currently loaded.
200    pub news_items: Vec<NewsFeedItem>,
201    /// Filtered/sorted news results shown in the UI.
202    pub news_results: Vec<NewsFeedItem>,
203    /// Whether the news feed is currently loading.
204    pub news_loading: bool,
205    /// Whether news are ready to be viewed (loading complete and news available).
206    pub news_ready: bool,
207    /// Selected index within news results.
208    pub news_selected: usize,
209    /// List state for news results pane.
210    pub news_list_state: ListState,
211    /// News search input text.
212    pub news_search_input: String,
213    /// Caret position within news search input.
214    pub news_search_caret: usize,
215    /// Selection anchor within news search input.
216    pub news_search_select_anchor: Option<usize>,
217    /// LRU cache of recent news searches (case-insensitive key).
218    pub news_recent: LruCache<String, String>,
219    /// Path where news recent searches are persisted.
220    pub news_recent_path: PathBuf,
221    /// Dirty flag indicating `news_recent` needs to be saved.
222    pub news_recent_dirty: bool,
223    /// Pending news search awaiting debounce before saving to history.
224    pub news_history_pending: Option<String>,
225    /// Timestamp when the pending news search was last updated.
226    pub news_history_pending_at: Option<std::time::Instant>,
227    /// Last news search saved to history (prevents duplicate saves).
228    pub news_history_last_saved: Option<String>,
229    /// Whether to show Arch news items.
230    pub news_filter_show_arch_news: bool,
231    /// Whether to show security advisories.
232    pub news_filter_show_advisories: bool,
233    /// Whether to show installed package update items.
234    pub news_filter_show_pkg_updates: bool,
235    /// Whether to show AUR package update items.
236    pub news_filter_show_aur_updates: bool,
237    /// Whether to show AUR comment items.
238    pub news_filter_show_aur_comments: bool,
239    /// Whether to restrict advisories to installed packages.
240    pub news_filter_installed_only: bool,
241    /// Read/unread filter for the News Feed list.
242    pub news_filter_read_status: NewsReadFilter,
243    /// Clickable rectangle for Arch news filter chip in news title.
244    pub news_filter_arch_rect: Option<(u16, u16, u16, u16)>,
245    /// Clickable rectangle for security advisory filter chip in news title.
246    pub news_filter_advisory_rect: Option<(u16, u16, u16, u16)>,
247    /// Clickable rectangle for installed-only advisory filter chip in news title.
248    pub news_filter_installed_rect: Option<(u16, u16, u16, u16)>,
249    /// Clickable rectangle for installed update filter chip in news title.
250    pub news_filter_updates_rect: Option<(u16, u16, u16, u16)>,
251    /// Clickable rectangle for AUR update filter chip in news title.
252    pub news_filter_aur_updates_rect: Option<(u16, u16, u16, u16)>,
253    /// Clickable rectangle for AUR comment filter chip in news title.
254    pub news_filter_aur_comments_rect: Option<(u16, u16, u16, u16)>,
255    /// Clickable rectangle for read/unread filter chip in news title.
256    pub news_filter_read_rect: Option<(u16, u16, u16, u16)>,
257    /// Maximum age of news items in days (None = unlimited).
258    pub news_max_age_days: Option<u32>,
259    /// Whether to show the news history pane in News mode.
260    pub show_news_history_pane: bool,
261    /// Whether to show the news bookmarks pane in News mode.
262    pub show_news_bookmarks_pane: bool,
263    /// Sort mode for news results.
264    pub news_sort_mode: NewsSortMode,
265    /// Saved news/bookmarked items with cached content.
266    pub news_bookmarks: Vec<crate::state::types::NewsBookmark>,
267    /// Path where news bookmarks are persisted.
268    pub news_bookmarks_path: PathBuf,
269    /// Dirty flag indicating `news_bookmarks` needs to be saved.
270    pub news_bookmarks_dirty: bool,
271    /// Cache of fetched news article content (URL -> content).
272    pub news_content_cache: std::collections::HashMap<String, String>,
273    /// Path where the news content cache is persisted.
274    pub news_content_cache_path: PathBuf,
275    /// Dirty flag indicating `news_content_cache` needs to be saved.
276    pub news_content_cache_dirty: bool,
277    /// Currently displayed news content (for the selected item).
278    pub news_content: Option<String>,
279    /// Whether news content is currently being fetched.
280    pub news_content_loading: bool,
281    /// When the current news content load started (for timeout/logging).
282    pub news_content_loading_since: Option<std::time::Instant>,
283    /// Debounce timer for news content requests - tracks when user selected current item.
284    /// Only requests content after 0.5 seconds of staying on the same item.
285    pub news_content_debounce_timer: Option<std::time::Instant>,
286    /// Scroll offset for news content details.
287    pub news_content_scroll: u16,
288    /// Path where the cached news feed is persisted.
289    pub news_feed_path: PathBuf,
290    /// Last-seen versions for installed packages (dedup for update feed items).
291    pub news_seen_pkg_versions: HashMap<String, String>,
292    /// Path where last-seen package versions are persisted.
293    pub news_seen_pkg_versions_path: PathBuf,
294    /// Dirty flag indicating `news_seen_pkg_versions` needs to be saved.
295    pub news_seen_pkg_versions_dirty: bool,
296    /// Last-seen AUR comment identifiers per installed package.
297    pub news_seen_aur_comments: HashMap<String, String>,
298    /// Path where last-seen AUR comments are persisted.
299    pub news_seen_aur_comments_path: PathBuf,
300    /// Dirty flag indicating `news_seen_aur_comments` needs to be saved.
301    pub news_seen_aur_comments_dirty: bool,
302
303    // Announcement read tracking (persisted)
304    /// Set of announcement IDs the user has marked as read.
305    /// Tracks both version strings (e.g., "v0.6.0") and remote announcement IDs.
306    pub announcements_read_ids: std::collections::HashSet<String>,
307    /// Path where the read announcement IDs are persisted as JSON.
308    pub announcement_read_path: PathBuf,
309    /// Dirty flag indicating `announcements_read_ids` needs to be saved.
310    pub announcement_dirty: bool,
311
312    // Last startup tracking (for incremental updates)
313    /// Timestamp of the previous TUI startup (format: `YYYYMMDD:HHMMSS`).
314    /// Used to determine what news/updates need fresh fetching vs cached data.
315    pub last_startup_timestamp: Option<String>,
316    /// Path where the last startup timestamp is persisted.
317    pub last_startup_path: PathBuf,
318
319    // Install list pane
320    /// Packages selected for installation.
321    pub install_list: Vec<PackageItem>,
322    /// List selection state for the Install pane.
323    pub install_state: ListState,
324    /// Separate list of packages selected for removal (active in installed-only mode).
325    pub remove_list: Vec<PackageItem>,
326    /// List selection state for the Remove pane.
327    pub remove_state: ListState,
328    /// Separate list of packages selected for downgrade (shown in installed-only mode).
329    pub downgrade_list: Vec<PackageItem>,
330    /// List selection state for the Downgrade pane.
331    pub downgrade_state: ListState,
332    // Persisted install list
333    /// Path where the install list is persisted as JSON.
334    pub install_path: PathBuf,
335    /// Dirty flag indicating `install_list` needs to be saved.
336    pub install_dirty: bool,
337    /// Timestamp of the most recent change to the install list for throttling disk writes.
338    pub last_install_change: Option<Instant>,
339    /// `HashSet` of package names in install list for O(1) membership checking.
340    pub install_list_names: HashSet<String>,
341    /// `HashSet` of package names in remove list for O(1) membership checking.
342    pub remove_list_names: HashSet<String>,
343    /// `HashSet` of package names in downgrade list for O(1) membership checking.
344    pub downgrade_list_names: HashSet<String>,
345
346    // Visibility toggles for middle row panes
347    /// Whether the Recent pane is visible in the middle row.
348    pub show_recent_pane: bool,
349    /// Whether the Install/Remove pane is visible in the middle row.
350    pub show_install_pane: bool,
351    /// Whether to show the keybindings footer in the details pane.
352    pub show_keybinds_footer: bool,
353
354    // In-pane search (for Recent/Install panes)
355    /// Optional, transient find pattern used by pane-local search ("/").
356    pub pane_find: Option<String>,
357
358    /// Whether Search pane is in Normal mode (Vim-like navigation) instead of Insert mode.
359    pub search_normal_mode: bool,
360
361    /// Whether fuzzy search is enabled (fzf-style matching) instead of normal substring search.
362    pub fuzzy_search_enabled: bool,
363
364    /// Caret position (in characters) within the `Search` input.
365    /// Always clamped to the range 0..=`input.chars().count()`.
366    pub search_caret: usize,
367    /// Selection anchor (in characters) for the Search input when selecting text.
368    /// When `None`, no selection is active. When `Some(i)`, the selected range is
369    /// between `min(i, search_caret)` and `max(i, search_caret)` (exclusive upper bound).
370    pub search_select_anchor: Option<usize>,
371
372    // Official package index persistence
373    /// Path to the persisted official package index used for fast offline lookups.
374    pub official_index_path: PathBuf,
375
376    // Loading indicator for official index generation
377    /// Whether the application is currently generating the official index.
378    pub loading_index: bool,
379
380    // Track which package's details the UI is focused on
381    /// Name of the package whose details are being emphasized in the UI, if any.
382    pub details_focus: Option<String>,
383
384    // Ring prefetch debounce state
385    /// Smooth scrolling accumulator for prefetch heuristics.
386    pub scroll_moves: u32,
387    /// Timestamp at which to resume ring prefetching, if paused.
388    pub ring_resume_at: Option<Instant>,
389    /// Whether a ring prefetch is needed soon.
390    pub need_ring_prefetch: bool,
391
392    // Clickable URL button rectangle (x, y, w, h) in terminal cells
393    /// Rectangle of the clickable URL button in terminal cell coordinates.
394    pub url_button_rect: Option<(u16, u16, u16, u16)>,
395
396    // VirusTotal API setup modal clickable URL rectangle
397    /// Rectangle of the clickable `VirusTotal` API URL in the setup modal (x, y, w, h).
398    pub vt_url_rect: Option<(u16, u16, u16, u16)>,
399
400    // Install pane bottom action (Import)
401    /// Clickable rectangle for the Install pane bottom "Import" button (x, y, w, h).
402    pub install_import_rect: Option<(u16, u16, u16, u16)>,
403    /// Clickable rectangle for the Install pane bottom "Export" button (x, y, w, h).
404    pub install_export_rect: Option<(u16, u16, u16, u16)>,
405
406    // Arch status label (middle row footer)
407    /// Latest fetched status message from `status.archlinux.org`.
408    pub arch_status_text: String,
409    /// Clickable rectangle for the status label (x, y, w, h).
410    pub arch_status_rect: Option<(u16, u16, u16, u16)>,
411    /// Optional status color indicator (e.g., operational vs. current incident).
412    pub arch_status_color: ArchStatusColor,
413
414    // Package updates available
415    /// Number of available package updates, if checked.
416    pub updates_count: Option<usize>,
417    /// Sorted list of package names with available updates.
418    pub updates_list: Vec<String>,
419    /// Clickable rectangle for the updates button (x, y, w, h).
420    pub updates_button_rect: Option<(u16, u16, u16, u16)>,
421    /// Clickable rectangle for the news button in News mode (x, y, w, h).
422    pub news_button_rect: Option<(u16, u16, u16, u16)>,
423    /// Whether updates check is currently in progress.
424    pub updates_loading: bool,
425    /// Whether the last completed update check used an authoritative official-repo source (`None` before first result).
426    pub updates_last_check_authoritative: Option<bool>,
427    /// Flag to trigger refresh of updates list after package installation/update.
428    pub refresh_updates: bool,
429    /// Flag to indicate that Updates modal should open after refresh completes.
430    pub pending_updates_modal: bool,
431
432    // Faillock lockout status
433    /// Whether the user account is currently locked out.
434    pub faillock_locked: bool,
435    /// Timestamp when the lockout will expire (if locked).
436    pub faillock_lockout_until: Option<std::time::SystemTime>,
437    /// Remaining lockout time in minutes (if locked).
438    pub faillock_remaining_minutes: Option<u32>,
439
440    // Clickable PKGBUILD button rectangle and viewer state
441    /// Rectangle of the clickable "Show PKGBUILD" in terminal cell coordinates.
442    pub pkgb_button_rect: Option<(u16, u16, u16, u16)>,
443    /// Rectangle of the clickable "Copy PKGBUILD" button in PKGBUILD title.
444    pub pkgb_check_button_rect: Option<(u16, u16, u16, u16)>,
445    /// Rectangle of the clickable "Reload PKGBUILD" button in PKGBUILD title.
446    pub pkgb_reload_button_rect: Option<(u16, u16, u16, u16)>,
447    /// Whether the PKGBUILD viewer is visible (details pane split in half).
448    pub pkgb_visible: bool,
449    /// The fetched PKGBUILD text when available.
450    pub pkgb_text: Option<String>,
451    /// Name of the package that the PKGBUILD is currently for.
452    pub pkgb_package_name: Option<String>,
453    /// Timestamp when PKGBUILD reload was last requested (for debouncing).
454    pub pkgb_reload_requested_at: Option<Instant>,
455    /// Name of the package for which PKGBUILD reload was requested (for debouncing).
456    pub pkgb_reload_requested_for: Option<String>,
457    /// Scroll offset (lines) for the PKGBUILD viewer.
458    pub pkgb_scroll: u16,
459    /// Active subsection for `Ctrl+D` rotation: 0 = PKGBUILD body, 1 = `ShellCheck`, 2 = `Namcap`.
460    pub pkgb_section_cycle: u8,
461    /// Content rectangle of the PKGBUILD viewer (x, y, w, h) when visible.
462    pub pkgb_rect: Option<(u16, u16, u16, u16)>,
463    /// Rectangle of the clickable "Run checks" button in PKGBUILD title.
464    pub pkgb_run_checks_button_rect: Option<(u16, u16, u16, u16)>,
465    /// Current status of PKGBUILD checks in preview panel.
466    pub pkgb_check_status: PkgbuildCheckStatus,
467    /// Parsed findings from latest PKGBUILD check run.
468    pub pkgb_check_findings: Vec<PkgbuildCheckFinding>,
469    /// Raw per-tool outputs from latest PKGBUILD check run.
470    pub pkgb_check_raw_results: Vec<PkgbuildToolRawResult>,
471    /// Missing tool hints shown when ShellCheck/namcap are unavailable.
472    pub pkgb_check_missing_tools: Vec<String>,
473    /// Whether raw output panel is expanded in PKGBUILD preview.
474    pub pkgb_check_show_raw_output: bool,
475    /// Scroll offset for parsed findings list.
476    pub pkgb_check_scroll: u16,
477    /// Scroll offset for raw output panel.
478    pub pkgb_check_raw_scroll: u16,
479    /// Last package name for which checks were run.
480    pub pkgb_check_last_package_name: Option<String>,
481    /// Last completion timestamp for checks.
482    pub pkgb_check_last_run_at: Option<Instant>,
483    /// Last error text for check execution path.
484    pub pkgb_check_last_error: Option<String>,
485
486    // AUR comments viewer state
487    /// Rectangle of the clickable "Show comments" / "Hide comments" button in terminal cell coordinates.
488    pub comments_button_rect: Option<(u16, u16, u16, u16)>,
489    /// Whether the comments viewer is visible (details pane split).
490    pub comments_visible: bool,
491    /// The fetched comments data when available.
492    pub comments: Vec<crate::state::types::AurComment>,
493    /// Name of the package that the comments are currently for.
494    pub comments_package_name: Option<String>,
495    /// Timestamp when comments were last fetched (for cache invalidation).
496    pub comments_fetched_at: Option<Instant>,
497    /// Scroll offset (lines) for the comments viewer.
498    pub comments_scroll: u16,
499    /// Content rectangle of the comments viewer (x, y, w, h) when visible.
500    pub comments_rect: Option<(u16, u16, u16, u16)>,
501    /// Whether comments are currently being fetched.
502    pub comments_loading: bool,
503    /// Error message if comments fetch failed.
504    pub comments_error: Option<String>,
505    /// URLs in comments with their screen positions for click detection.
506    /// Vector of (`x`, `y`, `width`, `url_string`) tuples.
507    pub comments_urls: Vec<(u16, u16, u16, String)>,
508    /// Author names in comments with their screen positions for click detection.
509    /// Vector of (`x`, `y`, `width`, `username`) tuples.
510    pub comments_authors: Vec<(u16, u16, u16, String)>,
511    /// Dates in comments with their screen positions and URLs for click detection.
512    /// Vector of (`x`, `y`, `width`, `url_string`) tuples.
513    pub comments_dates: Vec<(u16, u16, u16, String)>,
514
515    // Transient toast message (bottom-right)
516    /// Optional short-lived info message rendered at the bottom-right corner.
517    pub toast_message: Option<String>,
518    /// Deadline (Instant) after which the toast is automatically hidden.
519    pub toast_expires_at: Option<Instant>,
520
521    // User settings loaded at startup
522    /// Left pane width percentage.
523    pub layout_left_pct: u16,
524    /// Center pane width percentage.
525    pub layout_center_pct: u16,
526    /// Right pane width percentage.
527    pub layout_right_pct: u16,
528    /// Top-to-bottom order of the main vertical stack (results, middle, package info).
529    pub main_pane_order: [crate::state::MainVerticalPane; 3],
530    /// Min/max row counts for vertical layout (semantic per pane, not screen slot).
531    pub vertical_layout_limits: crate::state::VerticalLayoutLimits,
532    /// Resolved key bindings from user settings
533    pub keymap: KeyMap,
534    // Internationalization (i18n)
535    /// Resolved locale code (e.g., "de-DE", "en-US")
536    pub locale: String,
537    /// Translation map for the current locale
538    pub translations: crate::i18n::translations::TranslationMap,
539    /// Fallback translation map (English) for missing keys
540    pub translations_fallback: crate::i18n::translations::TranslationMap,
541
542    // Mouse hit-test rectangles for panes
543    /// Inner content rectangle of the Results list (x, y, w, h).
544    pub results_rect: Option<(u16, u16, u16, u16)>,
545    /// Inner content rectangle of the Package Info details pane (x, y, w, h).
546    pub details_rect: Option<(u16, u16, u16, u16)>,
547    /// Scroll offset (lines) for the Package Info details pane.
548    pub details_scroll: u16,
549    /// Inner content rectangle of the Recent pane list (x, y, w, h).
550    pub recent_rect: Option<(u16, u16, u16, u16)>,
551    /// Inner content rectangle of the Install pane list (x, y, w, h).
552    pub install_rect: Option<(u16, u16, u16, u16)>,
553    /// Inner content rectangle of the Downgrade subpane when visible.
554    pub downgrade_rect: Option<(u16, u16, u16, u16)>,
555    /// Whether mouse capture is temporarily disabled to allow text selection in details.
556    pub mouse_disabled_in_details: bool,
557    /// Last observed mouse position (column, row) in terminal cells.
558    pub last_mouse_pos: Option<(u16, u16)>,
559    /// Whether global terminal mouse capture is currently enabled.
560    pub mouse_capture_enabled: bool,
561
562    // News modal mouse hit-testing
563    /// Outer rectangle of the News modal (including borders) when visible.
564    pub news_rect: Option<(u16, u16, u16, u16)>,
565    /// Inner list rectangle for clickable news rows.
566    pub news_list_rect: Option<(u16, u16, u16, u16)>,
567
568    // Announcement modal mouse hit-testing
569    /// Outer rectangle of the Announcement modal (including borders) when visible.
570    pub announcement_rect: Option<(u16, u16, u16, u16)>,
571    /// URLs in announcement content with their screen positions for click detection.
572    /// Vector of (`x`, `y`, `width`, `url_string`) tuples.
573    pub announcement_urls: Vec<(u16, u16, u16, String)>,
574    /// Pending remote announcements to show after current announcement is dismissed.
575    pub pending_announcements: Vec<crate::announcements::RemoteAnnouncement>,
576    /// Pending news to show after all announcements are dismissed.
577    pub pending_news: Option<Vec<crate::state::NewsItem>>,
578    /// Startup setup steps queued from first-run setup selector.
579    pub pending_startup_setup_steps: VecDeque<crate::state::modal::StartupSetupTask>,
580    /// Flag to trigger startup news fetch after `NewsSetup` is completed.
581    pub trigger_startup_news_fetch: bool,
582    /// Session-scoped latch to avoid repeatedly showing long-run auth preflight warning text.
583    pub long_run_auth_preflight_warned: bool,
584
585    // Updates modal mouse hit-testing
586    /// Outer rectangle of the Updates modal (including borders) when visible.
587    pub updates_modal_rect: Option<(u16, u16, u16, u16)>,
588    /// Clickable rectangle for the `Wizard` button in the Optional Deps modal.
589    pub optional_deps_wizard_rect: Option<(u16, u16, u16, u16)>,
590    /// Outer rectangle of the Optional Deps modal for wheel hit-testing.
591    pub optional_deps_modal_rect: Option<(u16, u16, u16, u16)>,
592    /// Outer rectangle of the System Update modal for wheel hit-testing.
593    pub system_update_modal_rect: Option<(u16, u16, u16, u16)>,
594    /// Outer rectangle of the Repositories modal for wheel hit-testing.
595    pub repositories_modal_rect: Option<(u16, u16, u16, u16)>,
596    /// Clickable row for copying the SSH public key in the AUR SSH setup modal.
597    pub ssh_setup_copy_key_rect: Option<(u16, u16, u16, u16)>,
598    /// Inner content rectangle for scrollable updates list.
599    pub updates_modal_content_rect: Option<(u16, u16, u16, u16)>,
600    /// Per-entry starting rendered line indices for the updates modal content.
601    ///
602    /// Each value maps an entry index to its first rendered line in the wrapped pane output.
603    pub updates_modal_entry_line_starts: Vec<u16>,
604    /// Total rendered line count across all updates entries after wrapping.
605    pub updates_modal_total_lines: u16,
606    /// Timestamp when `g` was pressed in Updates modal awaiting chord completion.
607    pub updates_modal_pending_g_at: Option<Instant>,
608
609    // Help modal scroll and hit-testing
610    /// Scroll offset (lines) for the Help modal content.
611    pub help_scroll: u16,
612    /// Inner content rectangle of the Help modal (x, y, w, h) for hit-testing.
613    pub help_rect: Option<(u16, u16, u16, u16)>,
614
615    // Preflight modal mouse hit-testing
616    /// Clickable rectangles for preflight tabs (x, y, w, h) - Summary, Deps, Files, Services, Sandbox.
617    pub preflight_tab_rects: [Option<(u16, u16, u16, u16)>; 5],
618    /// Inner content rectangle of the preflight modal (x, y, w, h) for hit-testing package groups.
619    pub preflight_content_rect: Option<(u16, u16, u16, u16)>,
620
621    // Results sorting UI
622    /// Current sort mode for results.
623    pub sort_mode: SortMode,
624    /// Filter mode for installed packages (leaf only vs all explicit).
625    pub installed_packages_mode: InstalledPackagesMode,
626    /// Whether the sort dropdown is currently visible.
627    pub sort_menu_open: bool,
628    /// Clickable rectangle for the sort button in the Results title (x, y, w, h).
629    pub sort_button_rect: Option<(u16, u16, u16, u16)>,
630    /// Clickable rectangle for the news age toggle button (x, y, w, h).
631    pub news_age_button_rect: Option<(u16, u16, u16, u16)>,
632    /// Inner content rectangle of the sort dropdown menu when visible (x, y, w, h).
633    pub sort_menu_rect: Option<(u16, u16, u16, u16)>,
634    /// Deadline after which the sort dropdown auto-closes.
635    pub sort_menu_auto_close_at: Option<Instant>,
636    // Sort result caching for O(1) sort mode switching
637    /// Cached sort order for `RepoThenName` mode (indices into `results`).
638    pub sort_cache_repo_name: Option<Vec<usize>>,
639    /// Cached sort order for `AurPopularityThenOfficial` mode (indices into `results`).
640    pub sort_cache_aur_popularity: Option<Vec<usize>>,
641    /// Signature of results used to validate caches (order-insensitive hash of names).
642    pub sort_cache_signature: Option<u64>,
643
644    // Results options UI (top-right dropdown)
645    /// Whether the options dropdown is currently visible.
646    pub options_menu_open: bool,
647    /// Clickable rectangle for the options button in the Results title (x, y, w, h).
648    pub options_button_rect: Option<(u16, u16, u16, u16)>,
649    /// Inner content rectangle of the options dropdown menu when visible (x, y, w, h).
650    pub options_menu_rect: Option<(u16, u16, u16, u16)>,
651
652    // Panels dropdown UI (left of Options)
653    /// Whether the panels dropdown is currently visible.
654    pub panels_menu_open: bool,
655    /// Clickable rectangle for the panels button in the Results title (x, y, w, h).
656    pub panels_button_rect: Option<(u16, u16, u16, u16)>,
657    /// Inner content rectangle of the panels dropdown menu when visible (x, y, w, h).
658    pub panels_menu_rect: Option<(u16, u16, u16, u16)>,
659
660    // Config/Lists dropdown UI (left of Panels)
661    /// Whether the Config/Lists dropdown is currently visible.
662    pub config_menu_open: bool,
663    /// Clickable rectangle for the Config/Lists button in the Results title (x, y, w, h).
664    pub config_button_rect: Option<(u16, u16, u16, u16)>,
665    /// Inner content rectangle of the Config/Lists dropdown menu when visible (x, y, w, h).
666    pub config_menu_rect: Option<(u16, u16, u16, u16)>,
667
668    // Artix filter dropdown UI (when specific repo filters are hidden)
669    /// Whether the Artix filter dropdown is currently visible.
670    pub artix_filter_menu_open: bool,
671    /// Inner content rectangle of the Artix filter dropdown menu when visible (x, y, w, h).
672    pub artix_filter_menu_rect: Option<(u16, u16, u16, u16)>,
673
674    /// Whether the custom `repos.conf` results-filter dropdown is visible.
675    pub custom_repos_filter_menu_open: bool,
676    /// Inner hit-test rect for the custom repos filter dropdown when visible.
677    pub custom_repos_filter_menu_rect: Option<(u16, u16, u16, u16)>,
678
679    // Collapsed menu dropdown UI (when window is too narrow for all three buttons)
680    /// Whether the collapsed menu dropdown is currently visible.
681    pub collapsed_menu_open: bool,
682    /// Clickable rectangle for the collapsed menu button in the Results title (x, y, w, h).
683    pub collapsed_menu_button_rect: Option<(u16, u16, u16, u16)>,
684    /// Inner content rectangle of the collapsed menu dropdown when visible (x, y, w, h).
685    pub collapsed_menu_rect: Option<(u16, u16, u16, u16)>,
686
687    /// Whether Results is currently showing only explicitly installed packages.
688    pub installed_only_mode: bool,
689    /// Which right subpane is focused when installed-only mode splits the pane.
690    pub right_pane_focus: RightPaneFocus,
691    /// Visual marker style for packages added to lists (user preference cached at startup).
692    pub package_marker: crate::theme::PackageMarker,
693
694    // Results filters UI
695    /// Whether to include AUR packages in the Results view.
696    pub results_filter_show_aur: bool,
697    /// Whether to include packages from the `core` repo in the Results view.
698    pub results_filter_show_core: bool,
699    /// Whether to include packages from the `extra` repo in the Results view.
700    pub results_filter_show_extra: bool,
701    /// Whether to include packages from the `multilib` repo in the Results view.
702    pub results_filter_show_multilib: bool,
703    /// Whether to include packages from the `eos` repo in the Results view.
704    pub results_filter_show_eos: bool,
705    /// Whether to include packages from `cachyos*` repos in the Results view.
706    pub results_filter_show_cachyos: bool,
707    /// Whether to include packages from Artix Linux repos in the Results view.
708    pub results_filter_show_artix: bool,
709    /// Whether to include packages from Artix omniverse repo in the Results view.
710    pub results_filter_show_artix_omniverse: bool,
711    /// Whether to include packages from Artix universe repo in the Results view.
712    pub results_filter_show_artix_universe: bool,
713    /// Whether to include packages from Artix lib32 repo in the Results view.
714    pub results_filter_show_artix_lib32: bool,
715    /// Whether to include packages from Artix galaxy repo in the Results view.
716    pub results_filter_show_artix_galaxy: bool,
717    /// Whether to include packages from Artix world repo in the Results view.
718    pub results_filter_show_artix_world: bool,
719    /// Whether to include packages from Artix system repo in the Results view.
720    pub results_filter_show_artix_system: bool,
721    /// Whether to include packages from the `blackarch` repo in the Results view.
722    pub results_filter_show_blackarch: bool,
723    /// Whether to include packages labeled as `manjaro` in the Results view.
724    pub results_filter_show_manjaro: bool,
725    /// Lowercase pacman `[repo]` name → canonical `results_filter` id from `repos.conf`.
726    pub repo_results_filter_by_name: HashMap<String, String>,
727    /// Per dynamic filter id (canonical), whether search results include packages from mapped repos.
728    pub results_filter_dynamic: HashMap<String, bool>,
729    /// Clickable rectangle for the AUR filter toggle in the Results title (x, y, w, h).
730    pub results_filter_aur_rect: Option<(u16, u16, u16, u16)>,
731    /// Clickable rectangle for the core filter toggle in the Results title (x, y, w, h).
732    pub results_filter_core_rect: Option<(u16, u16, u16, u16)>,
733    /// Clickable rectangle for the extra filter toggle in the Results title (x, y, w, h).
734    pub results_filter_extra_rect: Option<(u16, u16, u16, u16)>,
735    /// Clickable rectangle for the multilib filter toggle in the Results title (x, y, w, h).
736    pub results_filter_multilib_rect: Option<(u16, u16, u16, u16)>,
737    /// Clickable rectangle for the EOS filter toggle in the Results title (x, y, w, h).
738    pub results_filter_eos_rect: Option<(u16, u16, u16, u16)>,
739    /// Clickable rectangle for the `CachyOS` filter toggle in the Results title (x, y, w, h).
740    pub results_filter_cachyos_rect: Option<(u16, u16, u16, u16)>,
741    /// Clickable rectangle for the Artix filter toggle in the Results title (x, y, w, h).
742    pub results_filter_artix_rect: Option<(u16, u16, u16, u16)>,
743    /// Clickable rectangle for the Artix omniverse filter toggle in the Results title (x, y, w, h).
744    pub results_filter_artix_omniverse_rect: Option<(u16, u16, u16, u16)>,
745    /// Clickable rectangle for the Artix universe filter toggle in the Results title (x, y, w, h).
746    pub results_filter_artix_universe_rect: Option<(u16, u16, u16, u16)>,
747    /// Clickable rectangle for the Artix lib32 filter toggle in the Results title (x, y, w, h).
748    pub results_filter_artix_lib32_rect: Option<(u16, u16, u16, u16)>,
749    /// Clickable rectangle for the Artix galaxy filter toggle in the Results title (x, y, w, h).
750    pub results_filter_artix_galaxy_rect: Option<(u16, u16, u16, u16)>,
751    /// Clickable rectangle for the Artix world filter toggle in the Results title (x, y, w, h).
752    pub results_filter_artix_world_rect: Option<(u16, u16, u16, u16)>,
753    /// Clickable rectangle for the Artix system filter toggle in the Results title (x, y, w, h).
754    pub results_filter_artix_system_rect: Option<(u16, u16, u16, u16)>,
755    /// Clickable rectangle for the `BlackArch` filter toggle in the Results title (x, y, w, h).
756    pub results_filter_blackarch_rect: Option<(u16, u16, u16, u16)>,
757    /// Clickable rectangle for the Manjaro filter toggle in the Results title (x, y, w, h).
758    pub results_filter_manjaro_rect: Option<(u16, u16, u16, u16)>,
759    /// Clickable rectangle for the custom `repos.conf` filter dropdown chip (x, y, w, h).
760    pub results_filter_custom_repos_rect: Option<(u16, u16, u16, u16)>,
761    /// Clickable rectangle for the fuzzy search mode indicator in the Search title (x, y, w, h).
762    pub fuzzy_indicator_rect: Option<(u16, u16, u16, u16)>,
763
764    // Background refresh of installed/explicit caches after package mutations
765    /// If `Some`, keep polling pacman/yay to refresh installed/explicit caches until this time.
766    pub refresh_installed_until: Option<Instant>,
767    /// Next scheduled time to poll caches while `refresh_installed_until` is active.
768    pub next_installed_refresh_at: Option<Instant>,
769
770    // Pending installs to detect completion and clear Install list
771    /// Names of packages we just triggered to install; when all appear installed, clear Install list.
772    pub pending_install_names: Option<Vec<String>>,
773
774    // Pending removals to detect completion and log
775    /// Names of packages we just triggered to remove; when all disappear, append to removed log.
776    pub pending_remove_names: Option<Vec<String>>,
777
778    // Dependency resolution cache for install list
779    /// Cached resolved dependencies for the current install list (updated in background).
780    pub install_list_deps: Vec<crate::state::modal::DependencyInfo>,
781    /// Reverse dependency summary for the current remove preflight modal (populated on demand).
782    pub remove_preflight_summary: Vec<crate::state::modal::ReverseRootSummary>,
783    /// Selected cascade removal mode for upcoming removals.
784    pub remove_cascade_mode: CascadeMode,
785    /// Whether dependency resolution is currently in progress.
786    pub deps_resolving: bool,
787    /// Path where the dependency cache is persisted as JSON.
788    pub deps_cache_path: PathBuf,
789    /// Dirty flag indicating `install_list_deps` needs to be saved.
790    pub deps_cache_dirty: bool,
791
792    // File resolution cache for install list
793    /// Cached resolved file changes for the current install list (updated in background).
794    pub install_list_files: Vec<crate::state::modal::PackageFileInfo>,
795    /// Whether file resolution is currently in progress.
796    pub files_resolving: bool,
797    /// Path where the file cache is persisted as JSON.
798    pub files_cache_path: PathBuf,
799    /// Dirty flag indicating `install_list_files` needs to be saved.
800    pub files_cache_dirty: bool,
801
802    // Service impact cache for install list
803    /// Cached resolved service impacts for the current install list (updated in background).
804    pub install_list_services: Vec<crate::state::modal::ServiceImpact>,
805    /// Whether service impact resolution is currently in progress.
806    pub services_resolving: bool,
807    /// Path where the service cache is persisted as JSON.
808    pub services_cache_path: PathBuf,
809    /// Dirty flag indicating `install_list_services` needs to be saved.
810    pub services_cache_dirty: bool,
811    /// Flag requesting that the runtime schedule service impact resolution for the active Preflight modal.
812    pub service_resolve_now: bool,
813    /// Identifier of the active service impact resolution request, if any.
814    pub active_service_request: Option<u64>,
815    /// Monotonic counter used to tag service impact resolution requests.
816    pub next_service_request_id: u64,
817    /// Signature of the package set currently queued for service impact resolution.
818    pub services_pending_signature: Option<(PreflightAction, Vec<String>)>,
819    /// Service restart decisions captured during the Preflight Services tab.
820    pub pending_service_plan: Vec<ServiceImpact>,
821
822    // Sandbox analysis cache for install list
823    /// Cached resolved sandbox information for the current install list (updated in background).
824    pub install_list_sandbox: Vec<crate::logic::sandbox::SandboxInfo>,
825    /// Whether sandbox resolution is currently in progress.
826    pub sandbox_resolving: bool,
827    /// Path where the sandbox cache is persisted as JSON.
828    pub sandbox_cache_path: PathBuf,
829    /// Dirty flag indicating `install_list_sandbox` needs to be saved.
830    pub sandbox_cache_dirty: bool,
831
832    // Preflight modal background resolution requests
833    /// Packages to resolve for preflight summary computation.
834    pub preflight_summary_items: Option<(Vec<PackageItem>, crate::state::modal::PreflightAction)>,
835    /// Packages to resolve for preflight dependency analysis (with action for forward/reverse).
836    pub preflight_deps_items: Option<(Vec<PackageItem>, crate::state::modal::PreflightAction)>,
837    /// Packages to resolve for preflight file analysis.
838    pub preflight_files_items: Option<Vec<PackageItem>>,
839    /// Packages to resolve for preflight service analysis.
840    pub preflight_services_items: Option<Vec<PackageItem>>,
841    /// AUR packages to resolve for preflight sandbox analysis (subset only).
842    pub preflight_sandbox_items: Option<Vec<PackageItem>>,
843    /// Whether preflight summary computation is in progress.
844    pub preflight_summary_resolving: bool,
845    /// Whether preflight dependency resolution is in progress.
846    pub preflight_deps_resolving: bool,
847    /// Whether preflight file resolution is in progress.
848    pub preflight_files_resolving: bool,
849    /// Whether preflight service resolution is in progress.
850    pub preflight_services_resolving: bool,
851    /// Whether preflight sandbox resolution is in progress.
852    pub preflight_sandbox_resolving: bool,
853    /// Last preflight dependency log state to suppress duplicate tick logs.
854    pub last_logged_preflight_deps_state: Option<(usize, bool, bool)>,
855    /// Cancellation flag for preflight operations (set to true when modal closes).
856    pub preflight_cancelled: std::sync::Arc<std::sync::atomic::AtomicBool>,
857
858    // Executor integration
859    /// Pending AUR vote intent (pkgbase and action) awaiting user confirmation.
860    pub pending_aur_vote_intent: Option<(String, VoteAction)>,
861    /// Pending AUR vote request (pkgbase and action) to be sent by the runtime tick handler.
862    pub pending_aur_vote_request: Option<(String, VoteAction)>,
863    /// Live AUR vote-state cache keyed by pkgbase/package name.
864    pub aur_vote_state_by_pkgbase: HashMap<String, AurVoteStateUi>,
865    /// Path where persisted AUR vote-state cache is stored as JSON.
866    pub aur_vote_state_path: PathBuf,
867    /// Dirty flag indicating `aur_vote_state_by_pkgbase` needs to be saved.
868    pub aur_vote_state_dirty: bool,
869    /// Whether live AUR vote-state lookup is available in current runtime session.
870    ///
871    /// Details:
872    /// - Set to `false` after first unsupported `list-votes` response to avoid repeatedly
873    ///   replacing stable cached states with transient `Loading`/`Unknown`.
874    pub aur_vote_state_lookup_supported: bool,
875    /// Pending AUR vote-state check request (pkgbase) to be sent by the runtime tick handler.
876    pub pending_aur_vote_state_request: Option<String>,
877    /// Pending executor request to be sent when `PreflightExec` modal is ready.
878    pub pending_executor_request: Option<crate::install::ExecutorRequest>,
879    /// Pending post-summary computation request (items and success flag to compute summary for).
880    pub pending_post_summary_items: Option<(Vec<PackageItem>, Option<bool>)>,
881    /// Header chips to use when transitioning to `PreflightExec` modal.
882    pub pending_exec_header_chips: Option<crate::state::modal::PreflightHeaderChips>,
883    /// Custom command to execute after password prompt (for special packages like paru/yay/semgrep-bin).
884    pub pending_custom_command: Option<String>,
885    /// Update commands to execute after password prompt (for system update).
886    pub pending_update_commands: Option<Vec<String>>,
887    /// Repo apply commands after password prompt (custom `repos.conf` apply).
888    pub pending_repo_apply_commands: Option<Vec<String>>,
889    /// Summary lines to seed `PreflightExec` when starting a repo apply.
890    pub pending_repo_apply_summary: Option<Vec<String>>,
891    /// Pending foreign∩sync overlap check after a successful full repository apply.
892    pub pending_repo_apply_overlap_check: Option<RepoOverlapApplyPending>,
893    /// Reopen the Repositories modal with a rescanned pacman view after repo apply completes.
894    pub pending_repositories_modal_resume: Option<RepositoriesModalResume>,
895    /// Privileged shell commands for foreign→sync migration (`PasswordPurpose::RepoForeignMigrate`).
896    pub pending_foreign_migrate_commands: Option<Vec<String>>,
897    /// Summary lines for foreign→sync migration preflight log.
898    pub pending_foreign_migrate_summary: Option<Vec<String>>,
899    /// Skips the next AUR-vs-repo duplicate-results warning (after user continues once).
900    pub skip_aur_repo_dup_warning_once: bool,
901    /// AUR update command to execute conditionally if pacman fails (for system update).
902    pub pending_aur_update_command: Option<String>,
903    /// Password obtained from password prompt, stored temporarily for reinstall confirmation flow.
904    pub pending_executor_password: Option<crate::state::SecureString>,
905    /// File database sync result from background thread (checked in tick handler).
906    pub pending_file_sync_result: Option<FileSyncResult>,
907    /// Background AUR SSH validation result handle for Optional Deps status refresh.
908    pub pending_aur_ssh_help_check_result: Option<std::sync::Arc<std::sync::Mutex<Option<bool>>>>,
909    /// Latest AUR SSH help validation result (`Some(true/false)`) from the background check.
910    pub aur_ssh_help_ready: Option<bool>,
911}