Historique des versions : snapshots horodatés, différences, restauration

- Avant d'écraser un schéma, le contenu précédent est copié dans
  DATA_DIR/.history/<chemin>/<date>.excalidraw, au plus une version par
  HISTORY_INTERVAL (10 min) ; rétention HISTORY_KEEP (50) et HISTORY_MAX_AGE.
  Pas de git : l'image reste distroless.
- API : GET /api/history, GET /api/history/version, POST /api/history/snapshot,
  POST /api/history/restore (le contenu remplacé devient une version).
- L'historique suit renommages et déplacements, part à la corbeille avec le
  schéma supprimé.
- Dialogue Historique (barre du haut, menu de l'explorateur) : aperçu,
  différences colorées avec l'état actuel, restaurer, restaurer en copie,
  créer une version.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H6J7vKwXx7uopxKhWfFJpm
This commit is contained in:
claude BotandClaude Opus 5.5 committed 2026-10-07 18:03:08 +02:00
1 parent 88f7750577
commit 9f9ec3b3a4
14 files changed
+1005 -7

No files matched your search

+23
View File
@@ -32,6 +32,13 @@ export interface UpdateStatus {
checkedAt?: string;
}
/** Version enregistrée d'un schéma (time : date de ce contenu). */
export interface Version {
id: string;
time: string;
size: number;
}
export class ApiError extends Error {
constructor(public status: number, message: string) {
super(message);
@@ -98,6 +105,22 @@ export const api = {
await request(`/api/entry?path=${q(path)}&recursive=${recursive}`, { method: "DELETE" });
},
history: async (path: string): Promise<Version[]> =>
(await request(`/api/history?path=${q(path)}`)).json(),
readVersion: async (path: string, id: string): Promise<string> =>
(await request(`/api/history/version?path=${q(path)}&id=${q(id)}`)).text(),
/** Crée une version du contenu courant (sans attendre l'intervalle automatique). */
snapshot: async (path: string): Promise<Version> =>
(await request("/api/history/snapshot", json({ path }))).json(),
/** Restaure une version ; le contenu remplacé devient lui-même une version. */
restore: async (path: string, id: string): Promise<Entry> =>
(await request("/api/history/restore", json({ path, id }))).json(),
versionUrl: (path: string, id: string) => `/api/history/version?path=${q(path)}&id=${q(id)}`,
readLibrary: async (): Promise<unknown> => (await request("/api/library")).json(),
writeLibrary: async (body: string): Promise<void> => {