Initial deliverable of the architecture phase: 16 ADRs (stack, CRDT collaboration, plugin sandbox, import/export, backups, CI/CD), data model, permission model, real-time collaboration and plugin concepts, deployment/operations/security documentation, and the milestone roadmap that the implementation issues are derived from. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3.1 KiB
3.1 KiB
ADR 0013: Page version history via Yjs snapshots, soft-delete trash
- Status: accepted
- Date: 2026-07-04
Context
Kickoff decision: version history (who changed what, view and restore old versions) and a trash (soft delete) are core product features, designed in from the start because retrofitting them into a CRDT data model is costly. Yjs (ADR 0003) stores documents as update logs with tombstones, which gives natural hooks for both.
Decision
Version history
- Automatic versions: the collab server creates a version snapshot when a page's editing session ends (last participant disconnects) or after a configurable active-editing interval (default 30 min), skipping no-op periods.
- Named versions: editors can create a version explicitly with a label ("before restructuring").
- Storage:
page_versionsrows hold a full encoded Yjs state (bytea), created-at, trigger (auto/manual/pre-restore), label, and the set of contributing user ids since the previous version (derived from Yjs update metadata). - Viewing: read-only render of any version + a text-level diff against the current version (diff computed on the derived plain/Markdown representation — good enough for "what changed", no structural diff UI in v1).
- Restore: restoring creates a new state on top of history (the restored content is applied as a regular update, preceded by an automatic "pre-restore" snapshot). History is append-only; nothing is rewritten.
- Compaction: to bound Yjs update-log growth, the persistence layer periodically merges the update log into the current state vector; version snapshots are self-contained, so compaction never loses restorable history. Retention of auto-versions is configurable (default: keep all for 90 days, then thin to daily).
Trash (soft delete)
- Deleting a page sets
deleted_at(+ who deleted it); the page disappears from sidebar, search, links (backlinks show a "deleted" hint), and the collab server refuses new sessions on it. - Pond Admins and the deleting editor see the pond's trash, can restore or purge. Auto-purge after a configurable retention (default 30 days) — purging deletes document state, versions, and files (ADR 0011).
- Deleting a whole pond follows the same pattern at pond level (Site-Admin-visible trash, same retention).
Consequences
- Version storage costs extra database volume; snapshot thinning and compaction keep it bounded and are covered by explicit stories.
- "Who changed what" is per-version granularity (contributor set), not per-keystroke attribution — deliberate simplification for v1.
- The permission model needs one extra rule: viewing history/trash requires
the same permission as editing the page (see
permissions.md).
Alternatives considered
- Event-sourcing every update forever, no compaction: unbounded growth; rejected.
- Storing Markdown snapshots instead of Yjs states: smaller, but restore would lose structure/plugin blocks and break the CRDT continuity; rejected (Markdown diffs are still used for the display layer).
- Hard delete only: data-loss risk contradicts kickoff decision.