Skip to content

Whiteboard

The Whiteboard modality is a free-form canvas where components are placed at absolute positions and compiled into rendered elements. It implements all three core traits — Reducer, Component, and Modality — on a single struct.

Associated TypeConcrete Type
SyncedWhiteboardSynced
EphemeralWhiteboardEphemeral
PositionWhiteboardPosition
ChildComponentdyn WhiteboardComponent
Component::MetadataWhiteboardMetadata
Component::OutputVec<WhiteboardElement>
SnapshotWhiteboardSnapshot

Session type: Session<Whiteboard, SessionIntent<WhiteboardIntent>>

pub struct Whiteboard {
rt: Runtime<Self>,
}

Embeds Runtime<Self> which holds State<WhiteboardSynced, WhiteboardEphemeral> inside rt.state. Implements Reducer<State = State<WhiteboardSynced, WhiteboardEphemeral>>, Component<Metadata = WhiteboardMetadata, Output = Vec<WhiteboardElement>>, and Modality.

WhiteboardPosition describes where a component sits on the canvas:

pub struct WhiteboardPosition {
pub x: f64,
pub y: f64,
pub w: f64,
pub h: f64,
pub rotation: f64,
pub z_index: i32,
}

Implements Clone, PartialEq, Hash, Describe. Describe output: "x:100 y:200 w:400 h:300".

WhiteboardSynced stores only placements — there is no separate objects list. Every shape, text box, or card on the canvas is a ComponentPlacement<WhiteboardPosition>.

pub struct WhiteboardSynced {
pub placements: Vec<ComponentPlacement<WhiteboardPosition>>,
// resource metadata (HasResourceMeta)
}

Each placement has a component_key (e.g. "text_box", "card", "shape") that maps to a static component in the registry, and an optional component_id for forked/customized instances loaded from storage.

WhiteboardEphemeral holds runtime-only state that is not persisted:

pub struct WhiteboardEphemeral {
pub output: Vec<WhiteboardElement>,
pub selected_ids: Vec<String>,
pub current_tool: ToolType,
pub tool_color: Color,
pub tool_thickness: f64,
pub tool_shape_type: ShapeType,
pub tool_font_family: TextFontFamily,
pub canvas_zoom: f64,
pub canvas_pan_x: f64,
pub canvas_pan_y: f64,
pub canvas_width: f64,
pub canvas_height: f64,
// the Embedded Prose RFC: Embedded Prose
pub doc: LoroDoc, // LoroDoc handle for creating LoroText containers
pub prose_editors: HashMap<String, Prose>, // embedded prose editors keyed by placement ID
pub focused_text_id: Option<String>, // currently focused text element for inline editing
}

The doc, prose_editors, and focused_text_id fields were added by the Embedded Prose RFC (Embedded Prose). Text elements now have live LoroText containers instead of opaque prose_bytes. The Prose embedded Reducer handles text editing without a separate ProseSession.

Elements are the compiled output — leaf rendered primitives on the canvas. The old WhiteboardObject enum is renamed to WhiteboardElement:

pub enum WhiteboardElement {
Path(PathElement),
Shape(ShapeElement),
Text(TextElement),
Image(ImageElement),
Native(NativeElement),
// ... additional variants
}

Elements are never directly mutated. You modify placements, property values, and metadata; recompilation flows downward to produce new elements.

The whiteboard’s compile() delegates to layout(), which iterates all placements, resolves bindings, and calls compile_child for each:

// Simplified flow
fn compile(&self, metadata: &WhiteboardMetadata, values: &HashMap<String, PropertyValue>)
-> Result<Vec<WhiteboardElement>, CompileError>
{
let pairs = self.layout(|placement, meta, resolved| {
self.compile_child(placement, meta, resolved)
}, values)?;
Ok(self.assemble(pairs))
}

child_metadata() extracts a Rect from the placement’s WhiteboardPosition. assemble() flattens the per-component Vec<WhiteboardElement> outputs into a single Vec<WhiteboardElement>.

  • Intents — all WhiteboardIntent variants
  • Components — the WhiteboardComponent subtrait and built-ins
  • Reducer — state management via the Genvoy pattern
  • Command — the universal envelope wrapping WhiteboardIntent
  • Lesson Plan — parent modality that embeds whiteboards as child sessions
  • FRB Patterns — how whiteboard types cross the Dart/Rust boundary