Skip to main content

sim_lib_view/
contract.rs

1//! View/editor contracts and their open metadata record.
2//!
3//! A **view** is an encoder in the codec sense: `Value -> Scene`. An **editor**
4//! is a decoder: `(Value, Intent) -> Draft`, then `Draft -> operation`. A
5//! **lens** pairs a view with an optional editor and carries an
6//! `ExportRecord`-style metadata record ([`LensMeta`]) describing what it claims
7//! and what it costs. The metadata is an open record, not a closed kernel enum:
8//! new lens kinds and fields ride along as data rather than forcing kernel
9//! changes.
10
11use std::sync::Arc;
12
13use sim_kernel::{CapabilityName, Cx, Diagnostic, Expr, Result, ShapeRef, Symbol};
14
15/// The role a lens plays. A lens of a given kind is only considered when a pane
16/// asks for that kind (a pane showing a value asks for `View`; an editing pane
17/// asks for `Editor`).
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub enum LensKind {
20    /// Encodes a value into a Scene for display.
21    View,
22    /// Decodes Intents into checked operations.
23    Editor,
24    /// A read-only detail panel over a value.
25    Inspector,
26    /// A floating layer (tooltip, popover, confirmation sheet).
27    Overlay,
28    /// A single invokable action surfaced as a control.
29    Action,
30}
31
32/// Open metadata describing a registered lens. Modelled on `ExportRecord`: a
33/// data record the dispatcher reads, never a closed kernel enum plus a parallel
34/// registry map.
35#[derive(Clone)]
36pub struct LensMeta {
37    /// Stable lens id (for example `view:agent-topology`).
38    pub id: Symbol,
39    /// The role this lens plays.
40    pub kind: LensKind,
41    /// Shapes this lens claims; a lens matches a value when one accepts it.
42    pub claimed_shapes: Vec<ShapeRef>,
43    /// Class symbols this lens claims as a fallback when no Shape matches.
44    pub claimed_classes: Vec<Symbol>,
45    /// Declared quality; higher wins ties among equally specific matches.
46    pub quality: i32,
47    /// Declared cost; lower wins ties after quality.
48    pub cost: i32,
49    /// Capabilities the operator must hold for this lens to be eligible.
50    pub required_capabilities: Vec<CapabilityName>,
51    /// Experience modes this lens prefers for mode-aware ranking.
52    pub preferred_modes: Vec<Symbol>,
53    /// Whether this is the always-matching universal default of its kind.
54    pub universal_default: bool,
55}
56
57impl LensMeta {
58    /// Build a minimal lens metadata record with no claims and zero quality and
59    /// cost. Use the builder methods to fill it in.
60    pub fn new(id: Symbol, kind: LensKind) -> Self {
61        Self {
62            id,
63            kind,
64            claimed_shapes: Vec::new(),
65            claimed_classes: Vec::new(),
66            quality: 0,
67            cost: 0,
68            required_capabilities: Vec::new(),
69            preferred_modes: Vec::new(),
70            universal_default: false,
71        }
72    }
73
74    /// Claim a Shape (a shape `Value`).
75    pub fn claiming_shape(mut self, shape: ShapeRef) -> Self {
76        self.claimed_shapes.push(shape);
77        self
78    }
79
80    /// Claim a class symbol as a fallback match.
81    pub fn claiming_class(mut self, class: Symbol) -> Self {
82        self.claimed_classes.push(class);
83        self
84    }
85
86    /// Set quality and cost.
87    pub fn with_quality_cost(mut self, quality: i32, cost: i32) -> Self {
88        self.quality = quality;
89        self.cost = cost;
90        self
91    }
92
93    /// Require a capability.
94    pub fn requiring(mut self, capability: CapabilityName) -> Self {
95        self.required_capabilities.push(capability);
96        self
97    }
98
99    /// Prefer an experience mode.
100    pub fn preferring_mode(mut self, mode: Symbol) -> Self {
101        self.preferred_modes.push(mode);
102        self
103    }
104
105    /// Mark this lens as the universal default of its kind.
106    pub fn as_universal_default(mut self) -> Self {
107        self.universal_default = true;
108        self
109    }
110}
111
112/// A view encoder: `Value -> Scene`. Views are pure: the same value and options
113/// yield the same Scene.
114pub trait View: Send + Sync {
115    /// Encode the value (in `Expr` form) into a Scene value.
116    fn encode(&self, cx: &mut Cx, value: &Expr) -> Result<Expr>;
117}
118
119/// An editor decoder: `(Value, Intent) -> Draft`, then `Draft -> operation`.
120pub trait Editor: Send + Sync {
121    /// Fold an Intent into a pending draft over the value.
122    fn decode(&self, cx: &mut Cx, value: &Expr, intent: &Expr) -> Result<Draft>;
123    /// Turn a committable draft into a checked operation.
124    fn commit(&self, cx: &mut Cx, draft: &Draft) -> Result<Operation>;
125}
126
127/// A pending edit over a value: the base, the proposed value, whether it may be
128/// committed, and any field-anchored diagnostics.
129#[derive(Clone, Debug)]
130pub struct Draft {
131    /// The value being edited, before this draft.
132    pub base: Expr,
133    /// The proposed value if the draft commits.
134    pub proposed: Expr,
135    /// Whether the draft currently passes validation.
136    pub committable: bool,
137    /// Field-anchored diagnostics; non-empty implies not committable.
138    pub diagnostics: Vec<Diagnostic>,
139}
140
141impl Draft {
142    /// A clean, committable draft proposing `proposed` over `base`.
143    pub fn clean(base: Expr, proposed: Expr) -> Self {
144        Self {
145            base,
146            proposed,
147            committable: true,
148            diagnostics: Vec::new(),
149        }
150    }
151
152    /// A rejected draft anchored to a diagnostic; the base is preserved.
153    pub fn rejected(base: Expr, diagnostic: Diagnostic) -> Self {
154        Self {
155            proposed: base.clone(),
156            base,
157            committable: false,
158            diagnostics: vec![diagnostic],
159        }
160    }
161}
162
163/// A checked operation produced by an editor commit, ready to be submitted
164/// through `realize`.
165#[derive(Clone, Debug)]
166pub struct Operation {
167    /// The checked operation form to realize.
168    pub form: Expr,
169    /// Optional shape the realized result must satisfy before it is accepted.
170    pub result_shape: Option<ShapeRef>,
171    /// Capabilities the realization target must hold to answer the operation.
172    pub required_capabilities: Vec<CapabilityName>,
173}
174
175impl Operation {
176    /// Build an operation with no additional authority metadata.
177    pub fn new(form: Expr) -> Self {
178        Self {
179            form,
180            result_shape: None,
181            required_capabilities: Vec::new(),
182        }
183    }
184
185    /// Attach the expected shape of the realized result.
186    pub fn with_result_shape(mut self, shape: ShapeRef) -> Self {
187        self.result_shape = Some(shape);
188        self
189    }
190
191    /// Attach one capability required by the realization target.
192    pub fn requiring(mut self, capability: CapabilityName) -> Self {
193        self.required_capabilities.push(capability);
194        self
195    }
196}
197
198/// A registered lens: its metadata plus optional view and editor objects.
199#[derive(Clone)]
200pub struct Lens {
201    /// The lens metadata the dispatcher reads.
202    pub meta: LensMeta,
203    /// The view encoder, if this lens renders.
204    pub view: Option<Arc<dyn View>>,
205    /// The editor decoder, if this lens edits.
206    pub editor: Option<Arc<dyn Editor>>,
207}
208
209impl Lens {
210    /// A metadata-only lens (no view or editor object yet).
211    pub fn metadata_only(meta: LensMeta) -> Self {
212        Self {
213            meta,
214            view: None,
215            editor: None,
216        }
217    }
218
219    /// A view lens backed by `view`.
220    pub fn view(meta: LensMeta, view: Arc<dyn View>) -> Self {
221        Self {
222            meta,
223            view: Some(view),
224            editor: None,
225        }
226    }
227
228    /// An editor lens backed by `editor`.
229    pub fn editor(meta: LensMeta, editor: Arc<dyn Editor>) -> Self {
230        Self {
231            meta,
232            view: None,
233            editor: Some(editor),
234        }
235    }
236}