Files
feuerwerk/src/core/store/SnapshotStore.ts
T
adminandClaude Opus 5.5 94749b4f3e
game-maker / gate (push) Successful in 3m6s
game-maker / suite (push) Successful in 9m38s
Let a project keep named snapshots and restore them (#231)
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>
2026-09-25 18:21:52 +02:00

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>;
}