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}