Persistence
How and where your projects are saved, how auto‑save works, the on‑load hydration process, and the supported import/export formats (.klippy.json and .klippy.bundle.json[.gz]). This page also covers storage limits, automatic cleanup, and private browsing behavior so you can ship a reliable editor across browsers and devices.
What gets saved
The editor persists the current ProjectState
and any imported/generated media files locally in the browser. Persistence is fully client‑side — there is no remote database by default.
- Project snapshots: timeline items, text, captions, zooms, export settings, UI selections, TTS state.
- Media files: imported videos/images/audio, generated assets (e.g. TTS outputs), linked by
fileId
. - Project thumbnails: small data‑URL previews saved with the project metadata for faster project listings.
- Theme and preferences: lightweight UI preferences in
localStorage
(separate from project storage). - Not saved: functions/components (e.g. compiled AI renderers), playback state, ephemeral blob URLs.
Where it’s stored
Project data and files are stored in IndexedDB under the klippy-storage
database with two object stores:
projects
: Keyed byid
. Indexed bylastModified
,lastAccessed
,size
.files
: Keyed byid
(thefileId
). Indexed byprojectId
,lastAccessed
,size
.
Migration: Older builds used a klippy-files
DB with projects
/files
stores. On startup, data is migrated to klippy-storage
and duplicates are cleaned up. If you see both DBs in devtools, that’s expected.
Auto‑save and immediate saves
- Auto‑save: Edits debounce for ~3s and then the project snapshot is written to IndexedDB. This is handled by
useProjectAutoSave
, which serializes a safe subset ofProjectState
and related slices (timeline, transcripts, AI components without functions). - Immediate saves: Certain precise interactions (e.g., timing edits) call an immediate, debounced save via
queueSaveProject()
to capture state without waiting for the 3s timer. - De‑duplication: If the serialized snapshot hasn’t changed since the last write, the save is skipped.
- Safety: Before the page unloads, any queued save is flushed to avoid data loss.
Loading and hydration
Opening a project reads the snapshot from IndexedDB and then re‑hydrates file references into live blob URLs. The hydration step is tolerant of slow IndexedDB opens by retrying reads briefly.
- Snapshot: A clean
ProjectState
is loaded first (no blob URLs stored), then media items are wired up by resolving eachfileId
to aFile
and creating ablob:
URL. - Globals: Blob URLs are cached in a global map to avoid revocation during route changes; they’re revoked on app cleanup.
- AI components: If an AI canvas component had serializable code, it’s re‑compiled on load; otherwise it falls back gracefully.
Import and export
- Project JSON (
.klippy.json
): Exports only the project snapshot (no binary assets). Good for quick backups or inspecting project structure. - Project bundle (
.klippy.bundle.json
or.klippy.bundle.json.gz
): Includes the snapshot plus all referenced assets as base64. The.gz
variant is smaller and preferred for sharing. - Importing: You can import either the plain JSON or a bundle; bundles will re‑create files in IndexedDB and rewrite
fileId
references accordingly. - Where in UI: In the editor header, use “Export JSON” for
.klippy.json
and “Export bundle” for the full archive. Use the “Import” control in Projects to add a JSON/bundle.
Quota management and cleanup
The storage layer continuously monitors quota via navigator.storage.estimate()
and applies policies to prevent failures and keep the app responsive.
- Per‑item limits: ~500MB/file, ~100MB/project snapshot (configurable in policy).
- Proactive cleanup: When usage passes ~80%, the scheduler prunes old/unused files and very old projects automatically. Emergency cleanup can run if a write hits
QuotaExceededError
. - De‑duplication: Projects with duplicate IDs are consolidated (newest wins); the DB is compacted accordingly.
- Health report: Size estimates, counts, and suggestions are computed for UI surfaces that display storage state.
Private browsing and multi‑tab behavior
- Private/Incognito: Some browsers (notably Safari Private) severely restrict IndexedDB. The app detects this and falls back to in‑memory storage. You can edit, but data will not persist after the tab closes.
- Multi‑tab: During DB upgrades, the app may prompt you to close other tabs. Saves are per‑tab and per‑project; keep only one active editing tab for a given project to avoid last‑write‑wins confusion.
Troubleshooting and maintenance
“Out of storage” or export failures
- Delete old projects or unused media from the Projects page.
- Clear caches for FFmpeg WASM and static chunks from the Storage page (
/storage
) if needed. - Check
navigator.storage.estimate()
in DevTools to confirm quota; some environments (iOS PWA) have very small limits.
Reset local database
Use your browser DevTools → Application → IndexedDB and delete klippy-storage
. If you still see the legacy klippy-files
DB, you can delete it too. Code snippet:
indexedDB.deleteDatabase('klippy-storage'); indexedDB.deleteDatabase('klippy-files');
Imported media shows as missing
- Hydration retries briefly for each
fileId
. If it still fails, the asset may have been cleared. - Re‑import the affected asset, or import from a saved
.klippy.bundle.json[.gz]
.
Data model reference (saved fields)
The persisted snapshot includes only serializable fields. Highlights:
- Identity/metadata:
id
,projectName
,createdAt
,lastModified
- Tracks/items:
mediaFiles
,textElements
,zoomElements
- Captions:
captionTracks
,activeCaptionTrackId
,showCaptions
, plustranscripts
- Canvas/time:
resolution
,fps
,aspectRatio
,duration
,contentDuration
,currentTime
- UI/editor:
activeSection
,activeElement
,visibleRows
,timelineZoom
, snapping/markers - Export:
exportSettings
,betweenClipTransitions
,transitionIds
- TTS:
ttsState
including history and last item (withfileId
pointers) - AI components: serialized metadata and optional
code
; functions/components are stripped
Best practices
- Export a bundle periodically for long projects to guard against browser storage resets.
- Keep one editor tab per project to avoid confusing last‑write‑wins scenarios.
- Prefer compressed bundle export (
.gz
) for sharing or moving to another device. - On low‑quota devices, import via bundle and then prune unused media to conserve space.