Data Storage
Data Storage
Local-first data storage with IndexedDB, OPFS backup, integrity checks, and (optional) cloud sync
Overview
The Data Storage layer provides a local-first architecture. All user data is persisted in the browser's IndexedDB (with OPFS for full-state snapshots and large binary assets). It serves as the foundation for all CRUD operations, data export/import, schema migrations, and integrity validation. The app works fully offline; cloud sync is opt-in.
Requirements
Functional Requirements
| ID | Requirement | Acceptance Criteria | Priority | Source |
|---|---|---|---|---|
| REQ-DST-001 | The system shall persist all user data locally in IndexedDB with no server dependency for basic CRUD | Create/edit/delete data → changes persist after page reload without network; data intact after browser restart | Must | storageService |
| REQ-DST-002 | The system shall support exporting all user data as a single JSON file | Click Download Full Backup → JSON file downloads; file contains all projects, contacts, owned gear, settings | Must | storageService |
| REQ-DST-003 | The system shall support importing data from a previously exported JSON file with merge logic | Select JSON → preview shown; confirm → data imported and merged; invalid format → error message; duplicates → merged without overwriting | Must | storageService |
| REQ-DST-004 | The system shall validate data integrity on demand and (optionally) on load | Click Verify → 5-category report shown; click Repair → in-place fixes applied without dropping entities; corrupt structures auto-rebuilt | Must | integrityVerifiers + integrityRepairers |
| REQ-DST-005 | The system shall run schema migrations automatically on app startup when data version is outdated | Old schema data → migration runs silently; data accessible after migration; no data loss | Must | migrate |
| REQ-DST-006 | The system shall support factory reset (clear local + cloud-cached data) with single confirmation, while preserving paywall counters and the encryption salt | Click Factory Reset → confirmation dialog → all user data cleared, page reloads. _gl_sys paywall DB and gle_entity_encryption_salt_v1 localStorage key are intentionally preserved so free-tier counters survive and old cloud backups remain decryptable | Must | factoryReset |
| REQ-DST-007 | The system shall normalize data on read to ensure consistent field types and defaults | Load project with missing optional fields → fields filled with defaults; no null reference errors | Must | normalize |
| REQ-DST-008 | The system shall display a storage health panel with IndexedDB availability, OPFS availability, quota usage %, and a healthy/needs-attention status | Open Backup & Data → Storage section → panel shows four signals; bar tints amber at 70 %+, red at 90 %+; the "healthy" boolean flips at 80 % (QUOTA_WARNING_THRESHOLD) | Should | StorageHealthPanel |
| REQ-DST-009 | The system shall encrypt sensitive entity fields at rest | Encrypted fields → unreadable in raw IndexedDB; decryption → fields accessible in app; the per-device salt survives factory reset so cloud backups remain decryptable | Should | entityEncryption |
| REQ-DST-010 | The system shall warn the user when private / incognito browsing is detected and storage may not persist | Open in incognito → detection runs (quota / localStorage / IndexedDB / FileSystem API); if any probe positive, banner appears: "Storage may not persist after this session." Banner dismissible, app still works | Should | privacyModeDetector |
Non-Functional Requirements
| ID | Requirement | Metric | Priority |
|---|---|---|---|
| NFR-DST-001 | Storage read operations shall complete within 50ms for single entities | Latency | Must |
| NFR-DST-002 | Export shall handle databases up to 100MB without browser crash | Capacity | Should |
| NFR-DST-003 | Schema migrations shall be idempotent and backward-compatible | Data safety | Must |
Data Requirements
- Storage engine: IndexedDB (primary), OPFS (large binary assets and full-state snapshots)
- Schema validation: JSON schema definitions in
@data/schema/ - Normalizers: Per-entity normalizers in
@data/normalizers/ - Migrations: Version-based migrations in
@data/migrations/ - Encryption: AES-GCM encryption for sensitive fields via
entityEncryption.js; salt stored inlocalStorage[gle_entity_encryption_salt_v1]and survives factory reset
Constraints & Limits
- IndexedDB storage quota varies by browser (typically 50–80 % of available disk)
- Export file size limited by available RAM (JSON serialization)
- The Storage Health "healthy" threshold is a single value (
QUOTA_WARNING_THRESHOLD = 80 %); the progress bar's red/amber tints (90 % / 70 %) are cosmetic and do not affect any logic
Offline Behavior
- Fully offline — this IS the offline storage layer
- All CRUD, export, import, and migrations work without network
- Cloud sync is opt-in and provides additional cross-device backup when enabled
Dependencies
- Required by: All features use
storageServicefor data persistence - Required by: Sync Engine — sync reads/writes via storage
- Required by: Settings — export/import via Backup & Data tab
Accessing Data Storage
Data storage is automatic. Management lives in Settings → Backup & Data, which is split into eight sub-sections (Storage · Backup · Auto Backups · Cleanup · Data Integrity · Sync · Preferences Export · Factory Reset).
Managing Your Data
Export (Download Full Backup)
- Open Settings → Backup & Data → Backup.
- Click Download full backup.
- A JSON file downloads containing all your projects, gear, contacts, settings, and time-tracking agreements.
Import (Merge a Backup)
- Settings → Backup & Data → Backup.
- Click Import full backup.
- Select a previously exported JSON file.
- Confirm. The imported data is merged with what's already there — duplicates are reconciled, not overwritten.
Restore from Device Auto-Backup
- Settings → Backup & Data → Backup.
- Click Restore from device backup to apply the latest on-device snapshot saved to OPFS.
- For a per-project history, click Browse device backups instead — auto-saved snapshots are shown, and any one can be restored.
Cloud Backups (when sync is enabled)
If cloud sync is on, conflict-resolution snapshots are uploaded to the cloud. They live in the Cloud backups card (Settings → Backup & Data → Backup → Cloud backups). Click Load cloud backups to fetch the list, then Download or Restore any entry.
Check Data Integrity
- Settings → Backup & Data → Data Integrity.
- Verify runs five integrity checks (state structure, entity IDs, references, content, maintenance schedules) and shows a summary.
- Repair runs the same checks and applies in-place fixes — bad foreign keys are cleared, missing IDs are generated, invalid statuses are reset to safe defaults. No entities are dropped by Repair. (For deletion, see Cleanup below.)
- Export Diagnostic Data downloads a JSON bundle of your full state plus the verification result. Useful when filing a support request.
Cleanup (deletes data)
Settings → Backup & Data → Cleanup is the only path that removes content:
- Clear orphaned items — removes contacts not used by any project, owned gear, or company link, and removes custom devices not used by any project / favorite. Profile and production-company contacts are always kept.
- Clear old auto-backups — removes auto-backups older than your retention window. Set it in the Auto Backups sub-section: the field accepts 1–168 hours and starts at 24 h (one day). This page and Settings both called 168 h the default until 2026-09-07 — 168 is the field's maximum, not its starting value.
- Clear browser cache — clears the browser cache and resets the app shell. You may need to sign in again.
Factory Reset
- Settings → Backup & Data → Factory Reset.
- Click Run factory reset, then confirm twice: Continue, then type
RESETand click Confirm. - The app downloads a full backup first. Cancelling that download cancels the reset — nothing is wiped unless the backup was saved. While the reset runs, Confirm reads Resetting… and the whole dialog is inert (Cancel, the confirm field, Escape and click-outside are all disabled) until the page reloads.
- Then it wipes:
- Every user-data IndexedDB (e.g.
gear-list-sync,collab_baselines,gear-list-overrides, …). - The OPFS namespace.
- localStorage, sessionStorage, cookies, browser caches, service workers.
- Every user-data IndexedDB (e.g.
- Three things are deliberately preserved:
- Free-tier counters (
_gl_sysIndexedDB) — projects-created count, first-seen timestamp, calculator-usage count. Without this, clearing data would reset the free-tier limits. - Encryption salt (
gle_entity_encryption_salt_v1in localStorage) — keeps any cloud backups uploaded before the reset readable. - Terra Xplore's local store (
terraxploreIndexedDB) — a deliberately standalone store for the public, login-less/terraxplorepage. It shares no schema, no sync engine and no cloud path with the signed-in app, so resetting the app is not a reason to destroy it (owner decision on #409, 2026-08-09). This page listed only two preserved stores until 2026-09-07.
- Free-tier counters (
- The page reloads with a cache-buster (
?reset=<timestamp>).
The cloud session is revoked before the wipe, but that step is capped at six seconds: a slow or stuck revoke can no longer hold up the local wipe you confirmed. Nothing is lost by proceeding — the wipe deletes the stored session, so the device ends up signed out either way.
If you want a 100 % no-traces wipe (paywall counters and salt included), use the browser's site-data clear directly. Factory reset prioritizes "fresh app, same identity" over "scorched earth."
Storage Warnings
The Storage section shows four signals:
- IndexedDB — Available / Unavailable. Critical: if unavailable, the app falls back to in-memory state (data won't persist).
- OPFS Backup — Available / Not available. Nice-to-have: when missing, the secondary backup target is skipped silently.
- Storage Usage —
MB used / MB quotaand a percentage. Bar turns amber at 70 %+ and red at 90 %+. - Storage is healthy / needs attention —
truewhen IndexedDB is up and quota usage is belowQUOTA_WARNING_THRESHOLD = 80 %.
If you see the warning state, export a full backup, then use Cleanup → Clear orphaned items / Clear old auto-backups to free space.
Private / Incognito Browsing
When the app detects private browsing (one of: tiny quota, localStorage error, IndexedDB blocked, FileSystem API restricted), a banner is shown. The app still works, but your data may not survive closing the tab. Recommended action: download a full backup before working on anything important.
Tips
- Export regularly — JSON backups before major changes are cheap insurance.
- Watch the storage signals — drop unused auto-backups before quota crosses 80 %.
- Turn on cloud sync for automatic cross-device backup (requires an account; free tier limited).
- Run Verify before Repair — Verify is read-only; you can read the report and decide whether Repair is appropriate before applying changes.
- Cleanup ≠ Repair — Repair never deletes entities; only Cleanup does.
Related Documentation
Last Updated: 2026-09-07 Version: 0.790.2
