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}