Skip to content

Lessons on this device

The editor holds as many lessons as you make. They live in this browser, in IndexedDB, and you switch between them from the Lessons button in the editor's top bar (or On this device in the sidebar, which opens the same panel).

Nothing you are working on is ever replaced. That is the whole point of the feature, and it is worth saying plainly, because it used to be the opposite: the editor kept exactly one working document, so opening a lesson from the hub, forking one, or importing a Word file all overwrote whatever was on screen — and each of those flows needed a "Replace your current work?" dialog to warn you first. Those dialogs are gone, because there is nothing left to replace.

The panel

text
Lessons on this device
─────────────────────────────────────────────────────────
  Volcanoes                                    ✓  ⋯
  3 sections · 24 blocks · edited just now        Published

  Volcanoes (copy)                                 ⋯
  3 sections · 24 blocks · edited 2 minutes ago

  Year 4 spellings                                 ⋯
  1 section · 6 blocks · edited yesterday       Cloud draft
─────────────────────────────────────────────────────────
  + New lesson                                  Close

Clicking a row switches to it. The badge on the right says where else that lesson exists — Published on the hub, or a private Cloud draft; a lesson with no badge is on this device only. The menu holds the three things you can do to a lesson you are not currently in:

ActionWhat it does
RenameRetitles it. Same as editing the title at the top of the editor — the title is the lesson's name.
DuplicateA full copy, including its version history, unattached to the hub, titled "… (copy)".
Delete from this deviceRemoves the lesson, its document and its history. Asks twice, and cannot be undone.

Where each lesson lives

A lesson is three things. The first two are keyed by its id in this device's library; the third is keyed by whichever id its repository currently answers to:

WhatWhere
Its metadataThe lessons store, under the local id — title, block counts, hub attachment, last-edited time
Its documentThe lessonDocs store, under that same local id
Its version historyA git repository of its own, at /lessons/<repoId>/.git in LightningFS

The split between the first two is what keeps the list cheap: showing you a dozen titles reads a dozen small records, not a dozen whole lessons with their images.

repoId is that local id too, right up until the lesson is saved to the cloud — at which point the repository moves under the hub's id for it and follows the lesson to your other devices, while its metadata and document stay where they are. repoIdFor(lessonId, localId) is the one place that decides. See Version history for what that repository holds.

What each flow does now

You do thisWhat happens
New lesson (sidebar)Adds an empty lesson and opens it. Pressing it while already in an untouched one stays put.
Edit on one of your hub lessonsOpens the copy this device already has, exactly as you left it — never a second copy of the same lesson, and never overwritten by the cloud's. If the two differ, it tells you.
Fork a lesson from the hubA new lesson, cloned with the original's history, titled "… (copy)".
Fork into a new lesson (in the editor)The same, from the lesson you're in — which stays in the list, still attached to its hub row.
Import a Word or JSON fileA new lesson, with a history that starts at the import.
Save to cloud on a device-only lessonAttaches it to the hub lesson it creates, and takes its history up with it.

What this does not do

These lessons are local. Nothing here syncs: another browser, another device or another profile has its own library, and clearing your browsing data clears it. Saving a lesson to the cloud — published or as a private draft — is what puts a copy somewhere else, and is the only thing that does. The panel says so at the bottom, for the same reason.

Deleting a lesson that has been saved to the cloud removes only the local copy. The hub keeps the lesson and its published history, and opening it for editing again clones that history back down.

Because the copy here is never replaced, it can drift from the cloud one — edits made on this device and not saved, or edits saved from another. Saving to the cloud is what settles that: the push refuses to overwrite a lesson that has moved on since, and offers the same block-by-block merge everything else here uses.

Where this lives in the code

FileWhat it holds
packages/core/src/browser/storage.jsThe library API — list, get, create, save, delete, and the two migrations
packages/core/src/browser/imageStore.jsThe IndexedDB stores themselves (lessons, lessonDocs, images, app)
apps/web/src/components/LessonsDialog.jsxThe panel above
apps/web/src/pages/EditorPage.jsxOpening, creating, duplicating, deleting — and saving before it leaves one

Upgrading from the single-document editor

Two migrations run in order the first time the editor loads, and both are idempotent:

  1. migrateLocalStorage() — the pre-IndexedDB draft (a localStorage document with base64 images) moves into IndexedDB, images becoming binary blobs.
  2. migrateToLibrary() — that single document becomes the library's first lesson, keeping its title, its hub attachment and its fork origin.

The migrated lesson is given the id draft, which is not arbitrary: draft is the name the old working lesson's repository already has on disk, and a local lesson's id is its repo id, so the whole timeline carries across without a single git object being copied. Lessons made after it get ordinary random ids.

Copyright © 2026 Spelling Creator.