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::{collections::HashMap, collections::HashSet, path::PathBuf, time::Instant};
6
7use crate::state::modal::{CascadeMode, Modal, PreflightAction, ServiceImpact};
8use crate::state::types::{
9    AppMode, ArchStatusColor, Focus, InstalledPackagesMode, NewsFeedItem, NewsReadFilter,
10    NewsSortMode, PackageDetails, PackageItem, RightPaneFocus, SortMode,
11};
12use crate::theme::KeyMap;
13
14mod constants;
15mod default_impl;
16mod defaults;
17mod defaults_cache;
18mod methods;
19
20#[cfg(test)]
21mod tests;
22
23pub use constants::{FileSyncResult, RECENT_CAPACITY, recent_capacity};
24
25/// Global application state shared by the event, networking, and UI layers.
26///
27/// This structure is mutated frequently in response to input and background
28/// updates. Certain subsets are persisted to disk to preserve user context
29/// across runs (e.g., recent searches, details cache, install list).
30#[derive(Debug)]
31#[allow(clippy::struct_excessive_bools)]
32pub struct AppState {
33    /// Current top-level mode (package management vs news feed).
34    pub app_mode: AppMode,
35    /// Current search input text.
36    pub input: String,
37    /// Current search results, most relevant first.
38    pub results: Vec<PackageItem>,
39    /// Unfiltered results as last received from the search worker.
40    pub all_results: Vec<PackageItem>,
41    /// Backup of results when toggling to installed-only view.
42    pub results_backup_for_toggle: Option<Vec<PackageItem>>,
43    /// Index into `results` that is currently highlighted.
44    pub selected: usize,
45    /// Details for the currently highlighted result.
46    pub details: PackageDetails,
47    /// List selection state for the search results list.
48    pub list_state: ListState,
49    /// Active modal dialog, if any.
50    pub modal: Modal,
51    /// Previous modal state (used to restore when closing help/alert modals).
52    pub previous_modal: Option<Modal>,
53    /// If `true`, show install steps without executing side effects.
54    pub dry_run: bool,
55    // Recent searches
56    /// Previously executed queries stored as an LRU cache (keyed case-insensitively).
57    pub recent: LruCache<String, String>,
58    /// List selection state for the Recent pane.
59    pub history_state: ListState,
60    /// Which pane is currently focused.
61    pub focus: Focus,
62    /// Timestamp of the last input edit, used for debouncing or throttling.
63    pub last_input_change: Instant,
64    /// Last value persisted for the input field, to avoid redundant writes.
65    pub last_saved_value: Option<String>,
66    // Persisted recent searches
67    /// Path where recent searches are persisted as JSON.
68    pub recent_path: PathBuf,
69    /// Dirty flag indicating `recent` needs to be saved.
70    pub recent_dirty: bool,
71
72    // Search coordination
73    /// Identifier of the latest query whose results are being displayed.
74    pub latest_query_id: u64,
75    /// Next query identifier to allocate.
76    pub next_query_id: u64,
77    // Search result cache
78    /// Cached search query text (None if cache is empty or invalid).
79    pub search_cache_query: Option<String>,
80    /// Whether fuzzy mode was used for cached query.
81    pub search_cache_fuzzy: bool,
82    /// Cached search results (None if cache is empty or invalid).
83    pub search_cache_results: Option<Vec<PackageItem>>,
84    // Details cache
85    /// Cache of details keyed by package name.
86    pub details_cache: HashMap<String, PackageDetails>,
87    /// Path where the details cache is persisted as JSON.
88    pub cache_path: PathBuf,
89    /// Dirty flag indicating `details_cache` needs to be saved.
90    pub cache_dirty: bool,
91
92    // News read/unread tracking (persisted)
93    /// Set of Arch news item URLs the user has marked as read.
94    pub news_read_urls: std::collections::HashSet<String>,
95    /// Path where the read news URLs are persisted as JSON.
96    pub news_read_path: PathBuf,
97    /// Dirty flag indicating `news_read_urls` needs to be saved.
98    pub news_read_dirty: bool,
99    /// Set of news feed item IDs the user has marked as read.
100    pub news_read_ids: std::collections::HashSet<String>,
101    /// Path where the read news IDs are persisted as JSON.
102    pub news_read_ids_path: PathBuf,
103    /// Dirty flag indicating `news_read_ids` needs to be saved.
104    pub news_read_ids_dirty: bool,
105    /// News feed items currently loaded.
106    pub news_items: Vec<NewsFeedItem>,
107    /// Filtered/sorted news results shown in the UI.
108    pub news_results: Vec<NewsFeedItem>,
109    /// Whether the news feed is currently loading.
110    pub news_loading: bool,
111    /// Whether news are ready to be viewed (loading complete and news available).
112    pub news_ready: bool,
113    /// Selected index within news results.
114    pub news_selected: usize,
115    /// List state for news results pane.
116    pub news_list_state: ListState,
117    /// News search input text.
118    pub news_search_input: String,
119    /// Caret position within news search input.
120    pub news_search_caret: usize,
121    /// Selection anchor within news search input.
122    pub news_search_select_anchor: Option<usize>,
123    /// LRU cache of recent news searches (case-insensitive key).
124    pub news_recent: LruCache<String, String>,
125    /// Path where news recent searches are persisted.
126    pub news_recent_path: PathBuf,
127    /// Dirty flag indicating `news_recent` needs to be saved.
128    pub news_recent_dirty: bool,
129    /// Pending news search awaiting debounce before saving to history.
130    pub news_history_pending: Option<String>,
131    /// Timestamp when the pending news search was last updated.
132    pub news_history_pending_at: Option<std::time::Instant>,
133    /// Last news search saved to history (prevents duplicate saves).
134    pub news_history_last_saved: Option<String>,
135    /// Whether to show Arch news items.
136    pub news_filter_show_arch_news: bool,
137    /// Whether to show security advisories.
138    pub news_filter_show_advisories: bool,
139    /// Whether to show installed package update items.
140    pub news_filter_show_pkg_updates: bool,
141    /// Whether to show AUR package update items.
142    pub news_filter_show_aur_updates: bool,
143    /// Whether to show AUR comment items.
144    pub news_filter_show_aur_comments: bool,
145    /// Whether to restrict advisories to installed packages.
146    pub news_filter_installed_only: bool,
147    /// Read/unread filter for the News Feed list.
148    pub news_filter_read_status: NewsReadFilter,
149    /// Clickable rectangle for Arch news filter chip in news title.
150    pub news_filter_arch_rect: Option<(u16, u16, u16, u16)>,
151    /// Clickable rectangle for security advisory filter chip in news title.
152    pub news_filter_advisory_rect: Option<(u16, u16, u16, u16)>,
153    /// Clickable rectangle for installed-only advisory filter chip in news title.
154    pub news_filter_installed_rect: Option<(u16, u16, u16, u16)>,
155    /// Clickable rectangle for installed update filter chip in news title.
156    pub news_filter_updates_rect: Option<(u16, u16, u16, u16)>,
157    /// Clickable rectangle for AUR update filter chip in news title.
158    pub news_filter_aur_updates_rect: Option<(u16, u16, u16, u16)>,
159    /// Clickable rectangle for AUR comment filter chip in news title.
160    pub news_filter_aur_comments_rect: Option<(u16, u16, u16, u16)>,
161    /// Clickable rectangle for read/unread filter chip in news title.
162    pub news_filter_read_rect: Option<(u16, u16, u16, u16)>,
163    /// Maximum age of news items in days (None = unlimited).
164    pub news_max_age_days: Option<u32>,
165    /// Whether to show the news history pane in News mode.
166    pub show_news_history_pane: bool,
167    /// Whether to show the news bookmarks pane in News mode.
168    pub show_news_bookmarks_pane: bool,
169    /// Sort mode for news results.
170    pub news_sort_mode: NewsSortMode,
171    /// Saved news/bookmarked items with cached content.
172    pub news_bookmarks: Vec<crate::state::types::NewsBookmark>,
173    /// Path where news bookmarks are persisted.
174    pub news_bookmarks_path: PathBuf,
175    /// Dirty flag indicating `news_bookmarks` needs to be saved.
176    pub news_bookmarks_dirty: bool,
177    /// Cache of fetched news article content (URL -> content).
178    pub news_content_cache: std::collections::HashMap<String, String>,
179    /// Path where the news content cache is persisted.
180    pub news_content_cache_path: PathBuf,
181    /// Dirty flag indicating `news_content_cache` needs to be saved.
182    pub news_content_cache_dirty: bool,
183    /// Currently displayed news content (for the selected item).
184    pub news_content: Option<String>,
185    /// Whether news content is currently being fetched.
186    pub news_content_loading: bool,
187    /// When the current news content load started (for timeout/logging).
188    pub news_content_loading_since: Option<std::time::Instant>,
189    /// Debounce timer for news content requests - tracks when user selected current item.
190    /// Only requests content after 0.5 seconds of staying on the same item.
191    pub news_content_debounce_timer: Option<std::time::Instant>,
192    /// Scroll offset for news content details.
193    pub news_content_scroll: u16,
194    /// Path where the cached news feed is persisted.
195    pub news_feed_path: PathBuf,
196    /// Last-seen versions for installed packages (dedup for update feed items).
197    pub news_seen_pkg_versions: HashMap<String, String>,
198    /// Path where last-seen package versions are persisted.
199    pub news_seen_pkg_versions_path: PathBuf,
200    /// Dirty flag indicating `news_seen_pkg_versions` needs to be saved.
201    pub news_seen_pkg_versions_dirty: bool,
202    /// Last-seen AUR comment identifiers per installed package.
203    pub news_seen_aur_comments: HashMap<String, String>,
204    /// Path where last-seen AUR comments are persisted.
205    pub news_seen_aur_comments_path: PathBuf,
206    /// Dirty flag indicating `news_seen_aur_comments` needs to be saved.
207    pub news_seen_aur_comments_dirty: bool,
208
209    // Announcement read tracking (persisted)
210    /// Set of announcement IDs the user has marked as read.
211    /// Tracks both version strings (e.g., "v0.6.0") and remote announcement IDs.
212    pub announcements_read_ids: std::collections::HashSet<String>,
213    /// Path where the read announcement IDs are persisted as JSON.
214    pub announcement_read_path: PathBuf,
215    /// Dirty flag indicating `announcements_read_ids` needs to be saved.
216    pub announcement_dirty: bool,
217
218    // Last startup tracking (for incremental updates)
219    /// Timestamp of the previous TUI startup (format: `YYYYMMDD:HHMMSS`).
220    /// Used to determine what news/updates need fresh fetching vs cached data.
221    pub last_startup_timestamp: Option<String>,
222    /// Path where the last startup timestamp is persisted.
223    pub last_startup_path: PathBuf,
224
225    // Install list pane
226    /// Packages selected for installation.
227    pub install_list: Vec<PackageItem>,
228    /// List selection state for the Install pane.
229    pub install_state: ListState,
230    /// Separate list of packages selected for removal (active in installed-only mode).
231    pub remove_list: Vec<PackageItem>,
232    /// List selection state for the Remove pane.
233    pub remove_state: ListState,
234    /// Separate list of packages selected for downgrade (shown in installed-only mode).
235    pub downgrade_list: Vec<PackageItem>,
236    /// List selection state for the Downgrade pane.
237    pub downgrade_state: ListState,
238    // Persisted install list
239    /// Path where the install list is persisted as JSON.
240    pub install_path: PathBuf,
241    /// Dirty flag indicating `install_list` needs to be saved.
242    pub install_dirty: bool,
243    /// Timestamp of the most recent change to the install list for throttling disk writes.
244    pub last_install_change: Option<Instant>,
245    /// `HashSet` of package names in install list for O(1) membership checking.
246    pub install_list_names: HashSet<String>,
247    /// `HashSet` of package names in remove list for O(1) membership checking.
248    pub remove_list_names: HashSet<String>,
249    /// `HashSet` of package names in downgrade list for O(1) membership checking.
250    pub downgrade_list_names: HashSet<String>,
251
252    // Visibility toggles for middle row panes
253    /// Whether the Recent pane is visible in the middle row.
254    pub show_recent_pane: bool,
255    /// Whether the Install/Remove pane is visible in the middle row.
256    pub show_install_pane: bool,
257    /// Whether to show the keybindings footer in the details pane.
258    pub show_keybinds_footer: bool,
259
260    // In-pane search (for Recent/Install panes)
261    /// Optional, transient find pattern used by pane-local search ("/").
262    pub pane_find: Option<String>,
263
264    /// Whether Search pane is in Normal mode (Vim-like navigation) instead of Insert mode.
265    pub search_normal_mode: bool,
266
267    /// Whether fuzzy search is enabled (fzf-style matching) instead of normal substring search.
268    pub fuzzy_search_enabled: bool,
269
270    /// Caret position (in characters) within the `Search` input.
271    /// Always clamped to the range 0..=`input.chars().count()`.
272    pub search_caret: usize,
273    /// Selection anchor (in characters) for the Search input when selecting text.
274    /// When `None`, no selection is active. When `Some(i)`, the selected range is
275    /// between `min(i, search_caret)` and `max(i, search_caret)` (exclusive upper bound).
276    pub search_select_anchor: Option<usize>,
277
278    // Official package index persistence
279    /// Path to the persisted official package index used for fast offline lookups.
280    pub official_index_path: PathBuf,
281
282    // Loading indicator for official index generation
283    /// Whether the application is currently generating the official index.
284    pub loading_index: bool,
285
286    // Track which package's details the UI is focused on
287    /// Name of the package whose details are being emphasized in the UI, if any.
288    pub details_focus: Option<String>,
289
290    // Ring prefetch debounce state
291    /// Smooth scrolling accumulator for prefetch heuristics.
292    pub scroll_moves: u32,
293    /// Timestamp at which to resume ring prefetching, if paused.
294    pub ring_resume_at: Option<Instant>,
295    /// Whether a ring prefetch is needed soon.
296    pub need_ring_prefetch: bool,
297
298    // Clickable URL button rectangle (x, y, w, h) in terminal cells
299    /// Rectangle of the clickable URL button in terminal cell coordinates.
300    pub url_button_rect: Option<(u16, u16, u16, u16)>,
301
302    // VirusTotal API setup modal clickable URL rectangle
303    /// Rectangle of the clickable `VirusTotal` API URL in the setup modal (x, y, w, h).
304    pub vt_url_rect: Option<(u16, u16, u16, u16)>,
305
306    // Install pane bottom action (Import)
307    /// Clickable rectangle for the Install pane bottom "Import" button (x, y, w, h).
308    pub install_import_rect: Option<(u16, u16, u16, u16)>,
309    /// Clickable rectangle for the Install pane bottom "Export" button (x, y, w, h).
310    pub install_export_rect: Option<(u16, u16, u16, u16)>,
311
312    // Arch status label (middle row footer)
313    /// Latest fetched status message from `status.archlinux.org`.
314    pub arch_status_text: String,
315    /// Clickable rectangle for the status label (x, y, w, h).
316    pub arch_status_rect: Option<(u16, u16, u16, u16)>,
317    /// Optional status color indicator (e.g., operational vs. current incident).
318    pub arch_status_color: ArchStatusColor,
319
320    // Package updates available
321    /// Number of available package updates, if checked.
322    pub updates_count: Option<usize>,
323    /// Sorted list of package names with available updates.
324    pub updates_list: Vec<String>,
325    /// Clickable rectangle for the updates button (x, y, w, h).
326    pub updates_button_rect: Option<(u16, u16, u16, u16)>,
327    /// Clickable rectangle for the news button in News mode (x, y, w, h).
328    pub news_button_rect: Option<(u16, u16, u16, u16)>,
329    /// Whether updates check is currently in progress.
330    pub updates_loading: bool,
331    /// Flag to trigger refresh of updates list after package installation/update.
332    pub refresh_updates: bool,
333    /// Flag to indicate that Updates modal should open after refresh completes.
334    pub pending_updates_modal: bool,
335
336    // Faillock lockout status
337    /// Whether the user account is currently locked out.
338    pub faillock_locked: bool,
339    /// Timestamp when the lockout will expire (if locked).
340    pub faillock_lockout_until: Option<std::time::SystemTime>,
341    /// Remaining lockout time in minutes (if locked).
342    pub faillock_remaining_minutes: Option<u32>,
343
344    // Clickable PKGBUILD button rectangle and viewer state
345    /// Rectangle of the clickable "Show PKGBUILD" in terminal cell coordinates.
346    pub pkgb_button_rect: Option<(u16, u16, u16, u16)>,
347    /// Rectangle of the clickable "Copy PKGBUILD" button in PKGBUILD title.
348    pub pkgb_check_button_rect: Option<(u16, u16, u16, u16)>,
349    /// Rectangle of the clickable "Reload PKGBUILD" button in PKGBUILD title.
350    pub pkgb_reload_button_rect: Option<(u16, u16, u16, u16)>,
351    /// Whether the PKGBUILD viewer is visible (details pane split in half).
352    pub pkgb_visible: bool,
353    /// The fetched PKGBUILD text when available.
354    pub pkgb_text: Option<String>,
355    /// Name of the package that the PKGBUILD is currently for.
356    pub pkgb_package_name: Option<String>,
357    /// Timestamp when PKGBUILD reload was last requested (for debouncing).
358    pub pkgb_reload_requested_at: Option<Instant>,
359    /// Name of the package for which PKGBUILD reload was requested (for debouncing).
360    pub pkgb_reload_requested_for: Option<String>,
361    /// Scroll offset (lines) for the PKGBUILD viewer.
362    pub pkgb_scroll: u16,
363    /// Content rectangle of the PKGBUILD viewer (x, y, w, h) when visible.
364    pub pkgb_rect: Option<(u16, u16, u16, u16)>,
365
366    // AUR comments viewer state
367    /// Rectangle of the clickable "Show comments" / "Hide comments" button in terminal cell coordinates.
368    pub comments_button_rect: Option<(u16, u16, u16, u16)>,
369    /// Whether the comments viewer is visible (details pane split).
370    pub comments_visible: bool,
371    /// The fetched comments data when available.
372    pub comments: Vec<crate::state::types::AurComment>,
373    /// Name of the package that the comments are currently for.
374    pub comments_package_name: Option<String>,
375    /// Timestamp when comments were last fetched (for cache invalidation).
376    pub comments_fetched_at: Option<Instant>,
377    /// Scroll offset (lines) for the comments viewer.
378    pub comments_scroll: u16,
379    /// Content rectangle of the comments viewer (x, y, w, h) when visible.
380    pub comments_rect: Option<(u16, u16, u16, u16)>,
381    /// Whether comments are currently being fetched.
382    pub comments_loading: bool,
383    /// Error message if comments fetch failed.
384    pub comments_error: Option<String>,
385    /// URLs in comments with their screen positions for click detection.
386    /// Vector of (`x`, `y`, `width`, `url_string`) tuples.
387    pub comments_urls: Vec<(u16, u16, u16, String)>,
388    /// Author names in comments with their screen positions for click detection.
389    /// Vector of (`x`, `y`, `width`, `username`) tuples.
390    pub comments_authors: Vec<(u16, u16, u16, String)>,
391    /// Dates in comments with their screen positions and URLs for click detection.
392    /// Vector of (`x`, `y`, `width`, `url_string`) tuples.
393    pub comments_dates: Vec<(u16, u16, u16, String)>,
394
395    // Transient toast message (bottom-right)
396    /// Optional short-lived info message rendered at the bottom-right corner.
397    pub toast_message: Option<String>,
398    /// Deadline (Instant) after which the toast is automatically hidden.
399    pub toast_expires_at: Option<Instant>,
400
401    // User settings loaded at startup
402    /// Left pane width percentage.
403    pub layout_left_pct: u16,
404    /// Center pane width percentage.
405    pub layout_center_pct: u16,
406    /// Right pane width percentage.
407    pub layout_right_pct: u16,
408    /// Resolved key bindings from user settings
409    pub keymap: KeyMap,
410    // Internationalization (i18n)
411    /// Resolved locale code (e.g., "de-DE", "en-US")
412    pub locale: String,
413    /// Translation map for the current locale
414    pub translations: crate::i18n::translations::TranslationMap,
415    /// Fallback translation map (English) for missing keys
416    pub translations_fallback: crate::i18n::translations::TranslationMap,
417
418    // Mouse hit-test rectangles for panes
419    /// Inner content rectangle of the Results list (x, y, w, h).
420    pub results_rect: Option<(u16, u16, u16, u16)>,
421    /// Inner content rectangle of the Package Info details pane (x, y, w, h).
422    pub details_rect: Option<(u16, u16, u16, u16)>,
423    /// Scroll offset (lines) for the Package Info details pane.
424    pub details_scroll: u16,
425    /// Inner content rectangle of the Recent pane list (x, y, w, h).
426    pub recent_rect: Option<(u16, u16, u16, u16)>,
427    /// Inner content rectangle of the Install pane list (x, y, w, h).
428    pub install_rect: Option<(u16, u16, u16, u16)>,
429    /// Inner content rectangle of the Downgrade subpane when visible.
430    pub downgrade_rect: Option<(u16, u16, u16, u16)>,
431    /// Whether mouse capture is temporarily disabled to allow text selection in details.
432    pub mouse_disabled_in_details: bool,
433    /// Last observed mouse position (column, row) in terminal cells.
434    pub last_mouse_pos: Option<(u16, u16)>,
435    /// Whether global terminal mouse capture is currently enabled.
436    pub mouse_capture_enabled: bool,
437
438    // News modal mouse hit-testing
439    /// Outer rectangle of the News modal (including borders) when visible.
440    pub news_rect: Option<(u16, u16, u16, u16)>,
441    /// Inner list rectangle for clickable news rows.
442    pub news_list_rect: Option<(u16, u16, u16, u16)>,
443
444    // Announcement modal mouse hit-testing
445    /// Outer rectangle of the Announcement modal (including borders) when visible.
446    pub announcement_rect: Option<(u16, u16, u16, u16)>,
447    /// URLs in announcement content with their screen positions for click detection.
448    /// Vector of (`x`, `y`, `width`, `url_string`) tuples.
449    pub announcement_urls: Vec<(u16, u16, u16, String)>,
450    /// Pending remote announcements to show after current announcement is dismissed.
451    pub pending_announcements: Vec<crate::announcements::RemoteAnnouncement>,
452    /// Pending news to show after all announcements are dismissed.
453    pub pending_news: Option<Vec<crate::state::NewsItem>>,
454    /// Flag to trigger startup news fetch after `NewsSetup` is completed.
455    pub trigger_startup_news_fetch: bool,
456
457    // Updates modal mouse hit-testing
458    /// Outer rectangle of the Updates modal (including borders) when visible.
459    pub updates_modal_rect: Option<(u16, u16, u16, u16)>,
460    /// Inner content rectangle for scrollable updates list.
461    pub updates_modal_content_rect: Option<(u16, u16, u16, u16)>,
462
463    // Help modal scroll and hit-testing
464    /// Scroll offset (lines) for the Help modal content.
465    pub help_scroll: u16,
466    /// Inner content rectangle of the Help modal (x, y, w, h) for hit-testing.
467    pub help_rect: Option<(u16, u16, u16, u16)>,
468
469    // Preflight modal mouse hit-testing
470    /// Clickable rectangles for preflight tabs (x, y, w, h) - Summary, Deps, Files, Services, Sandbox.
471    pub preflight_tab_rects: [Option<(u16, u16, u16, u16)>; 5],
472    /// Inner content rectangle of the preflight modal (x, y, w, h) for hit-testing package groups.
473    pub preflight_content_rect: Option<(u16, u16, u16, u16)>,
474
475    // Results sorting UI
476    /// Current sort mode for results.
477    pub sort_mode: SortMode,
478    /// Filter mode for installed packages (leaf only vs all explicit).
479    pub installed_packages_mode: InstalledPackagesMode,
480    /// Whether the sort dropdown is currently visible.
481    pub sort_menu_open: bool,
482    /// Clickable rectangle for the sort button in the Results title (x, y, w, h).
483    pub sort_button_rect: Option<(u16, u16, u16, u16)>,
484    /// Clickable rectangle for the news age toggle button (x, y, w, h).
485    pub news_age_button_rect: Option<(u16, u16, u16, u16)>,
486    /// Inner content rectangle of the sort dropdown menu when visible (x, y, w, h).
487    pub sort_menu_rect: Option<(u16, u16, u16, u16)>,
488    /// Deadline after which the sort dropdown auto-closes.
489    pub sort_menu_auto_close_at: Option<Instant>,
490    // Sort result caching for O(1) sort mode switching
491    /// Cached sort order for `RepoThenName` mode (indices into `results`).
492    pub sort_cache_repo_name: Option<Vec<usize>>,
493    /// Cached sort order for `AurPopularityThenOfficial` mode (indices into `results`).
494    pub sort_cache_aur_popularity: Option<Vec<usize>>,
495    /// Signature of results used to validate caches (order-insensitive hash of names).
496    pub sort_cache_signature: Option<u64>,
497
498    // Results options UI (top-right dropdown)
499    /// Whether the options dropdown is currently visible.
500    pub options_menu_open: bool,
501    /// Clickable rectangle for the options button in the Results title (x, y, w, h).
502    pub options_button_rect: Option<(u16, u16, u16, u16)>,
503    /// Inner content rectangle of the options dropdown menu when visible (x, y, w, h).
504    pub options_menu_rect: Option<(u16, u16, u16, u16)>,
505
506    // Panels dropdown UI (left of Options)
507    /// Whether the panels dropdown is currently visible.
508    pub panels_menu_open: bool,
509    /// Clickable rectangle for the panels button in the Results title (x, y, w, h).
510    pub panels_button_rect: Option<(u16, u16, u16, u16)>,
511    /// Inner content rectangle of the panels dropdown menu when visible (x, y, w, h).
512    pub panels_menu_rect: Option<(u16, u16, u16, u16)>,
513
514    // Config/Lists dropdown UI (left of Panels)
515    /// Whether the Config/Lists dropdown is currently visible.
516    pub config_menu_open: bool,
517    /// Clickable rectangle for the Config/Lists button in the Results title (x, y, w, h).
518    pub config_button_rect: Option<(u16, u16, u16, u16)>,
519    /// Inner content rectangle of the Config/Lists dropdown menu when visible (x, y, w, h).
520    pub config_menu_rect: Option<(u16, u16, u16, u16)>,
521
522    // Artix filter dropdown UI (when specific repo filters are hidden)
523    /// Whether the Artix filter dropdown is currently visible.
524    pub artix_filter_menu_open: bool,
525    /// Inner content rectangle of the Artix filter dropdown menu when visible (x, y, w, h).
526    pub artix_filter_menu_rect: Option<(u16, u16, u16, u16)>,
527
528    // Collapsed menu dropdown UI (when window is too narrow for all three buttons)
529    /// Whether the collapsed menu dropdown is currently visible.
530    pub collapsed_menu_open: bool,
531    /// Clickable rectangle for the collapsed menu button in the Results title (x, y, w, h).
532    pub collapsed_menu_button_rect: Option<(u16, u16, u16, u16)>,
533    /// Inner content rectangle of the collapsed menu dropdown when visible (x, y, w, h).
534    pub collapsed_menu_rect: Option<(u16, u16, u16, u16)>,
535
536    /// Whether Results is currently showing only explicitly installed packages.
537    pub installed_only_mode: bool,
538    /// Which right subpane is focused when installed-only mode splits the pane.
539    pub right_pane_focus: RightPaneFocus,
540    /// Visual marker style for packages added to lists (user preference cached at startup).
541    pub package_marker: crate::theme::PackageMarker,
542
543    // Results filters UI
544    /// Whether to include AUR packages in the Results view.
545    pub results_filter_show_aur: bool,
546    /// Whether to include packages from the `core` repo in the Results view.
547    pub results_filter_show_core: bool,
548    /// Whether to include packages from the `extra` repo in the Results view.
549    pub results_filter_show_extra: bool,
550    /// Whether to include packages from the `multilib` repo in the Results view.
551    pub results_filter_show_multilib: bool,
552    /// Whether to include packages from the `eos` repo in the Results view.
553    pub results_filter_show_eos: bool,
554    /// Whether to include packages from `cachyos*` repos in the Results view.
555    pub results_filter_show_cachyos: bool,
556    /// Whether to include packages from Artix Linux repos in the Results view.
557    pub results_filter_show_artix: bool,
558    /// Whether to include packages from Artix omniverse repo in the Results view.
559    pub results_filter_show_artix_omniverse: bool,
560    /// Whether to include packages from Artix universe repo in the Results view.
561    pub results_filter_show_artix_universe: bool,
562    /// Whether to include packages from Artix lib32 repo in the Results view.
563    pub results_filter_show_artix_lib32: bool,
564    /// Whether to include packages from Artix galaxy repo in the Results view.
565    pub results_filter_show_artix_galaxy: bool,
566    /// Whether to include packages from Artix world repo in the Results view.
567    pub results_filter_show_artix_world: bool,
568    /// Whether to include packages from Artix system repo in the Results view.
569    pub results_filter_show_artix_system: bool,
570    /// Whether to include packages labeled as `manjaro` in the Results view.
571    pub results_filter_show_manjaro: bool,
572    /// Clickable rectangle for the AUR filter toggle in the Results title (x, y, w, h).
573    pub results_filter_aur_rect: Option<(u16, u16, u16, u16)>,
574    /// Clickable rectangle for the core filter toggle in the Results title (x, y, w, h).
575    pub results_filter_core_rect: Option<(u16, u16, u16, u16)>,
576    /// Clickable rectangle for the extra filter toggle in the Results title (x, y, w, h).
577    pub results_filter_extra_rect: Option<(u16, u16, u16, u16)>,
578    /// Clickable rectangle for the multilib filter toggle in the Results title (x, y, w, h).
579    pub results_filter_multilib_rect: Option<(u16, u16, u16, u16)>,
580    /// Clickable rectangle for the EOS filter toggle in the Results title (x, y, w, h).
581    pub results_filter_eos_rect: Option<(u16, u16, u16, u16)>,
582    /// Clickable rectangle for the `CachyOS` filter toggle in the Results title (x, y, w, h).
583    pub results_filter_cachyos_rect: Option<(u16, u16, u16, u16)>,
584    /// Clickable rectangle for the Artix filter toggle in the Results title (x, y, w, h).
585    pub results_filter_artix_rect: Option<(u16, u16, u16, u16)>,
586    /// Clickable rectangle for the Artix omniverse filter toggle in the Results title (x, y, w, h).
587    pub results_filter_artix_omniverse_rect: Option<(u16, u16, u16, u16)>,
588    /// Clickable rectangle for the Artix universe filter toggle in the Results title (x, y, w, h).
589    pub results_filter_artix_universe_rect: Option<(u16, u16, u16, u16)>,
590    /// Clickable rectangle for the Artix lib32 filter toggle in the Results title (x, y, w, h).
591    pub results_filter_artix_lib32_rect: Option<(u16, u16, u16, u16)>,
592    /// Clickable rectangle for the Artix galaxy filter toggle in the Results title (x, y, w, h).
593    pub results_filter_artix_galaxy_rect: Option<(u16, u16, u16, u16)>,
594    /// Clickable rectangle for the Artix world filter toggle in the Results title (x, y, w, h).
595    pub results_filter_artix_world_rect: Option<(u16, u16, u16, u16)>,
596    /// Clickable rectangle for the Artix system filter toggle in the Results title (x, y, w, h).
597    pub results_filter_artix_system_rect: Option<(u16, u16, u16, u16)>,
598    /// Clickable rectangle for the Manjaro filter toggle in the Results title (x, y, w, h).
599    pub results_filter_manjaro_rect: Option<(u16, u16, u16, u16)>,
600    /// Clickable rectangle for the fuzzy search mode indicator in the Search title (x, y, w, h).
601    pub fuzzy_indicator_rect: Option<(u16, u16, u16, u16)>,
602
603    // Background refresh of installed/explicit caches after package mutations
604    /// If `Some`, keep polling pacman/yay to refresh installed/explicit caches until this time.
605    pub refresh_installed_until: Option<Instant>,
606    /// Next scheduled time to poll caches while `refresh_installed_until` is active.
607    pub next_installed_refresh_at: Option<Instant>,
608
609    // Pending installs to detect completion and clear Install list
610    /// Names of packages we just triggered to install; when all appear installed, clear Install list.
611    pub pending_install_names: Option<Vec<String>>,
612
613    // Pending removals to detect completion and log
614    /// Names of packages we just triggered to remove; when all disappear, append to removed log.
615    pub pending_remove_names: Option<Vec<String>>,
616
617    // Dependency resolution cache for install list
618    /// Cached resolved dependencies for the current install list (updated in background).
619    pub install_list_deps: Vec<crate::state::modal::DependencyInfo>,
620    /// Reverse dependency summary for the current remove preflight modal (populated on demand).
621    pub remove_preflight_summary: Vec<crate::state::modal::ReverseRootSummary>,
622    /// Selected cascade removal mode for upcoming removals.
623    pub remove_cascade_mode: CascadeMode,
624    /// Whether dependency resolution is currently in progress.
625    pub deps_resolving: bool,
626    /// Path where the dependency cache is persisted as JSON.
627    pub deps_cache_path: PathBuf,
628    /// Dirty flag indicating `install_list_deps` needs to be saved.
629    pub deps_cache_dirty: bool,
630
631    // File resolution cache for install list
632    /// Cached resolved file changes for the current install list (updated in background).
633    pub install_list_files: Vec<crate::state::modal::PackageFileInfo>,
634    /// Whether file resolution is currently in progress.
635    pub files_resolving: bool,
636    /// Path where the file cache is persisted as JSON.
637    pub files_cache_path: PathBuf,
638    /// Dirty flag indicating `install_list_files` needs to be saved.
639    pub files_cache_dirty: bool,
640
641    // Service impact cache for install list
642    /// Cached resolved service impacts for the current install list (updated in background).
643    pub install_list_services: Vec<crate::state::modal::ServiceImpact>,
644    /// Whether service impact resolution is currently in progress.
645    pub services_resolving: bool,
646    /// Path where the service cache is persisted as JSON.
647    pub services_cache_path: PathBuf,
648    /// Dirty flag indicating `install_list_services` needs to be saved.
649    pub services_cache_dirty: bool,
650    /// Flag requesting that the runtime schedule service impact resolution for the active Preflight modal.
651    pub service_resolve_now: bool,
652    /// Identifier of the active service impact resolution request, if any.
653    pub active_service_request: Option<u64>,
654    /// Monotonic counter used to tag service impact resolution requests.
655    pub next_service_request_id: u64,
656    /// Signature of the package set currently queued for service impact resolution.
657    pub services_pending_signature: Option<(PreflightAction, Vec<String>)>,
658    /// Service restart decisions captured during the Preflight Services tab.
659    pub pending_service_plan: Vec<ServiceImpact>,
660
661    // Sandbox analysis cache for install list
662    /// Cached resolved sandbox information for the current install list (updated in background).
663    pub install_list_sandbox: Vec<crate::logic::sandbox::SandboxInfo>,
664    /// Whether sandbox resolution is currently in progress.
665    pub sandbox_resolving: bool,
666    /// Path where the sandbox cache is persisted as JSON.
667    pub sandbox_cache_path: PathBuf,
668    /// Dirty flag indicating `install_list_sandbox` needs to be saved.
669    pub sandbox_cache_dirty: bool,
670
671    // Preflight modal background resolution requests
672    /// Packages to resolve for preflight summary computation.
673    pub preflight_summary_items: Option<(Vec<PackageItem>, crate::state::modal::PreflightAction)>,
674    /// Packages to resolve for preflight dependency analysis (with action for forward/reverse).
675    pub preflight_deps_items: Option<(Vec<PackageItem>, crate::state::modal::PreflightAction)>,
676    /// Packages to resolve for preflight file analysis.
677    pub preflight_files_items: Option<Vec<PackageItem>>,
678    /// Packages to resolve for preflight service analysis.
679    pub preflight_services_items: Option<Vec<PackageItem>>,
680    /// AUR packages to resolve for preflight sandbox analysis (subset only).
681    pub preflight_sandbox_items: Option<Vec<PackageItem>>,
682    /// Whether preflight summary computation is in progress.
683    pub preflight_summary_resolving: bool,
684    /// Whether preflight dependency resolution is in progress.
685    pub preflight_deps_resolving: bool,
686    /// Whether preflight file resolution is in progress.
687    pub preflight_files_resolving: bool,
688    /// Whether preflight service resolution is in progress.
689    pub preflight_services_resolving: bool,
690    /// Whether preflight sandbox resolution is in progress.
691    pub preflight_sandbox_resolving: bool,
692    /// Last preflight dependency log state to suppress duplicate tick logs.
693    pub last_logged_preflight_deps_state: Option<(usize, bool, bool)>,
694    /// Cancellation flag for preflight operations (set to true when modal closes).
695    pub preflight_cancelled: std::sync::Arc<std::sync::atomic::AtomicBool>,
696
697    // Executor integration
698    /// Pending executor request to be sent when `PreflightExec` modal is ready.
699    pub pending_executor_request: Option<crate::install::ExecutorRequest>,
700    /// Pending post-summary computation request (items and success flag to compute summary for).
701    pub pending_post_summary_items: Option<(Vec<PackageItem>, Option<bool>)>,
702    /// Header chips to use when transitioning to `PreflightExec` modal.
703    pub pending_exec_header_chips: Option<crate::state::modal::PreflightHeaderChips>,
704    /// Custom command to execute after password prompt (for special packages like paru/yay/semgrep-bin).
705    pub pending_custom_command: Option<String>,
706    /// Update commands to execute after password prompt (for system update).
707    pub pending_update_commands: Option<Vec<String>>,
708    /// AUR update command to execute conditionally if pacman fails (for system update).
709    pub pending_aur_update_command: Option<String>,
710    /// Password obtained from password prompt, stored temporarily for reinstall confirmation flow.
711    pub pending_executor_password: Option<String>,
712    /// File database sync result from background thread (checked in tick handler).
713    pub pending_file_sync_result: Option<FileSyncResult>,
714}