← Back to Docs
  • index.html
  • Clayground
  • Clayground.Lab
  • Flow
  • Clayground 2026.7
  • Flow QML Type

    A narrated walkthrough that drives the lab through its own mutation API. More...

    Import Statement: import Clayground.Lab

    Properties

    Signals

    Methods

    Detailed Description

    A flow is an ordered list of FlowStep. Demonstration steps run the lab's own verbs (from the lab's flowActions() map) so the learner sees the lab being operated; task steps wait for a condition on lab state. Pacing is measured in sim seconds, so a flow traverses the same states live and headless, and SimClock.timeScale scales it.

    While a flow runs the board is the flow's, and a task lends back exactly the one interaction it asks for - see control. That is the whole difference between a lesson and a demo somebody can walk over: the professor cannot explain the current that flows while a second click is reopening the switch.

    Example usage:

    import Clayground.Lab
    
    Flow {
        id: flow
        lab: root; flowId: "led-basics"
        FlowStep {
            key: "place"
            demo: [["let", "bat", "addPart", "battery", 6, 2]]
        }
        FlowStep {
            key: "flip"
            task: { "until": (n) => root.elemAt(n("sw")).on,
                    "allow": ["sw"],          // the one thing that is live
                    "solve": [["flipSwitch", "sw"]] }
        }
    }

    See also FlowStep, Narrator, and LabLang.

    Property Documentation

    camera : var

    The lab's camera rig, for steps that carry a FlowStep::view.

    An OrbitCamera3D (anything with goTo / focusOn / applyState will do). Left null, FlowStep.view is ignored - which is the whole of the compatibility story: a flow that never wires a camera behaves exactly as it did before the property existed.


    control : string [read-only]

    Who has the board right now: "learner", "flow" or "task".

    A lesson alternates the way a game tutorial does. While no flow runs the board is the learner's and everything works. The moment one starts it is the flow's: it is building and explaining, and a touch on the board is refused rather than obeyed. A task step opens exactly what it named (task.allow) and nothing else - that is task - and the instant its until holds the board is the flow's again. Full control comes back by leaving the flow (stop()), not by touching something mid-lesson.

    See also grants() and refusal.


    dwellTarget : real [read-only]

    Sim seconds this step is estimated to need (0 while waiting).


    flowId : string

    Stable id; prefixes narration keys and identifies the flow.


    hintShown : bool [read-only]


    index : int

    Active step, -1 while the flow is not running.


    lab : var

    The sandbox root: source of flowActions() and of the checkpoints.


    marks : var [read-only]

    The active step's FlowStep::mark names; empty when idle.

    One binding for the lab: hand it to whatever resolves and draws the marks (the professor kit's FlowGuide, or a MarkLayer directly). It empties itself when the flow stops, so a mark cannot outlive the lesson.


    narration : string [read-only]

    The active step's text in the current language.


    pacing : string

    How a step ends: "ready" (default), "auto" or "manual".

    ready uses the reading estimate as a ripening time, not as an advance: while it runs the Next control is quiet, and when it elapses Next becomes prominent and the learner confirms. Nobody is hurried and nobody waits - Next stays clickable throughout, so a learner who already knows this step can skip straight past it.

    auto advances by itself once the estimate elapses (kiosk mode, recordings, and the headless verification run); manual offers no estimate at all.

    A task step is the exception in every mode: it ends when its until holds (or solve is asked for), never on a Next - the Narrator offers none while waiting - so no later line can assume the learner did something they did not.


    pending : bool [read-only]

    Waiting on the learner or on the sim.


    readyProgress : real [read-only]

    0..1 progress through the estimate; 1 means "read it, go on".


    readySince : real [read-only]

    0..1 progress through the estimate; 1 means "read it, go on".


    refusal : string

    LabLang key of why the last touch did nothing; "" again after a moment.

    The refusal channel of a locked board, and the reason locking one is not the same as ignoring the learner: a click that does nothing and says nothing reads as a broken lab. Narrator renders it.

    See also refuse().


    ripe : bool [read-only]

    The estimate has elapsed: Next is the obvious thing to do now.


    running : bool [read-only]


    step : FlowStep [read-only]


    steps : list<FlowStep> [default]

    The steps (default property).


    title : string [read-only]


    titleKey : string

    Dictionary key of the flow's title.


    unresolvedVerbs : var [read-only]

    Verb names a demo or a solve asked for and the lab does not have.

    Cleared by start(). A verb that does not resolve fails SILENTLY - the action does nothing and the flow walks on teaching nothing - so a headless run has to be able to read the list rather than hope somebody was watching the log.

    See also Lab::runFlow.


    waiting : bool [read-only]

    In a task: the learner must act.


    Signal Documentation

    finished()

    Note: The corresponding handler is onFinished.


    narrated(string text, string lang, string key)

    Note: The corresponding handler is onNarrated.


    Method Documentation

    void applyView(var v)

    Moves camera as a FlowStep::view asks; a no-op without one.


    bool check()

    Re-tests the running task's until at once; advances if it holds.

    The clock samples ten times a second, which is late enough for a second click to land on a task the first one already satisfied - the exact thing a locked board exists to stop. Whoever performed a granted interaction calls this the moment the gesture is over, so the board is the flow's again before the next press can arrive.


    void goTo(int i)

    Jumps to a step: restores its checkpoint, then replays its demo.

    Every step is entered from a stored lab state, so stepping back or scrubbing never has to replay the whole flow.


    bool grants(var id)

    May the learner touch part id right now?

    True whenever no flow runs. While one does, only during a task step and only for what that task's allow named - resolved through nameOf, so a task names the part the way its until and its solve do ("allow": ["sw"]). A task whose subject only exists once its own demo has run gives a function instead ("allow": (n) => root.logicInputs), evaluated per press. A task that names nothing keeps the whole board live, which is what every flow written before this existed still gets.


    void next()


    void prev()


    void refuse()

    Says why the touch just refused did nothing, for a moment.


    void replayStep()

    Re-enters the current step from its checkpoint.


    void run(var actions)

    Executes an action list against the lab's verbs.


    string sayOf(QtObject step)

    Narration for a step: dictionary entry for its key, else say.


    void solve()

    Performs the current task for the learner ("show me").


    void start()

    Starts at the first step, dropping earlier checkpoints.


    void stop()

    Leaves the flow - and hands the whole board back, at once.