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