Appearance
Version History & Changelog
Overview
The Version History & Changelog feature provides two complementary mechanisms for tracking how content evolves over time. Version history is an automatic, system-managed record of every significant change to an entity. Changelog is a curated, creator-authored narrative of what changed and why. Together they serve different audiences and purposes while sharing the common goal of making content evolution transparent.
Version history (Phase 3) captures snapshots of entity state at meaningful points in time. When a creator edits a timeline update, character, or faction, the system preserves the previous state as a version snapshot. Any authorized user can browse previous versions, compare them side by side, and restore an earlier version if a change was made in error. This provides an audit trail and a safety net for collaborative editing.
Changelog (Phase 4) is a reader-facing feature scoped to individual timeline updates. Creators manually author changelog entries to communicate what changed in human-readable terms -- "Added three new NPCs", "Updated the map of the Northern District", "Removed the deprecated quest line". While version history records every field-level change automatically, the changelog tells the story of the content's evolution in terms that matter to the audience.
Goals
- Automatically capture version snapshots on every meaningful edit to tracked entities
- Provide a browsable history of all versions for any entity
- Enable side-by-side comparison of any two versions (diff view)
- Support non-destructive restoration of previous versions
- Maintain a complete audit trail of who changed what and when
- Allow creators to author curated, reader-facing changelog entries per timeline update
- Clearly separate automatic version history (complete, technical) from curated changelog (intentional, narrative)
- Manage storage growth from accumulating version snapshots
User Stories
Game Master / Storyteller
- As a GM, I want to see a history of changes to a timeline update so I can track how my content evolved over multiple sessions
- As a GM, I want to restore a previous version of a character if I accidentally overwrote important details
- As a GM, I want to compare two versions of a faction to see exactly what changed between sessions
- As a GM, I want to write changelog entries for my timeline updates so my players can see a summary of what is new
Writer
- As a writer, I want automatic version history so I never lose previous drafts of my world-building content
- As a writer, I want to see who made each change in a collaborative project so I can coordinate with co-authors
- As a writer, I want to author changelog entries that describe narrative changes (not field-level diffs) for my readers
Player / Reader
- As a player, I want to read the changelog for a timeline update to see what content has been added or changed recently
- As a player, I want to see when changelog entries were published so I know what is new since I last checked
Functional Description
Version History (Phase 3)
What Triggers Version Creation
A new version snapshot is created whenever a tracked entity is saved with meaningful changes. The system uses the following rules:
- Explicit save operations -- Every
PUTrequest that modifies entity data triggers a version snapshot of the state before the change is applied - Restore operations -- Restoring a previous version creates a new version (capturing the state before restoration)
- Cascade exclusion -- Child entity changes (e.g., adding an item to a section) create a version on the child's owning aggregate root, not on every ancestor
- No-op suppression -- If a save request results in no actual field changes (the submitted data matches current state), no version snapshot is created
This "snapshot on every meaningful save" approach provides comprehensive history without requiring creators to manually create checkpoints. The system always captures the state before the change, so the current live state plus all version snapshots provide a complete timeline of the entity's evolution.
Tracked Entities
Version history applies to aggregate roots:
- TimelineUpdate -- including its child sections, items, and sub-items (the full aggregate is snapshotted)
- Character -- including metadata fields
- Faction -- including memberships and nested faction structure
Child entities (Section, Item, FactionMembership) are versioned as part of their parent aggregate, not independently. When an item within a timeline update changes, the version snapshot captures the entire timeline update aggregate state.
Version Snapshot Format
Each version is stored as a full JSON snapshot of the aggregate root and all its children at that point in time. This denormalized approach trades storage space for simplicity and reliability:
- Self-contained -- Each snapshot can be rendered independently without joins or reconstruction
- Schema-tolerant -- Old snapshots remain valid even after schema migrations (they represent what the data looked like at the time)
- Diff-friendly -- Comparing two JSON snapshots is straightforward
The snapshot includes all entity fields except internal database IDs (which are replaced with the public-facing identifiers) and audit metadata (which is stored separately on the version record itself).
Version Record Structure
Each version record contains:
- Id -- unique identifier for the version
- ProjectId -- the project this version belongs to
- TargetType -- the entity type (timeline, character, faction)
- TargetId -- the internal ID of the versioned entity
- TargetSequenceNumber -- the public sequence number for API access
- VersionNumber -- sequential version counter scoped to the target entity (1, 2, 3, ...)
- Snapshot -- JSON blob containing the full entity state
- ChangeSummary -- auto-generated brief description of what fields changed
- CreatedAt -- when this version was captured
- CreatedBy -- who triggered the change that created this version
Browsing Version History
The version history list endpoint returns version records for a specified entity, ordered by version number descending (newest first). The response includes metadata (version number, timestamp, author, change summary) but excludes the full snapshot to keep list responses lightweight. The full snapshot is retrieved via the individual version detail endpoint.
GET /api/projects/{key}/history?targetType=timeline&targetSeq=3Returns a paginated list of versions for timeline update #3, including version number, timestamp, author name, and change summary for each version.
Comparing Versions (Diff View)
The diff endpoint compares two versions and returns a structured representation of what changed. The default comparison is between a version and its immediate predecessor, but the system also supports comparing any two arbitrary versions of the same entity.
GET /api/projects/{key}/history/{id}/diff
GET /api/projects/{key}/history/{id}/diff?compareWith={otherId}The diff output is a structured JSON response that identifies:
- Added fields/items -- present in the newer version but not the older
- Removed fields/items -- present in the older version but not the newer
- Changed fields -- present in both versions with different values, showing old and new
For complex nested structures (e.g., a timeline update with sections and items), the diff is hierarchical -- it identifies which sections were added/removed/changed, and within changed sections, which items were added/removed/changed.
Restoring Previous Versions
Restoring a previous version is a non-destructive operation. It does not overwrite history; instead, it creates a new version based on the old state.
The restore flow:
- Creator requests restoration of version N
- The system captures the current state as a new version snapshot (version N+1 equivalent)
- The system applies the state from version N to the live entity
- The restored state becomes the new current state
- The version history now shows: ... > version N (original) > version capturing pre-restore state > current (restored from N)
This ensures no data is ever lost. A creator can always "undo the undo" by restoring the version captured just before the restoration.
Audit Trail
Every version record captures CreatedBy (the user who triggered the change) and CreatedAt (when the change occurred). Combined with the ChangeSummary, this provides a complete audit trail answering "who changed what, when, and what did they change."
The audit trail is accessible to any project member with permission to view the entity. Players and Viewers can see version history for published entities, giving them visibility into when content was last updated.
Storage Considerations
Version snapshots accumulate over time and can consume significant storage, especially for large aggregate roots (e.g., a timeline update with many sections and items). The system employs several strategies to manage growth:
- Snapshot compression -- JSON snapshots are stored with PostgreSQL TOAST compression, which handles large text values transparently
- Retention policy -- An optional project-level setting to limit version retention (e.g., keep last 50 versions per entity). When the limit is exceeded, the oldest versions are pruned, but the first version (original creation) is always preserved
- Archival -- Future enhancement: move old version snapshots to cold storage after a configurable threshold
No proactive cleanup runs automatically at launch. The retention policy is evaluated lazily when new versions are created -- if the count exceeds the limit, the oldest excess versions are deleted in the same transaction.
Changelog (Phase 4)
Purpose and Scope
The changelog is a creator-authored, reader-facing record of content evolution. Unlike version history (which is automatic and comprehensive), changelog entries are intentionally written to communicate meaningful updates to the audience.
Each changelog entry is scoped to a single timeline update. This scoping aligns with how creators think about changes -- "I updated Session 3's timeline" rather than "I modified a character entity."
Changelog Entry Structure
Each entry contains:
- Date -- when the change was made (or when the creator says it was made -- this is editable)
- Title -- a short summary (e.g., "Added Northern District NPCs")
- Description -- optional longer explanation of the changes
- NewItems -- optional list of items that were added
- UpdatedItems -- optional list of items that were modified
- RemovedItems -- optional list of items that were removed
The item lists (NewItems, UpdatedItems, RemovedItems) are stored as string arrays, not foreign keys. This is intentional: the changelog is a narrative record. If an item is later deleted, its mention in the changelog should persist as a historical record, not become a dangling reference.
Changelog Lifecycle
Changelog Visibility
Changelog entries inherit visibility from their parent timeline update. If a timeline update is Published or Legacy, its changelog entries are visible to all project members including Players and Viewers. If the timeline update is Concept or In Development, changelog entries are only visible to Owners, Storytellers, and Co-Creators.
Changelog vs. Version History
These two features serve different purposes and audiences:
| Aspect | Version History | Changelog |
|---|---|---|
| Created by | System (automatic) | Creator (manual) |
| Granularity | Every field-level change | Curated summaries |
| Audience | Creators, collaborators | Everyone (including readers) |
| Content | JSON snapshots and diffs | Narrative descriptions |
| Scope | Any aggregate root | Timeline updates only |
| Purpose | Safety net, audit trail | Communication, transparency |
| Phase | Phase 3 | Phase 4 |
Data Flow
Version Snapshot Creation Flow
Version Comparison / Diff Process
Restore Operation Flow
Changelog Entry Lifecycle
Relationship Between Version History and Changelog
Key Components
VersionController
Handles HTTP endpoints for browsing version history, viewing individual versions, requesting diffs, and triggering restores. Routes requests to the VersionService.
VersionService
Core business logic for version management. Responsibilities:
- Creating version snapshots (called by entity services before applying changes)
- Serializing entity aggregates to JSON snapshots
- Generating change summaries by comparing old and new state
- Computing structured diffs between two version snapshots
- Orchestrating restore operations (snapshot current state, apply old state)
- Enforcing retention policies (pruning old versions)
ChangelogController
Handles HTTP endpoints for listing, creating, updating, and deleting changelog entries scoped to a timeline update.
ChangelogService
Business logic for changelog CRUD operations. Validates that the parent timeline update exists, enforces permission checks, and manages changelog entry persistence.
VersionRecord (Entity)
The domain entity representing a stored version. Belongs to the Collaboration feature module since it spans multiple entity types. Contains the JSON snapshot, version number, change summary, and audit metadata.
ISnapshotSerializer
Interface for converting entity aggregates to and from JSON snapshots. Each aggregate type (TimelineUpdate, Character, Faction) has a serializer implementation that knows how to capture the full aggregate state including children, and how to rehydrate an entity from a snapshot during restore.
IDiffEngine
Interface for comparing two JSON snapshots and producing a structured diff result. Walks the entity tree recursively, identifying additions, removals, and modifications at each level.
Feature Interactions
| Feature | Interaction |
|---|---|
| Content Timeline | Timeline updates are a primary target for version history. Changelog entries are scoped to individual timeline updates. Status changes on timelines trigger version snapshots. |
| Character System | Characters are tracked by version history. Editing a character creates a version snapshot. Character versions can be browsed, diffed, and restored. |
| Faction System | Factions are tracked by version history. Faction membership changes trigger version snapshots on the faction aggregate. |
| Permission & Visibility | Version history and restore operations require edit permissions. Version browsing requires view permission for the target entity. Changelog visibility inherits from the parent timeline update's status. |
| Real-time & Collaboration | Restore operations trigger real-time notifications to connected collaborators. Changelog entry creation may trigger activity feed entries. |
| Project Management | Version history is scoped to a project. Project deletion cascades to all version records. Retention policies may be configured at the project level. |
Edge Cases & Error Handling
Restoring a Version After Schema Changes
If the entity schema has changed since the version was captured (e.g., a new required field was added), the restore operation must handle the discrepancy. The snapshot serializer applies sensible defaults for any fields present in the current schema but absent from the snapshot. Fields present in the snapshot but removed from the current schema are ignored. This ensures old snapshots remain restorable even after migrations.
Restoring During Concurrent Editing
If User A is editing an entity while User B restores a previous version, User A's in-progress changes may conflict. The restore operation uses optimistic concurrency -- it captures the pre-restore snapshot and applies the restoration atomically. If User A submits their edit after the restore, the standard update flow creates a new version (capturing the restored state) before applying User A's changes. No data is lost, though User A's changes apply on top of the restored state rather than their original baseline.
Version History for Deleted Entities
When an entity is deleted, its version history records are retained for a configurable grace period (default: 30 days). This allows recovery if the deletion was accidental. After the grace period, orphaned version records are cleaned up by a background task. During the grace period, version records are accessible via direct ID but do not appear in standard history queries (since the parent entity no longer exists).
Large Aggregate Snapshots
A timeline update with many sections and items can produce large JSON snapshots. The system does not impose a hard size limit on snapshots but relies on PostgreSQL TOAST compression to manage storage. If a snapshot exceeds reasonable bounds (monitored via logging), the project's retention policy should be reviewed to prevent excessive storage consumption.
First Version of an Entity
When an entity is first created, there is no "previous state" to snapshot. The first version record is created with a snapshot of the initial state and a version number of 1. The change summary indicates "Initial creation."
Diff Between Non-Adjacent Versions
The diff engine supports comparing any two versions of the same entity, not just adjacent ones. When comparing version 2 and version 8, the diff shows the cumulative changes between those two states, not the intermediate steps. Each individual version's change summary provides the step-by-step narrative.
Changelog Entries for Deleted Items
Changelog entries reference item names as strings, not foreign keys. If an item mentioned in a changelog entry is later deleted, the changelog entry remains unchanged. The string "Ironhold Castle" in the NewItems array persists as a historical record even if the Ironhold Castle item no longer exists.
Empty Changelog Lists
A timeline update with no changelog entries returns an empty array, not an error. This is the default state for all timeline updates until the creator writes their first entry.
Bulk Status Changes and Version History
When a bulk status change affects multiple timeline updates simultaneously, each affected timeline update gets its own individual version snapshot. The snapshots are created within the same transaction as the status change to maintain consistency.
Maximum Version Count
The default retention policy preserves the 50 most recent versions per entity plus the first version (original creation). Projects with frequent edits may reach this limit quickly. When the limit is exceeded, the oldest versions (excluding the first) are pruned on the next version creation. This is a soft limit configurable per project.
Phase & Priority
Version History: Phase 3 (Advanced Collaboration)
Version history depends on the core entity systems (Timelines, Characters, Factions) being stable and in production. It is implemented alongside Comments and Activity Feed as part of the collaboration phase. The version infrastructure must be in place before the Changelog feature can fully leverage it.
Changelog: Phase 4 (Enhanced Features)
Changelog builds on both the timeline system (Phase 1) and optionally references version history (Phase 3) for context. It is a lower-priority enhancement that adds reader-facing communication capabilities. Changelog can be implemented independently of version history since it does not technically depend on it, but the two features are most valuable when used together.