Skip to main content

orbok_ui/
state.rs

1//! Headless UI state (view models) and the message vocabulary.
2//!
3//! Everything here is plain data — testable without a display server.
4//! `orbok-app` populates these structs from backend services; views
5//! render them; `update` mutates them. No iced types appear in this
6//! module so state logic stays UI-framework-agnostic.
7
8use crate::i18n::Locale;
9use orbok_models::SearchCapability;
10
11/// Top-level pages (GUI external design §3.1 order).
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum ViewId {
14    Search,
15    Sources,
16    Indexing,
17    Storage,
18    Models,
19    Settings,
20}
21
22impl ViewId {
23    /// Sidebar order (Search first — search-first navigation, GUI
24    /// design §2.2).
25    pub const ALL: &'static [ViewId] = &[
26        ViewId::Search,
27        ViewId::Sources,
28        ViewId::Indexing,
29        ViewId::Storage,
30        ViewId::Models,
31        ViewId::Settings,
32    ];
33}
34
35/// Sidebar index-health summary (GUI design §5.2).
36#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
37pub struct IndexHealth {
38    pub indexed: u64,
39    pub stale: u64,
40    pub failed: u64,
41    pub queued: u64,
42}
43
44/// One source card (GUI design §8.1), pre-localized display fields
45/// excepted — status text is resolved at render time via i18n.
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct SourceCard {
48    pub display_name: String,
49    pub display_path: String,
50    pub indexed: u64,
51    pub stale: u64,
52    pub failed: u64,
53    pub active: bool,
54}
55
56/// The whole-app view model.
57#[derive(Debug, Clone)]
58pub struct AppState {
59    pub active_view: ViewId,
60    pub locale: Locale,
61    pub query: String,
62    pub last_query: Option<String>,
63    pub health: IndexHealth,
64    pub sources: Vec<SourceCard>,
65    pub capability: SearchCapability,
66    /// Total orbok storage in bytes (Storage view headline).
67    pub storage_total_bytes: u64,
68}
69
70impl Default for AppState {
71    fn default() -> Self {
72        Self {
73            active_view: ViewId::Search,
74            locale: Locale::default(),
75            query: String::new(),
76            last_query: None,
77            health: IndexHealth::default(),
78            sources: Vec::new(),
79            capability: SearchCapability::KeywordOnly,
80            storage_total_bytes: 0,
81        }
82    }
83}
84
85/// UI messages. Backend-effecting intents (scan, cleanup, search) are
86/// surfaced as messages here and executed by `orbok-app`'s update glue,
87/// keeping this crate free of side effects.
88#[derive(Debug, Clone)]
89pub enum Message {
90    Switch(ViewId),
91    QueryChanged(String),
92    SubmitSearch,
93    SetLocale(Locale),
94}
95
96impl AppState {
97    /// Pure state transition. Side-effect intents (e.g. running the
98    /// search) are handled by the embedding application after calling
99    /// this.
100    pub fn update(&mut self, message: &Message) {
101        match message {
102            Message::Switch(view) => self.active_view = *view,
103            Message::QueryChanged(query) => self.query = query.clone(),
104            Message::SubmitSearch => {
105                let trimmed = self.query.trim();
106                if !trimmed.is_empty() {
107                    self.last_query = Some(trimmed.to_string());
108                }
109            }
110            Message::SetLocale(locale) => self.locale = *locale,
111        }
112    }
113}