Add version history with rollback and per-note created/synced info
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:
14
SPEC.md
14
SPEC.md
@@ -92,17 +92,29 @@ CREATE TABLE notes (
|
||||
CREATE INDEX idx_notes_version ON notes(version);
|
||||
|
||||
CREATE TABLE meta (k TEXT PRIMARY KEY, v TEXT); -- schema_version, next_version counter
|
||||
|
||||
CREATE TABLE note_history ( -- schema v2: version history
|
||||
note_id TEXT NOT NULL,
|
||||
version INTEGER NOT NULL, -- version of the superseded revision
|
||||
content TEXT NOT NULL DEFAULT '',
|
||||
tags TEXT NOT NULL DEFAULT '[]',
|
||||
modified_at INTEGER NOT NULL,
|
||||
replaced_at INTEGER NOT NULL, -- unix ms when it was overwritten
|
||||
PRIMARY KEY (note_id, version)
|
||||
);
|
||||
```
|
||||
|
||||
- **Title is derived**, never stored: first non-empty line of `content`, markdown heading markers stripped for display.
|
||||
- `version` is a single global monotonically increasing counter (like a Lamport clock per server). Every accepted write bumps the global counter and stamps the note. This makes "give me everything changed since cursor X" trivial.
|
||||
- Tombstones are kept forever (personal scale; millions of notes are not expected). A `tefterd compact` subcommand may purge tombstones older than N days.
|
||||
- **Version history:** whenever an accepted push overwrites an existing note (fast-forward or edit-over-tombstone), the superseded state is copied into `note_history`, capped at the newest 50 revisions per note. Conflict pushes touch nothing, so they record nothing. `compact` purges history rows of notes that no longer exist. Rollback is client-side: fetch a revision, apply its content as a normal edit.
|
||||
|
||||
### Client store (IndexedDB)
|
||||
|
||||
Object store `notes`: same fields as server, plus:
|
||||
- `dirty: boolean` — locally modified, not yet pushed.
|
||||
- `baseVersion: number` — server version this local copy was derived from (0 for never-synced).
|
||||
- `synced_at?: number` — unix ms of the last confirmed exchange with the server for this note (accepted push or applied pull); absent = never synced.
|
||||
|
||||
Object store `meta`: `cursor` (last server version pulled), `serverUrl`, `token`.
|
||||
|
||||
@@ -116,6 +128,7 @@ Design: **pull-then-push, last-write-wins with conflict copies** (Simplenote-sty
|
||||
|---|---|---|
|
||||
| GET | `/changes?since=<cursor>&limit=500` | Pull notes with `version > cursor`, ordered by version. Returns `{notes: [...], cursor: <max version returned>, more: bool}` |
|
||||
| POST | `/notes/batch` | Push local changes. Body: `{notes: [{id, content, tags, created_at, modified_at, deleted, baseVersion}]}` |
|
||||
| GET | `/notes/{id}/history` | Superseded revisions of a note, newest first: `{revisions: [{note_id, version, content, tags, modified_at, replaced_at}]}` (empty list for unknown ids) |
|
||||
| GET | `/health` | Liveness + schema version |
|
||||
| POST | `/import/simplenote` | Multipart upload of Simplenote export zip (also available as CLI) |
|
||||
|
||||
@@ -187,6 +200,7 @@ This section is the heart of the product. The NV model must be reproduced exactl
|
||||
| Cmd/Ctrl+Shift+P | Toggle markdown preview for current note |
|
||||
| Cmd/Ctrl+K | Cycle tag filter (simple tag dropdown) |
|
||||
| Cmd/Ctrl+J / Cmd/Ctrl+Shift+J | Next / previous note in list while in editor |
|
||||
| Cmd/Ctrl+I | Note info (created / modified / last synced) + version history panel; a revision can be previewed and restored (restore = normal edit, non-destructive) |
|
||||
|
||||
### Editor
|
||||
- CodeMirror 6, markdown mode, light syntax styling only (bold headings, dim syntax marks). Monospace or user-set font. No WYSIWYG.
|
||||
|
||||
Reference in New Issue
Block a user