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 (used by mode-aware ranking in P9).
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` (P7). For now it carries the operation in `Expr` form.
165#[derive(Clone, Debug)]
166pub struct Operation {
167    /// The checked operation form to realize.
168    pub form: Expr,
169}
170
171/// A registered lens: its metadata plus optional view and editor objects.
172#[derive(Clone)]
173pub struct Lens {
174    /// The lens metadata the dispatcher reads.
175    pub meta: LensMeta,
176    /// The view encoder, if this lens renders.
177    pub view: Option<Arc<dyn View>>,
178    /// The editor decoder, if this lens edits.
179    pub editor: Option<Arc<dyn Editor>>,
180}
181
182impl Lens {
183    /// A metadata-only lens (no view or editor object yet).
184    pub fn metadata_only(meta: LensMeta) -> Self {
185        Self {
186            meta,
187            view: None,
188            editor: None,
189        }
190    }
191
192    /// A view lens backed by `view`.
193    pub fn view(meta: LensMeta, view: Arc<dyn View>) -> Self {
194        Self {
195            meta,
196            view: Some(view),
197            editor: None,
198        }
199    }
200
201    /// An editor lens backed by `editor`.
202    pub fn editor(meta: LensMeta, editor: Arc<dyn Editor>) -> Self {
203        Self {
204            meta,
205            view: None,
206            editor: Some(editor),
207        }
208    }
209}