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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
//! **The canonical Elm contract** — the reusable scaffold behind FC-2 / FC-9.
//!
//! `facett-security` hand-rolled the pattern first (see its `lib.rs`): all state
//! in one serializable `Model`, every input a `Msg`, mutation only in
//! `update(&mut self, Msg) -> Vec<Effect>`, and a **pure** `render(&self) -> Vec<Msg>`
//! view that paints and *returns* the `Msg`s it produced — with a tiny `ui()`
//! bridge that applies them through `update`. This module lifts that bridge into
//! `facett-core` so the ~28 remaining components adopt it instead of re-deriving it.
//!
//! ## What you get
//! - [`Elm`] — the trait (associated `Model` / `Msg` / `Effect`; methods
//! [`state`](Elm::state) / [`update`](Elm::update) / [`view`](Elm::view) /
//! [`title`](Elm::title)). Implement this once per component.
//! - [`impl_facet_via_elm!`](crate::impl_facet_via_elm) — the bridge macro. It
//! writes the `impl Facet for YourView` for you (`title` + the pure-view `ui`
//! loop + a serde `state_json`). **A blanket `impl<T: Elm> Facet for T` is
//! impossible across crates (orphan rule) and would collide with the existing
//! hand-written `impl Facet` blocks mid-migration** — so each component crate
//! invokes this macro locally on its concrete type instead.
//! - [`crate::harness`] — a headless driver: apply a `Vec<Msg>`, snapshot
//! `state()`, no egui / no GPU. That turns FC-2 into a *tested* property.
//!
//! ## Adopting it in a component crate
//! ```ignore
//! use facett_core::Elm;
//!
//! impl Elm for MyView {
//! type Model = MyState; // Serialize + DeserializeOwned + Clone + PartialEq
//! type Msg = MyMsg; // Clone + Debug
//! type Effect = MyEffect; // often an uninhabited `enum MyEffect {}` (no I/O)
//! fn title(&self) -> &str { &self.title }
//! fn state(&self) -> &MyState { &self.state }
//! fn update(&mut self, msg: MyMsg) -> Vec<MyEffect> { /* the ONLY mutation path */ }
//! fn view(&self, ui: &mut egui::Ui) -> Vec<MyMsg> { /* pure: paint + return Msgs */ }
//! }
//!
//! // Writes `impl Facet for MyView` (title + view/update bridge + state_json):
//! facett_core::impl_facet_via_elm!(MyView);
//! ```
use Serialize;
use DeserializeOwned;
/// The canonical Model / Msg / Effect / pure-view split the component contract
/// (`.nornir/component-contract.md` §3) requires. Implement it once per component;
/// [`impl_facet_via_elm!`](crate::impl_facet_via_elm) turns it into a [`Facet`](crate::Facet).
///
/// The bounds encode the FCs directly:
/// - `Model: Serialize + DeserializeOwned + Clone + PartialEq` — FC-3 (readable,
/// round-trippable observable state).
/// - `Msg: Clone + Debug` — FC-2 (a writable, inspectable input surface).
/// - `Effect` — FC-8 side work *as data*; an uninhabited `enum Effect {}` is the
/// honest statement that a component does no I/O.
/// Crate paths the [`impl_facet_via_elm!`](crate::impl_facet_via_elm) expansion
/// reaches through, so a downstream crate needs only `facett-core` in scope (not
/// its own `egui` / `serde_json` idents) for the macro to resolve. Hidden: not a
/// stable API, only the macro should touch it.
/// Write the `impl Facet for $ty` for a type that already implements
/// [`Elm`](crate::Elm) — the reusable version of the bridge `facett-security`
/// hand-writes.
///
/// This is a **macro, not a blanket impl**, on purpose: `impl<T: Elm> Facet for T`
/// cannot live in `facett-core` and also be added by downstream crates (orphan
/// rule), and it would collide with every still-hand-written `impl Facet` during
/// the staged migration. Invoking the macro locally sidesteps both.
///
/// # Three forms
/// ```ignore
/// // 1. Just the core bridge: title + pure-view `ui` loop + serde `state_json`.
/// facett_core::impl_facet_via_elm!(MyView);
///
/// // 2. Core bridge PLUS extra `Facet` overrides (caps, scale, selection_json,
/// // copy, …) spliced into the SAME impl block. NOTE: form 2 still emits the
/// // default serde `state_json`, so the extra block must NOT define its own
/// // `state_json` (that would be a duplicate-method error) — use form 3 for that:
/// facett_core::impl_facet_via_elm!(MyView, {
/// fn caps(&self) -> facett_core::FacetCaps {
/// facett_core::FacetCaps::NONE.selectable().scalable()
/// }
/// fn scale(&self) -> f32 { self.state().scale }
/// fn set_scale(&mut self, s: f32) { let _ = self.update(MyMsg::SetScale(s)); }
/// });
///
/// // 3. Component publishes a RICHER `state_json` than plain `serde(state())`:
/// // pass `custom_state_json` to SUPPRESS the default, then provide your own
/// // `state_json` (and any other overrides) in the extra block. This is what
/// // lets rich components (helix, timeline, security, …) use the macro instead
/// // of hand-writing the whole `impl Facet`.
/// facett_core::impl_facet_via_elm!(MyView, custom_state_json, {
/// fn state_json(&self) -> serde_json::Value {
/// // e.g. the model PLUS derived introspection keys the deck/matrix read
/// serde_json::json!({ "bar_count": self.state().bars.len(), /* … */ })
/// }
/// fn caps(&self) -> facett_core::FacetCaps { facett_core::FacetCaps::NONE.selectable() }
/// });
/// ```
/// The generated `ui` is the FC-9 loop verbatim: `for m in self.view(ui) { self.update(m); }`.