Skip to main content

yog/app/
view.rs

1//! The model's read surface — everything a caller may ask an [`AppModel`], and
2//! nothing that changes one.
3//!
4//! Split from `app/mod.rs` at the cap. The parent boots the model and takes the
5//! worker's published snapshots; this is what is read off one. Every query here
6//! is over [`AppModel::snap`] — a completed derivation the reader cannot change
7//! and never waits for (§7.2).
8//!
9//! **What left with the window** (bl-7942): the focus and every accessor over
10//! it. Which workspace and which conversation a seat is looking at is per-seat
11//! state (REMOTE §7), held by the seat and named in the gesture it sends; a
12//! server that kept a focus would be keeping one seat's view of itself.
13
14use super::AppModel;
15#[cfg(test)]
16use crate::binding::Workspace;
17use crate::git_tree::GitTree;
18use std::path::Path;
19
20impl AppModel {
21    /// **Take the newest completed derivation**, if the worker published one
22    /// since the last take. Returns whether the read source moved.
23    ///
24    /// It was `refresh` — one frame's whole model duty — and everything else it
25    /// did was the frame's (bl-7942): the optimistic echo's retirement, the
26    /// window's wire settle, the follow lane's, the act path's receipts, the §6
27    /// ack held while a conversation was on screen. Every one of those is a
28    /// seat's now, and what is left is the pointer swap.
29    pub fn take(&mut self) -> bool {
30        let latest = crate::state::latest_snapshot(&self.cell);
31        let landed = !std::sync::Arc::ptr_eq(&self.snap, &latest);
32        if landed {
33            self.snap = latest;
34            // Adopt an externally-changed `ui.json` the worker read for us
35            // (§4.1, I5): unless it is this instance's own echo (content-hash
36            // match), wholesale-adopt it — the converging seen/pins path every
37            // instance shares.
38            if let Some(bytes) = self.snap.ui_bytes.clone()
39                && !self.ui.is_echo(&bytes)
40            {
41                self.ui.adopt(&bytes);
42            }
43        }
44        landed
45    }
46
47    /// The published-snapshot cell this model reads (§7.2) — what the gesture
48    /// consumer answers queries from (§8.5), shared by `Arc`.
49    pub fn snapshot_cell(&self) -> crate::state::SnapshotCell {
50        std::sync::Arc::clone(&self.cell)
51    }
52
53    /// The durable `ui.json` path (§4.1) — the consumer's write-through copy
54    /// opens the same file this instance adopts external changes from.
55    pub fn ui_json_path(&self) -> std::path::PathBuf {
56        self.roots.ui_json()
57    }
58
59    /// Wall-clock unix seconds off this model's **injected** clock (§7.2) —
60    /// what a derivation dates its ages against, and the injected seam, so a
61    /// test advances it like anything else.
62    pub fn now_unix(&self) -> i64 {
63        self.clock.unix()
64    }
65
66    /// The clock's live periods off the published snapshot (bl-3381) — the
67    /// §7.2 rhythms everything else is spelled in terms of, so an operator who
68    /// re-tunes the sweeps re-tunes them all and no reader touches disk.
69    pub fn cadence(&self) -> super::Cadence {
70        self.snap.cadence
71    }
72
73    /// The classified workspace set — a test-only reader of the roster input
74    /// the boundary derives its listing from.
75    #[cfg(test)]
76    pub(crate) fn workspaces(&self) -> &[Workspace] {
77        &self.snap.workspaces
78    }
79
80    /// **The `Query::Workspaces` answer** (bl-296f) — a test-only reader over
81    /// the derivation the chokepoint answers from, which is the one thing
82    /// `boundary::answer::answer` does for that query. A seat asks it over the
83    /// wire; a test asks here, and the two cannot be two derivations because
84    /// this is that one call.
85    #[cfg(test)]
86    pub(crate) fn ws_listing(&self) -> crate::boundary::reply::Workspaces {
87        crate::boundary::answer::workspaces(&self.snap, &self.ui, self.now_unix())
88    }
89
90    /// The dirty hand-off to the worker (§7.2) — a test-only reader, so a test
91    /// can mark a root with a specific [`Mark`](crate::watch::Mark) rather than
92    /// the `Watch` every production caller means.
93    #[cfg(test)]
94    pub(crate) fn dirty_handle(&self) -> crate::state::DirtySet {
95        self.dirty.clone()
96    }
97
98    /// The current snapshot for `ws`, if derived.
99    pub fn tree(&self, ws: &Path) -> Option<&GitTree> {
100        self.snap.trees.get(ws)
101    }
102
103    /// **The one door from a §3.1 name to a path** (bl-7407): `name` resolved
104    /// against the enumeration, by the same [`by_leaf`](crate::naming::by_leaf)
105    /// rule the engine resolves a gesture's address by — a name nothing answers
106    /// resolves to nothing rather than to a guess.
107    pub fn workspace_path(&self, name: &str) -> Option<std::path::PathBuf> {
108        self.snap.ws_path(name).ok()
109    }
110
111    /// Per-workspace roster facts (§6 rollup): attention-bearing agent count,
112    /// total agent count, and whether any agent is running (Live/InFlight). An
113    /// unfetched workspace contributes zeros.
114    pub fn workspace_stats(&self, ws: &Path) -> (usize, usize, bool) {
115        crate::boundary::answer::workspace_stats(&self.snap, &self.ui, ws)
116    }
117
118    /// The in-process query chokepoint (§8.5): the same
119    /// [`answer`](crate::boundary::answer::answer) the deposit consumer runs,
120    /// over `deps` — one derivation, two serializations (VISION §4.8).
121    ///
122    /// **No production caller reaches it** (bl-ab32): every read a seat takes
123    /// crosses the wire and is answered by the intake. What is left is the
124    /// acceptance world asking the same call the intake makes, over the same
125    /// [`boundary_deps`](Self::boundary_deps) — which asks disk for its
126    /// addressable sets exactly as the intake does, so a story cannot resolve
127    /// a name against a set the engine would not.
128    pub fn answer(
129        &self,
130        deps: &crate::boundary::dispatch::Deps,
131        query: &crate::boundary::Query,
132        now_unix: i64,
133    ) -> Result<crate::boundary::reply::Reply, String> {
134        crate::boundary::answer::answer(query, deps, &self.ui, now_unix)
135    }
136}