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 by id. Indexed by lastModified, lastAccessed, size.
  • files: Keyed by id (the fileId). Indexed by projectId, 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 of ProjectState 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 each fileId to a File and creating a blob: 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, plus transcripts
  • 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 (with fileId 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.