dorfteich/docs/architecture/adr/0013-versioning-and-trash.md
Claude Fable 5 0629411966 Add architecture documentation, ADRs, and operations concept
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>
2026-07-04 14:36:16 +02:00

69 lines
3.1 KiB
Markdown

# 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_versions` rows 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.