Snapshots
Snapshots and version mapping
Snapshots are immutable content packages. They capture all statements and categories for an app at a point in time, resolve category filters into concrete statement lists, and serve as the payload delivered to mobile clients.
What a snapshot contains
The payload is a single JSON blob stored in the database:
{
"app": {
"id": "uuid",
"name": "Truth or Dare",
"bundleIds": ["com.psycatgames.truthordare"],
"contentSchemaType": {
"id": 1,
"name": "Truth or Dare",
"structure": { "parts": ["main"], "join": " " }
}
},
"categories": [
{
"id": "uuid",
"name": "party-classics",
"isActive": true,
"hiddenOnIOS": false,
"hiddenOnAndroid": false,
"isPaid": false,
"isExplicit": false,
"emoji": "🎉",
"designerMetadata": {},
"gameMetadata": {},
"localizedTexts": [
{ "locale": "en", "title": "Party Classics", "description": "..." },
{ "locale": "de", "title": "Party-Klassiker", "description": "..." }
],
"statements": ["stmt-id-1", "stmt-id-2", "stmt-id-3"]
}
],
"statements": [
{
"id": "stmt-id-1",
"mode": "classic",
"isActive": true,
"hiddenOnIOS": false,
"hiddenOnAndroid": false,
"tags": ["party"],
"designerMetadata": {},
"gameMetadata": {},
"parts": [
{ "partName": "main", "locale": "en", "text": "What's your biggest fear?" },
{ "partName": "main", "locale": "de", "text": "Was ist deine größte Angst?" }
]
}
],
"metadata": {
"generatedAt": "2025-01-15T10:30:00.000Z",
"locales": ["de", "en", "es", "fr"],
"categoryCount": 5,
"statementCount": 250
}
}
What’s excluded from snapshots:
- Vector embeddings (only used for search/matching in the admin)
- Filter definitions (resolved into statement ID lists at generation time)
- Content metadata like
fullText(reconstructed from parts + schema on the client)
Snapshot lifecycle
Every snapshot goes through three states:
Draft
Initial state after creation. The payload is empty until you click Generate.
Generate resolves all category filters into concrete statement lists:
- For each active category, the filter is evaluated against the statement database
- Matching statement IDs are stored in the category’s
statementsarray - All referenced statements are collected with their localized parts
- Statement and category metadata is validated against the app’s metadata schemas – items that fail validation are excluded (with warnings)
- The payload, checksum, byte size, and locale list are computed
You can regenerate a Draft snapshot to pick up content changes. Once moved to Ready, regeneration is locked.
Ready
Staging state. The payload is frozen – no regeneration allowed. Use this for review and preview before going live.
Move to Ready when you’re confident the content is correct. This is a one-way transition.
Published
Final, immutable state. The snapshot is:
- Locked – cannot be modified or deleted
- Available via the client API for any version mapping pointing to it
- Timestamped with
publishedAt - Checksummed (SHA-256) for integrity verification
Version mapping
The App Releases tab maps app versions (semver) to published snapshots. Each mapping has:
- App – the target app
- App Version – a semver string (e.g.,
1.2.0,2.0.0) - Snapshot – a published snapshot
Constraint: each (app, version) pair maps to exactly one snapshot. But multiple versions can point to the same snapshot.
Version fallback
When a mobile app requests content, the API resolves the snapshot:
- Find all version mappings for the app
- Try exact match against the requested version
- If no exact match, find the highest mapped version ≤ the requested version
- If nothing matches, return 404
This means you don’t need a mapping for every app version. If you map version 1.0.0 to a snapshot, all app versions from 1.0.0 up to (but not including) the next mapped version will receive that snapshot.
Rollback
To roll back content for a specific app version:
- Go to App Releases
- Edit the version mapping
- Point it to the previous published snapshot
The change takes effect immediately (after the 4-hour Redis cache expires, or after the cache key rotates). No need to create a new snapshot.
When to create a new snapshot
Create a new snapshot when:
- You’ve added, removed, or edited statements since the last snapshot
- Category filters have changed
- You want different content for a new app version
- Metadata schema changes require re-validation
You don’t need a new snapshot when:
- You’re just updating version mappings (pointing existing versions to existing snapshots)
- You’re rolling back to a previous snapshot
Snapshot table
The Release Manager shows all snapshots with:
- App name and comment
- Status (Draft / Ready / Published)
- Category count and statement count
- Number of active version mappings pointing to this snapshot
- Payload size (bytes)
- Locales included
- Created and published timestamps