Localstorage projects using Y.js by glastonbridge · Pull Request #1236 · microbit-foundation/python-editor-v3
⚠️ Investigation project, do not merge
What this adds
- Persistent project storage using Y.js
- Realtime syncing of information across tabs
- Project browser with delete and rename project functionality
- Manual project revisioning and restore functionality
Approach
Storage
There is a list of projects, stored in an IndexedDB instance. This contains the project names, last edited dates, against a fixed GUID.
There are the projects themselves, which consist of a Y.Doc per project, backed by y-indexeddb which creates a DB per doc.
The revisions themselves are serialised Y.js Updates which act as a complete binary delta since their parent revision. They are stored in a separate DB again, as a linked list.
Note that the "working copy" of the project (in a version control analogy) is a Y.Doc but all the "commits" are Uint8Arrays and have to be unpacked to a working copy before they will work.
Revisioning
Currently the only way to create a revision is to click the clock button then "Save as new revision." This calculates the diff between the previously-saved revision (if any) and the current working copy, and adds it to the end of the linked list of revisions for the project.
Loading a previous revision creates a new project, so that it does not revert any of the other project's changes, but this is not necessarily a proposed behaviour for the future.
Code structure
I've tried to keep everything reusable inside the src/project-persistence folder so that it can be extracted as a modular add-on to other apps in future.
ProjectStorageProvider.tsx- underlying state, not intended to be used by consuming apps directly (this is a decision that helped me see clearly what was being used in the investigative work but might not be relevant for you)persistent-project-hooks.ts- if a project is loaded, this provides it, as well as itsawarenesswhich is Y.js' way of managing multiple interactive users on a document, where their cursors are etc.project-list-hooks.ts- create, load, delete, and list user projectsproject-history-hooks.ts- view and interact with a project's history.
There are also some chakra components for displaying common information about a project, although loading a project also has to do app-specific things so I didn't try to force it into a complete component that works everywhere.
Python Editor specifics
CodeMirror
CodeMirror has its own easy yjs integration that makes this really intuitive to implement. Check out the diff in src/editor/CodeMirror.tsx for the directness of the implementation.
Project Browser
There was no existing project browser, so I hacked the routing to allow it in src/ProjectPageRouting.tsx. This was not a designed solution, but the Python editor already assumes it has control over the router for event information and I didn't want to get caught up in picking it apart. Currently the only way to get to the project view is via the root path /.

