Skip to content

Lesson Plan

The LessonPlan modality organizes slides into sections, where each slide embeds a whiteboard. It is the only current parent modality — it implements HasChildren and uses Slot for hot/cold child session management. Like all modalities, a single struct implements Reducer, Component, and Modality.

Associated TypeConcrete Type
SyncedLessonPlanSynced
EphemeralLessonPlanEphemeral
PositionSlidePosition
ChildComponentSlot<Session<Whiteboard, SessionIntent<WhiteboardIntent>>, Vec<WhiteboardElement>>
Component::MetadataLessonPlanMetadata
Component::OutputVec<CompiledSlide>
SnapshotLessonPlanSnapshot

Session type: Session<LessonPlan, ParentSessionIntent<LessonPlanIntent, SessionIntent<WhiteboardIntent>>>

pub struct LessonPlan {
state: State<LessonPlanSynced, LessonPlanEphemeral>,
children: HashMap<String, Slot<
Session<Whiteboard, SessionIntent<WhiteboardIntent>>,
Vec<WhiteboardElement>,
>>,
}

The children map is keyed by placement ID. Each value is a Slot that is either Cold (cached output) or Hot (full child session).

SlidePosition describes a slide’s place in the lesson structure:

pub struct SlidePosition {
pub section_id: String,
pub enabled: bool,
pub duration: Duration,
}

Implements Clone, PartialEq, Hash, Describe. Describe output: "slide in section X, enabled, 5min".

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

Every placement uses component_key: "whiteboard" and component_id: Some("specific-whiteboard-uuid"). Each slide always references a specific whiteboard instance loaded from storage.

layout() iterates placements, looks up children by component_id, and calls slot.compile(). Cold slots return cached Vec<WhiteboardElement>; Hot slots delegate to the child Session::compile() which returns its already-compiled output.

LessonPlan overrides layout() to filter disabled slides:

fn layout<F>(&self, compile_child: F, values: &HashMap<String, PropertyValue>)
-> Result<Vec<(ComponentPlacement<SlidePosition>, Vec<WhiteboardElement>)>, CompileError>
where
F: Fn(&ComponentPlacement<SlidePosition>, &WhiteboardMetadata,
&HashMap<String, PropertyValue>)
-> Result<Vec<WhiteboardElement>, CompileError>,
{
self.placements().iter()
.filter(|p| p.position.enabled)
.map(|p| {
let meta = self.child_metadata(p);
let resolved = resolve_bindings(&p.bindings, values);
compile_child(p, &meta, &resolved).map(|out| (p.clone(), out))
})
.collect()
}

child_metadata() returns WhiteboardMetadata for the slide’s whiteboard. assemble() builds Vec<CompiledSlide> from the (placement, child output) pairs.

The ParentSessionIntent enum routes commands to the lesson plan or its children:

pub enum ParentSessionIntent<I, CI> {
Base(SessionIntent<I>), // delegate to leaf behavior
Child { id: Option<String>, intent: CI }, // route to child session
WarmChild { id: String }, // promote Cold -> Hot
}

Child { id: None, intent } routes to selected_child_id(). The concrete type for LessonPlan:

ParentSessionIntent<LessonPlanIntent, SessionIntent<WhiteboardIntent>>
pub enum ParentSessionFeedback {
Base(SessionFeedback),
ChildWarmed { id: String, bytes: Vec<u8> },
}
  • Children — HasChildren impl, hot/cold lifecycle, child AI
  • Whiteboard — the child modality embedded in each slide
  • Reducer — state management via the Genvoy pattern
  • Command — the universal envelope
  • FRB API Layer — thin open/dispatch/close surface