1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
//! **The records pane between frames** (bl-2cf7): open or not, and the acts
//! that open and close it.
//!
//! # A flag, because the pane has one state
//!
//! The tuning pane is a two-state enum because an assignment can be mid-edit;
//! this pane's own state is one draft and no mode — every row on it is the
//! engine's answer, filed on [`Model`] beside the roles whether or not the
//! pane is open, and the fork's two words are a bare field (`super::spine`).
//! So `records` is a `bool`, and a struct here would be a state machine with
//! one state.
//!
//! # It follows the tuning pane's whole shape, one noun over
//!
//! The subject is the SELECTED CONVERSATION where tuning's is the aimed wall,
//! and every consequence carries across: the pane opens only where there is a
//! subject; the two reads stand while it is open and only then
//! (`crate::state::Standing::records`), so what it paints is always the
//! engine's fact and never this end's prediction; and it closes when the
//! subject moves ([`Model::select`], [`Model::aim_at`]), because a pane about
//! *the selected conversation* left open over a new selection would paint one
//! conversation's records under another's name for a beat.
use super::{Listing, Model};
/// **What the records pane has been answered** — its seven reads, held
/// together.
///
/// One struct rather than seven fields on [`Model`], for `super::listing`'s
/// reason one layer over: they are one pane's questions about one subject, and
/// they are retired together the moment that subject moves
/// ([`Model::retire_records`]). Seven options spread across the model would be
/// seven places to remember, and forgetting one paints a conversation's
/// records under another conversation's name.
///
/// Every one is `None` until an answer lands, which is the reading
/// [`Model::roles`]'s option carries: nobody has been answered is not the same
/// claim as a conversation whose loop did nothing.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Records {
/// The steps its loop has taken (§4.18).
pub steps: Option<crate::reply::steps::Steps>,
/// What its worktree holds (§4.18).
pub files: Option<crate::reply::files::Files>,
/// Its spine — every operable commit, and the cards off them (§4.29).
pub rail: Option<crate::reply::rail::Rail>,
/// The config commit governing it (§4.29).
pub governing: Option<crate::reply::governing::Governing>,
/// Its own row, whole — the pane's header (§4.32).
pub agent: Option<crate::reply::agent::Agent>,
/// Its undelivered mail (§4.32).
pub mail: Option<Vec<crate::reply::inbox::Row>>,
/// **One step's records**, and the one of the seven that is POSTED rather
/// than standing: it is asked about a row, by the control on that row.
/// Which step it is about is the answer's own `seq`, so nothing here is a
/// second name for it (`super::deep`).
pub drilled: Option<crate::reply::step::Step>,
}
impl Model {
/// **Whether a pane covers the conversation** — the enrollment, the
/// tuning pane, one of the three listings (`super::listing`), the window's
/// own FOUR, the login pane, the config pane, the fleet pane, or an
/// unmaking. The question
/// the shell and every pane-opening control share, asked once so thirteen
/// panes cannot stand on one glass: a control that opened a second cover
/// would replace what is standing without saying so.
pub fn covered(&self) -> bool {
self.enroll.is_some()
|| self.tuning.is_some()
|| self.listing.is_some()
|| self.lookup.is_some()
|| self.login.is_some()
|| self.configuring.is_some()
|| self.fleet.is_some()
|| self.unmaking.is_some()
}
/// **Open the records pane on the selected conversation**, or do nothing
/// where none is selected. The selection is the gate exactly as the aim is
/// tuning's: the two reads it stands up are addressed, and a selected
/// conversation is what an address is.
pub fn begin_records(&mut self) {
if self.conversation.is_some() {
self.stand(Listing::Records);
}
}
/// **Close it.** The answers stay, for the same reason the roles do: the
/// next open on the same conversation is about the same records, and the
/// standing read replaces them anyway.
pub fn close_records(&mut self) {
self.put_down(Listing::Records);
}
/// **The records go with the conversation they answer** — called by the
/// two acts that move the subject, so the pane and its rows never outlive
/// what they are about.
pub(super) fn retire_records(&mut self) {
self.put_down(Listing::Records);
// **One assignment, because the answers are one value** ([`Records`]):
// a pane whose reads are retired field by field is a pane with a read
// somebody will forget.
self.records = Records::default();
// **The draft goes with them** (`super::spine`), which the answers do
// not need to say: a goal typed for one conversation is a sentence
// about that one, and a box left standing over a new selection would
// fire it at whatever is selected next.
self.forking = crate::ui::Forking::default();
// **And so does the arming** (`crate::ui::records::cascade`), for a
// sharper form of the same reason: a name typed to admit ONE
// conversation's subtree, left standing over the next selection, is an
// arming for a conversation nobody armed. It cannot fire at one — the
// control compares the box against the row it is painted beside — but
// a box that still reads the old name is a control that looks armed.
self.cascade = String::new();
}
/// **Stop the selected conversation and everything under it**, or do
/// nothing where nothing is aimed at, nothing is selected, or the box does
/// not hold the name.
///
/// The three gates are one reading rather than three arms of a control, on
/// `super::spine::Model::post_fork`'s own terms: every state they exclude
/// is one the pane cannot paint this control in, so they make the states
/// unreachable rather than merely unlikely.
///
/// **The arming is not spent** (DESIGN §4.20): the refusal is the common
/// answer here, and clearing the box would charge a retype for the
/// engine's *no*.
pub fn post_cascade(&mut self) {
let (Some(aim), Some(agent)) = (self.aim.clone(), self.conversation.clone()) else {
return;
};
let armed = self
.records
.agent
.as_ref()
.is_some_and(|row| crate::ui::records::cascade::armed(row, &self.cascade));
if !armed {
return;
}
self.outbox.push(super::Posted::act(crate::verbs::stop(
aim.address,
agent,
// **The boundary field, at its one site in this window.** litany
// bl-3114 made every engine stop take its children, so yog retires
// this field at its next PROTOCOL bump (yog bl-6efc) — and what
// the bump deletes here is this line.
true,
)));
}
}
#[cfg(test)]
mod tests;