Autosave only keeps the latest revision. Before a risky change there
was no way to put a known good state aside and go back to it later.
The main menu now has "Snapshots..." to create, restore and delete
named copies of the open project.
- Server: SnapshotStore.php keeps each snapshot as a copy of the
project JSON under data/snapshots/<project>/ (git-ignored). Four
routes under projects/{id}/snapshots use the same owner check as the
project, so an admin also sees foreign snapshots.
- Create writes the autosave first. With unsaved edits (play with open
edits, failed save, conflict) no snapshot is made and a message says
why. The rule is the pure function in src/editor/snapshots.ts.
- An empty name falls back to the local date and time.
- Restore asks first, then keeps the current state as "Before
restoring <name>". It writes the snapshot back as a new revision and
reloads the project, so the undo list is empty. A restore can itself
be undone by restoring that snapshot.
- Deleting a project deletes its snapshots too. Images, sounds, fonts
and pixel art are content-addressed and never deleted, so a snapshot
can always reach them.
- Client: SnapshotStore and HttpSnapshotStore in src/core/store, the
SnapshotsDialog, and the flows in ProjectSession. A stale revision
(409) becomes StaleRevisionError.
- CI runs the new PHP test in the gate job next to accounts.
Not covered by e2e: the lock during play and the conflict case. Both
are unit tests only.
Tests:
- check.sh: 164 files, 2401 tests, CHECK EXIT 0.
- tests/php/snapshots.test.php: 14 checks, all passed.
- tests/editor/snapshots.test.ts: 5 tests for the time format, the
blocker rule, the routes and 409.
- New e2e module 50 on a throwaway project: create, rename, restore,
undo the restore, delete, then delete the project and get 404 on
its snapshots. 46b expects the new menu entry. e2e 46,50: 20 checks
passed. The check sum goes from 1385 to 1393 (computed).
- Counter-checks, originals restored and compared with cmp: blocker
always allowed fails 1 unit test. No sorting fails 2 PHP checks.
Restore without keeping the old state fails 50c and aborts 50d.
- The first sort test with 2 snapshots passed by chance half the time
because of random ids. It now uses 8.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
27 lines
958 B
TypeScript
27 lines
958 B
TypeScript
/** One snapshot of a project as the list shows it (#231). */
|
|
export interface SnapshotSummary {
|
|
id: string;
|
|
name: string;
|
|
/** The project revision the snapshot copied. */
|
|
revision: number;
|
|
/** ISO time, UTC. */
|
|
createdAt: string;
|
|
}
|
|
|
|
/**
|
|
* The snapshots of a project: copies of the project as the server stored it. A restore writes the
|
|
* copy back as a new revision and keeps the stand it replaced as a snapshot of its own, `before`.
|
|
*/
|
|
export interface SnapshotStore {
|
|
list(projectId: string): Promise<SnapshotSummary[]>;
|
|
/** An empty name lets the server name it by the time. */
|
|
create(projectId: string, name: string): Promise<SnapshotSummary>;
|
|
/** Rejects with `StaleRevisionError` when `revision` is not the stored one. */
|
|
restore(
|
|
projectId: string,
|
|
snapshotId: string,
|
|
revision: number,
|
|
): Promise<{ revision: number; before: SnapshotSummary }>;
|
|
delete(projectId: string, snapshotId: string): Promise<void>;
|
|
}
|