Skip to content

Storage Schema

Get Based stores all data in the browser. There is no backend database. Two storage mechanisms are used: localStorage for all app data and preferences, and IndexedDB for auto-backup snapshots.

localStorage key reference

Keys are namespaced by profile ID where data is per-profile. {profileId} defaults to "default" for the first profile.

Global keys (not profile-specific)

KeyTypePurpose
labcharts-profilesJSON arrayProfile index: [{ id, name, createdAt }]
labcharts-ai-providerstringActive AI provider: 'anthropic' | 'openrouter' | 'venice' | 'ollama'
labcharts-api-keystringAnthropic API key
labcharts-openrouter-keystringOpenRouter API key
labcharts-venice-keystringVenice AI API key
labcharts-ollama-pii-modelstringOllama model used for PII obfuscation (can differ from main model)
labcharts-anthropic-modelstringSelected Anthropic model ID
labcharts-openrouter-modelstringSelected OpenRouter model ID (e.g., anthropic/claude-sonnet-4-6)
labcharts-venice-modelstringSelected Venice model ID
labcharts-anthropic-modelsJSONCached model list from Anthropic /v1/models
labcharts-openrouter-modelsJSONCached model list from OpenRouter
labcharts-openrouter-pricingJSONCached per-token pricing from OpenRouter
labcharts-venice-modelsJSONCached model list from Venice
labcharts-marker-descJSONCached custom marker descriptions from AI
labcharts-time-formatstring'24h' | '12h' — time display preference
labcharts-debugstringDebug mode flag — enables console output and diff viewer
labcharts-pii-choicesessionStorageOne-time PII warning flag (sessionStorage, not localStorage)

Per-profile keys

KeyTypePurpose
labcharts-{profileId}-importedJSON (may be encrypted)Main data store — see importedData structure below
labcharts-{profileId}-unitsstring'EU' | 'US' unit preference
labcharts-{profileId}-rangeModestring'optimal' | 'reference' chart range mode
labcharts-{profileId}-noteOverlaystring'off' | 'on' — note dots on charts
labcharts-{profileId}-suppOverlaystring'off' | 'on' — supplement bars on charts
labcharts-{profileId}-phaseOverlaystring'off' | 'on' — cycle phase bands on charts
labcharts-{profileId}-chatPersonalitystring'default' | 'house' | 'custom'
labcharts-{profileId}-chatPersonalityCustomJSONCustom personality: { name, icon, promptText, evidenceBased }
labcharts-{profileId}-chatRailOpenstring'true' | 'false' — chat thread rail visibility
labcharts-{profileId}-chatJSON (encrypted)Legacy chat history (migrated to threads on first load)
labcharts-{profileId}-chat-threadsJSONThread index: [{ id, name, createdAt, personalityName, personalityIcon }]
labcharts-{profileId}-chat-t_{id}JSON (encrypted)Per-thread messages: [{ role, content, timestamp }]
labcharts-{profileId}-contextHealthJSONCached AI health dots: { dots, summaries, fingerprints }
labcharts-{profileId}-focusCardJSONCached AI focus card: { fingerprint, text }
labcharts-{profileId}-tourstring'completed' — app tour completion flag
labcharts-{profileId}-cycleTourstring'completed' — cycle tour completion flag
labcharts-{profileId}-onboardedstring'profile-set' | 'dismissed' — onboarding state
labcharts-{profileId}-sexstring'male' | 'female' — profile sex
labcharts-{profileId}-dobstring'YYYY-MM-DD' — date of birth
labcharts-{profileId}-countrystringISO country code
labcharts-{profileId}-zipstringZIP/postal code
labcharts-encryption-enabledstring'true' — encryption at rest enabled
labcharts-{profileId}-enc-saltstringBase64 PBKDF2 salt for this profile's encryption

importedData structure

Stored as JSON at labcharts-{profileId}-imported. This is everything a user can export/import.

