Component
Component is the compilation trait. Any type that can produce rendered output from metadata and property values implements it. Both leaf component instances (TextBox, Card) and modality structs (Whiteboard, Worksheet) are Components.
Trait Definition
Section titled “Trait Definition”pub trait Component: Send + Sync + 'static { type Metadata: Hash; type Output: Clone;
fn properties(&self) -> Vec<PropertySchema>;
fn compile( &self, metadata: &Self::Metadata, values: &HashMap<String, PropertyValue>, ) -> Result<Self::Output, CompileError>;}Unlike Reducer::reduce(), compile() takes &self — component instances carry their own configuration.
Associated Types
Section titled “Associated Types”| Type | Bound | Purpose |
|---|---|---|
Metadata | Hash | Spatial/structural context passed to compilation. Hash enables incremental caching. |
Output | Clone | The compiled result. Clone required for caching and Slot::Cold. |
Metadata and Hash
Section titled “Metadata and Hash”Metadata: Hash is required for Session’s incremental compilation cache. The default cache key for each placement is hash(placement_id, child_metadata, resolved_values) via default_compile_child_hash(). Modalities override compile_child_hash() when compile output depends on additional state (e.g. live LoroText content). If the hash matches the previous compilation, the cached output is reused.
What Metadata represents varies by modality:
| Modality | Metadata Type | Represents |
|---|---|---|
| Whiteboard | WhiteboardMetadata | Canvas dimensions, viewport |
| Worksheet | WorksheetMetadata | Page layout, grid configuration |
| LessonPlan | LessonPlanMetadata | Presentation settings |
For child components, metadata represents the space allocated by the parent:
| Child Context | Metadata | Represents |
|---|---|---|
| Whiteboard child | WhiteboardChildMeta | Placement ID + bounding Rect |
| Worksheet child | () | No spatial context needed |
PropertySchema
Section titled “PropertySchema”properties() returns the schema for configurable values. See Properties Overview for the full PropertySchema definition (10 fields). The key fields for compilation are:
pub struct PropertySchema { pub key: String, // Unique key (snake_case) pub name: String, // Display name pub property_type: PropertyType, // Type and constraints pub required: bool, // Must have a value pub default_value: Option<PropertyValue>, // Default when unset // ... + is_generator, description, icon, is_system, is_title}ComponentSchema (used at the FRB/API layer) is built from properties() plus display metadata like id() and name. It is not part of the trait itself.
ComponentPlacement<P>
Section titled “ComponentPlacement<P>”A placed component instance, stored in the modality’s synced state. Links a component type to a position and property overrides.
pub struct ComponentPlacement<P> { pub id: String, pub component_key: String, pub component_id: Option<String>, pub position: P, pub bindings: HashMap<String, PropertyBinding>,}| Field | Purpose |
|---|---|
id | Unique placement identifier |
component_key | Component type (e.g. "text_box", "card"). Maps to the static registry. |
component_id | If Some, a forked/customized instance loaded from storage. If None, use the registry blueprint. |
position | Modality-specific spatial info (generic P) |
bindings | Property bindings — Reference to parent or Static value |
TypedComponent
Section titled “TypedComponent”TypedComponent replaces manual Component impls for leaf components. It pairs a ComponentProperties struct with static identity metadata:
pub trait TypedComponent: Send + Sync + 'static { type Properties: ComponentProperties; type Metadata: Hash; type Output: Clone;
fn id() -> &'static str; fn name() -> &'static str; fn description() -> &'static str; fn icon() -> &'static str;
fn compile( &self, props: &Self::Properties, metadata: &Self::Metadata, ) -> Result<Self::Output, CompileError>;}A blanket impl<T: TypedComponent> Component for T bridges to the untyped pipeline — the blanket impl calls Properties::from_values() and delegates to the typed compile(). No changes needed to Modality, Session, caching, or Slot.
See Adding a Component for the full walkthrough.
Modality-Specific Subtraits
Section titled “Modality-Specific Subtraits”Subtraits pin the associated types for trait object usage. This allows parent modalities to hold dyn WhiteboardComponent collections:
trait WhiteboardComponent: Component<Metadata = WhiteboardChildMeta, Output = Vec<WhiteboardElement>> { fn id(&self) -> &'static str; fn name(&self) -> &'static str; fn description(&self) -> &'static str; fn icon(&self) -> &'static str;}
trait WorksheetComponent: Component<Metadata = (), Output = WorksheetElement> { fn id(&self) -> &'static str; fn name(&self) -> &'static str; fn description(&self) -> &'static str; fn icon(&self) -> &'static str;}Both subtraits have blanket impls for any TypedComponent with matching associated types, so implementing TypedComponent is sufficient — you never implement WhiteboardComponent or WorksheetComponent directly.
Components register themselves via the inventory crate:
inventory::submit!(WhiteboardComponentEntry(&CardComponent));Resolution uses inventory::iter — no manual match arm needed:
pub fn resolve_component(id: &str) -> Option<&'static dyn WhiteboardComponent> { inventory::iter::<WhiteboardComponentEntry> .into_iter() .find(|e| e.0.id() == id) .map(|e| e.0)}Incremental Compilation
Section titled “Incremental Compilation”The Hash bound on Metadata enables Session to cache compilation results per placement. The flow:
- Session calls
layout()with a caching closure - For each placement, the closure calls
modality.compile_child_hash(placement, meta, values)to compute the cache key - Cache hit: return stored output. Cache miss: call
compile_child(), store result.
This means editing one placement in a 20-placement whiteboard only recompiles one component — the other 19 are hash hits. Selection changes (ephemeral-only) produce zero recompiles because all hashes match.
Compilation is Not Mutation
Section titled “Compilation is Not Mutation”Elements are compiled output, never directly mutated. To change what renders on screen, you modify placements, property values, or metadata. Recompilation flows downward automatically through the dispatch lifecycle.