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
157
//! **The login pane between frames** (bl-e3c5; DESIGN §4.24) — what it is
//! asking about, and the three acts its controls spend.
//!
//! # Two subjects, and neither excludes the other
//!
//! `super::tuning` is an enum because its two states are one pane's two modes:
//! showing rows, or rewriting one assignment, and *editing while closed* is a
//! state that means nothing. This pane is not that shape. **A sign-in being
//! followed and a row that was asked what it offers are two independent facts
//! about two possibly different rows**, and every combination of them is a
//! window an operator really reaches — so [`Login`] is a struct of two options
//! rather than an enum, and the state that would mean nothing (a subject with
//! no pane) is unrepresentable because both live *inside* the pane's own
//! option.
//!
//! # What is NOT held here is any answer
//!
//! [`Model::providers`], [`Model::offered`] and [`Model::signin`] are the
//! engine's, filed on the model beside the roles for `super::tuning`'s reason
//! verbatim: a pane that wrote its own row back would be painting a claim the
//! engine had not made, and a sign-in is exactly the act whose outcome this end
//! cannot predict.
//!
//! # One read stands, one is posted, and one is held
//!
//! All three of DESIGN's cadences meet on this pane, which is why each is
//! stated. **`providers` STANDS while the pane is open** (§4.17's rule): a
//! credential lands on the engine while the operator is looking at the table,
//! so the row that said *no credential* has to say otherwise on the next beat
//! without anybody asking again. **`models` is POSTED** (§4.21's rule): what a
//! row offers is fixed for the life of the engine's own answer, and a standing
//! read would spend a round trip a beat forever on something that cannot change
//! under the operator. **`login-tail` is HELD** on its own thread
//! (`crate::offframe::signin`), because it is answered at the provider's pace.
use super::{Aim, Model};
/// The login pane, while it is open.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Login {
/// **Which row a sign-in was started on**, and therefore whose run the
/// lane is following — or `None` where the pane stands on the table alone.
///
/// It is the lane's whole subject (`crate::state::Standing::signin`), so
/// closing the pane ends the lane. It terminates nothing: the run is
/// engine-owned and bounded there (REMOTE §8.3), which is a property this
/// side gets for free and must not re-implement.
pub following: Option<String>,
/// **Which row was asked what it offers.** The question rather than the
/// answer — the answer is [`Model::offered`] — so the pane can say it is
/// waiting under the row that asked instead of under all of them.
pub asking: Option<String>,
}
impl Model {
/// **Open the login pane on the wall the window is aimed at**, or do
/// nothing where it is aimed at none.
///
/// The aim is the gate rather than a separate check, exactly as it is for
/// the tuning pane: every gesture this pane composes carries a workspace,
/// and a workspace is what an aim is.
pub fn begin_login(&mut self) {
if self.aim.is_some() {
self.login = Some(Login::default());
}
}
/// **Close it.** The answers stay, for the reason the roles do: the next
/// open on the same wall is about the same table, and the standing read
/// replaces it anyway. What does end is the lane — its subject is this
/// pane's `following`, and there is none once the pane is down.
pub fn close_login(&mut self) {
self.login = None;
}
/// **The row whose sign-in this seat is following**, if any. The lane's
/// subject and the pane's own question, asked once so the two cannot
/// disagree.
pub fn following(&self) -> Option<String> {
self.login.as_ref()?.following.clone()
}
/// **Start a sign-in on one row**, and follow it.
///
/// The fold is dropped in the same act: a second sign-in is a different
/// run — upstream terminates and replaces the first — so keeping the old
/// lines under the new row's name would paint one run's output as
/// another's. What replaces it is the act's own receipt, which upstream
/// answers at once with the run's standing.
pub fn post_signin(&mut self, provider: &str) {
let Some(pane) = self.login.as_mut() else {
return;
};
pane.following = Some(provider.to_owned());
self.signin = None;
self.aimed_act(&crate::verbs::login, provider);
}
/// **Ask one row what it offers**, dropping whatever the last row answered.
///
/// A read rather than an act ([`super::Posted::read`]): asking twice is
/// asking once, so a lost reply is re-asked by clicking again rather than
/// leaving anything in doubt.
pub fn post_offering(&mut self, provider: &str) {
let Some(pane) = self.login.as_mut() else {
return;
};
pane.asking = Some(provider.to_owned());
self.offered = None;
let Some(Aim { address, .. }) = self.aim.clone() else {
return;
};
self.outbox.push(super::Posted::read(crate::verbs::models(
address,
provider.to_owned(),
)));
}
/// **Compose one act against the aimed wall and one provider row**, or none
/// at all — the aim being the same gate [`begin_login`](Self::begin_login)
/// enforces, made unreachable here rather than merely unlikely.
///
/// `&dyn Fn` rather than a generic, for `super::tuning::Model::tune`'s
/// reason: a bound would stamp one copy of this body out per call site.
fn aimed_act(&mut self, gesture: &dyn Fn(String, String) -> serde_json::Value, provider: &str) {
let Some(Aim { address, .. }) = self.aim.clone() else {
return;
};
self.outbox
.push(super::Posted::act(gesture(address, provider.to_owned())));
}
/// **Whether the aimed wall is held on another box** — read off the
/// roster's own client-side channel stamp and never off an address, which
/// is what keeps a host out of the pane's sentences.
///
/// A channel with a name on its host is an entry (§8.2); this box's own
/// engine has none. A channel this seat no longer holds answers `false`,
/// which is the honest reading: there is nothing to say *elsewhere* about.
pub fn elsewhere(&self) -> bool {
self.channel()
.is_some_and(|channel| channel.named_there.is_some())
}
/// **The pane and its three answers go with the wall they are about** —
/// called by the act that moves the aim, so nothing on it outlives its
/// subject. It is `super::records::retire_records`'s rule one noun over.
pub(super) fn retire_login(&mut self) {
self.login = None;
self.providers = None;
self.offered = None;
self.signin = None;
}
}
#[cfg(test)]
mod tests;