js
{
  // Lab results — array of dated snapshots
  entries: [
    {
      date: "2025-03-15",            // ISO date string
      markers: {
        "biochemistry.glucose": 5.2, // category.markerKey → numeric value
        "hormones.testosterone": 18.4,
        // ... all measured markers for this date
      }
    }
    // ... more entries
  ],

  // Standalone notes (independent of lab dates)
  notes: [
    { date: "2025-03-10", text: "Started vitamin D protocol" }
  ],

  // Supplement timeline
  supplements: [
    {
      name: "Vitamin D3",
      dose: "5000 IU",
      startDate: "2025-01-01",
      endDate: null,     // null = ongoing
      color: "#f59e0b",
      notes: ""
    }
  ],

  // Context cards — all nullable (null = not filled by user)
  diagnoses: {                          // Medical Conditions card
    conditions: [
      { name: "Hashimoto's", severity: "major", since: "2020" }
    ],
    note: ""
  },

  diet: {                               // Diet card
    type: "omnivore",
    restrictions: ["gluten-free", "seed-oil-free"],
    pattern: "intermittent_fasting",
    breakfast: "eggs and avocado",
    breakfastTime: "09:00",             // 24h format
    lunch: "salad with protein",
    lunchTime: "13:00",
    dinner: "meat and vegetables",
    dinnerTime: "18:00",
    snacks: "",
    snacksTime: "",
    note: ""
  },

  exercise: {                           // Exercise card
    frequency: "4-5x_week",
    types: ["strength", "walking"],
    intensity: "moderate",
    dailyMovement: "active",
    note: ""
  },

  sleepRest: {                          // Sleep & Rest card
    duration: "7-8h",
    quality: "good",
    schedule: "consistent",
    roomTemp: "cool",                   // circadian-informed
    issues: ["occasional_waking"],
    environment: ["blackout_curtains", "emf_off"],
    practices: ["mouth_taping", "magnesium"],
    note: ""
  },

  lightCircadian: {                     // Light & Circadian card
    amLight: "sunrise_sunlight",        // circadian-informed
    daytime: "outdoor_work",
    uvExposure: "daily",
    evening: ["blue_light_glasses", "dim_lights"],
    screenTime: "low",
    techEnv: ["wifi_off_night"],
    cold: "cold_shower",
    grounding: "daily_earthing",
    mealTiming: ["time_restricted"],
    note: ""
  },

  stress: {                             // Stress card
    level: "moderate",
    sources: ["work", "finances"],
    management: ["meditation", "exercise"],
    note: ""
  },

  loveLife: {                           // Love Life & Relationships card
    status: "partnered",
    relationship: "good",
    satisfaction: "satisfied",
    libido: "normal",
    frequency: "weekly",
    orgasm: "yes",
    concerns: [],
    note: ""
  },

  environment: {                        // Environment card
    setting: "suburban",
    climate: "temperate",
    water: "reverse_osmosis",          // circadian-informed
    waterConcerns: [],
    emf: ["wifi", "smart_meter"],
    emfMitigation: ["router_timer"],
    homeLight: "led_warm",
    air: ["hepa_filter"],
    toxins: [],
    building: "modern",
    note: ""
  },

  // Full-width card — freetext string
  interpretiveLens: "Longevity medicine, quantum biology, functional endocrinology",

  // Health goals — priority-ordered array
  healthGoals: [
    { text: "Optimize testosterone naturally", severity: "major" },
    { text: "Improve sleep quality",           severity: "mild"  },
    { text: "Reduce inflammation markers",     severity: "minor" }
  ],

  // Free-form AI context notes — freetext string
  contextNotes: "Currently experimenting with carnivore diet.",

  // Menstrual cycle — null for male profiles
  menstrualCycle: {
    cycleLength: 28,           // days (auto-calculated if enough periods)
    periodLength: 5,           // days
    regularity: "regular",     // 'regular' | 'irregular' | 'very_irregular'
    flow: "moderate",
    contraceptive: "none",
    conditions: "none",
    periods: [
      {
        startDate: "2025-02-01",
        endDate:   "2025-02-06",
        flow: "moderate",
        symptoms: ["cramps", "fatigue"],
        notes: ""
      }
    ]
  },

  // Custom markers from PDF import — keyed by "category.markerKey"
  customMarkers: {
    "mylab.cortisol": {
      name: "Cortisol (AM)",
      unit: "nmol/L",
      refMin: 170,
      refMax: 720,
      categoryLabel: "My Lab"
    }
  }
}

IndexedDB — auto-backup

Database: labcharts-backups Object store: snapshots Key path: id (auto-increment)

Each snapshot record:

js
{
  id: 42,                          // auto-increment
  createdAt: "2025-03-15T14:22:00.000Z",
  profileId: "default",
  data: {
    importedData: { ... },         // full importedData object
    unitSystem: "EU",
    rangeMode: "optimal",
    suppOverlayMode: "off",
    noteOverlay: "off",
    chatPersonality: "default",
    chatPersonalityCustom: null,
    // ... all per-profile preferences
    threadIndex: [...],            // chat thread index
    threads: {                     // per-thread messages (encrypted strings)
      "t_abc123": "...",
    }
  }
}

Maximum 5 snapshots are kept per profile. The oldest is pruned automatically on each new backup.

Encryption

When encryption is enabled (labcharts-encryption-enabled = 'true'), sensitive localStorage keys are stored as AES-256-GCM ciphertext (base64-encoded) instead of plaintext JSON.

Encrypted key patterns (SENSITIVE_PATTERNS in crypto.js):

  • *-imported — all importedData
  • *-chat — legacy chat history
  • *-chat-t_* — per-thread messages

Keys that are not sensitive (preferences, model selections, pricing caches, profile index) are always stored in plaintext.

The encryption key is derived via PBKDF2 from a user passphrase + per-profile salt (labcharts-{profileId}-enc-salt).

Released under the GPL-3.0 License.