Add version history with rollback and per-note created/synced info
All checks were successful
ci / test (push) Successful in 3m8s
ci / release (push) Successful in 11m21s

Server (schema v2, auto-migrates): every accepted overwrite snapshots the
superseded revision into note_history (capped at 50 per note); new
GET /api/v1/notes/{id}/history endpoint; compact purges orphaned history.

Client: notes get a synced_at stamp on every confirmed server exchange;
an info footer under the editor and a Ctrl/Cmd+I panel show created/
modified/last-synced plus the revision list. Restoring a revision applies
it as a normal edit through the sync path, so rollback is non-destructive.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-25 22:20:19 +02:00
parent bd2b8e6241
commit c389bab46e
12 changed files with 563 additions and 9 deletions

View File

@@ -1,5 +1,5 @@
import { writable, get } from 'svelte/store';
import type { PushResult, ServerNote, SyncState } from '../types';
import type { PushResult, Revision, ServerNote, SyncState } from '../types';
import { notes, settings, cursor, setCursor, applyServerNotes, markPushed, replaceWithServer } from '../db/store';
export const syncState = writable<SyncState>('local');
@@ -66,6 +66,15 @@ async function push(): Promise<void> {
}
}
/** Fetch a note's server-side revision history (newest first). */
export async function fetchHistory(id: string): Promise<Revision[]> {
if (!configured()) throw new Error('no server configured');
const res = await api(`/api/v1/notes/${encodeURIComponent(id)}/history`);
if (!res.ok) throw new Error(`history: HTTP ${res.status}`);
const body: { revisions: Revision[] } = await res.json();
return body.revisions;
}
/** Pull-then-push; safe to interrupt at any point (SPEC §4). */
export async function syncNow(): Promise<void> {
if (!configured()) {