M6: version history — note revisions + restore
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 5s
CI & Build / Python tests (push) Successful in 7s
CI & Build / Build & push image (push) Successful in 30s

A note's title+body is snapshotted on each edit that changes either, so an accidental overwrite can be viewed and restored (task 1906). Underwrites 'dump freely, nothing is lost'.

Backend: note_revisions table (migration 0014) + NoteRevision model; update_note records a revision of the PRE-edit state whenever title/body changes; GET /api/notes/<id>/revisions (newest 50) and POST /api/notes/<id>/revisions/<rev_id>/restore (snapshots the current state first so restore is itself undoable, then applies the revision with the usual title/body ripple — display name, links, #tags, backlinks). Title+body only in v1.

Frontend: a History toggle in the modal editor opens a panel of past versions (timestamp + preview) with per-row Restore. Store gains fetchRevisions/restoreRevision.

Migration 0014 runs on deploy; DB behavior operator-verified (no Postgres CI lane).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
This commit is contained in:
2026-07-22 16:08:30 -04:00
co-authored by Claude Opus 4.8
parent 47974f62ee
commit efbf981a2a
8 changed files with 252 additions and 2 deletions
+21
View File
@@ -29,6 +29,14 @@ export interface Attachment {
mime: string;
}
// A past version of a note's title+body (version history).
export interface NoteRevision {
id: string;
title: string | null;
body: string;
created_at: string | null;
}
export interface Note {
id: string;
title: string | null;
@@ -204,6 +212,17 @@ export const useNotesStore = defineStore("notes", () => {
if (idx >= 0) items.value.splice(idx, 1);
}
async function fetchRevisions(id: string): Promise<NoteRevision[]> {
const res = await api.get<{ revisions: NoteRevision[] }>(`/api/notes/${id}/revisions`);
return res.revisions;
}
async function restoreRevision(id: string, revId: string): Promise<Note> {
const note = await api.post<Note>(`/api/notes/${id}/revisions/${revId}/restore`);
reconcile(note);
return note;
}
return {
items,
loading,
@@ -229,5 +248,7 @@ export const useNotesStore = defineStore("notes", () => {
trash,
restore,
deleteForever,
fetchRevisions,
restoreRevision,
};
});