Canvases that remember
Persist a canvas run with sprout.state so the kid resumes where they left off. Merge-defaults authoring, sprout.resumed, 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.
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.
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
- Merge-defaults, never replace. Default-fill missing fields (
sprout.state.x ??= default) and mutate. A wholesalesprout.state = {…}clobbers a resumed run. - Durable state only. Answers, current step, progress, score-so-far. Keep volatile / derived / animation state OUT (cursor, tween frames, hover) , every write persists, so volatile writes bloat the run.
- JSON-serializable values only. No functions, DOM nodes,
Date,Map,Set, they're stripped on save. - No PII / identifiers in
sprout.state. Activity data only. - Always wire it , every canvas. A canvas that ignores
sprout.staterestarts the kid from scratch on every reopen, which we never want.
sprout.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.