Build

Canvases that remember

Persist a canvas run with sprout.state so the kid resumes where they left off, and carry a durable checkpoint across days with sprout.journey. Merge-defaults authoring, sprout.resumed, sprout.log, HARD rules.

Canvas Memory lets a canvas remember where the kid was. Write the run's durable state into sprout.state and the SDK auto-persists it as the kid works , no save button, assignment IS the save. When the kid closes the app mid-activity and reopens the canvas, their progress is already there , they continue where they left off.

How it works

On reopen the host seeds sprout.state with the saved snapshot before your canvas code runs, so it is already correct at your first line , no async wait, no polling. sprout.resumed is a boolean, accurate at that first line, telling you whether this run is a resume.

Author with merge-defaults

Read sprout.state, default-fill the fields that are missing with ??=, then mutate. Never replace the whole object , assigning sprout.state = {…} wipes a resumed run.

JavaScript (inside the canvas)
const S = sprout.state;   // already the saved snapshot on resume, {} on a fresh start
S.step ??= 0;             // default-fill ONLY what's missing , never overwrite
S.answers ??= {};
// …now mutate freely; every change auto-persists.
S.answers.q1 = "blue";
S.step = 1;

Fresh vs resume with sprout.resumed

Branch on sprout.resumed for the genuine fresh-vs-resume cases: welcome-back vs intro, a first-time bonus, an opening animation, schema migration (key a _v version field and migrate from the resumed snapshot), or analytics.

JavaScript (inside the canvas)
if (sprout.resumed) {
goToStep(sprout.state.step);   // returning mid-run , rebuild UI from restored state
} else {
showIntro();                   // brand-new run , intro / tutorial / first-time bonus
}

Rules

tips_and_updatessprout.restore() is a deprecated back-compat alias (returns the snapshot when resuming, else null) , prefer sprout.resumed. sprout.save() is a manual-flush escape hatch you rarely need , assignment already persists.

Across days: sprout.journey

sprout.state resumes the current run. For memory that survives across runs and across days: a level, unlocked words, a mastery map: use sprout.journey. The kid finishes Tuesday on level 3; when the task offers a new play on Wednesday, the canvas reads the journey and picks up at level 3.

JavaScript (inside the canvas)
// Read where this kid is. ALWAYS default-fill: get() resolves {} on a
// first run AND when the family hasn't enabled durable state.
const j = await sprout.journey.get();
const level = j.level ?? 1;
const stars = j.stars ?? 0;
// …the kid plays, clears level 3…
// Save the WHOLE checkpoint (replace-only, ≤ 64 KB). Never rejects:
// branch on the result. Save BEFORE sprout.complete().
const res = await sprout.journey.save({ level: 4, stars: 12 });
if (!res.ok) {
// res.error: 'too-large' | 'disabled' | 'not-task-linked'.
// Degrade gracefully: the canvas still works, just without cross-day memory.
}

Journey rules: replace-only (save overwrites the whole blob, keep it a small "where are they" object), save before you complete (so the checkpoint is durable before the run freezes), and history does not go here: a growing list of past attempts belongs in sprout.log.

The record: sprout.log

sprout.log(entry) appends one entry to the run's record: what happened, for a parent or the system to review later. Fire-and-forget; the host batches and caps it, so a chatty canvas is bounded automatically.

JavaScript (inside the canvas)
sprout.log("level 3 cleared");
sprout.log({ event: "hint_used", card: 7 });

The three-line model: sprout.state = this sitting · sprout.journey = this kid's journey (durable, cross-day) · sprout.log = what happened, for the record. Full contract on the Canvas SDK reference.

Interactive canvas Canvas Canvas SDK

Was this page helpful?