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}