> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getbased.health/llms.txt
> Use this file to discover all available pages before exploring further.

# Module reference

> Reference map for the getbased native ES modules and compatibility exports.

# Module Reference

Modules live under `js/`. At the time of this docs move, the app tree has **332** top-level JavaScript modules. This page is a guided implementation map, not a promise that every small hook/delegate/helper file has its own long section. Grouped by layer — lower layers have no dependencies on higher ones.

***

## Layer 1 — Foundation

### `schema.js`

The single source of truth for all biomarker definitions. No runtime logic — pure data.

**Key exports:**

* `MARKER_SCHEMA` — nested object: `{ categoryKey: { label, icon, markers: { markerKey: { name, unit, refMin, refMax, refMin_f, refMax_f, desc } } } }`. Categories include `biochemistry`, `hormones`, `electrolytes`, `lipids`, `hematology`, `differential`, `thyroid`, `proteins`, `vitamins`, `diabetes`, `inflammation`, `fattyAcids`, `calculatedRatios`, and others
* `UNIT_CONVERSIONS` — keyed by `"category.markerKey"`: `{ type: 'multiply', factor, unit }` for EU→US conversions
* `OPTIMAL_RANGES` — keyed by `"category.markerKey"`: `{ optimalMin, optimalMax, optimalMin_f?, optimalMax_f? }`
* `PHASE_RANGES` — keyed by `"category.markerKey"`: `{ menstrual: { min, max }, follicular: {...}, ovulatory: {...}, luteal: {...} }` — covers `hormones.estradiol`, `hormones.progesterone`, `hormones.lh`, and `hormones.fsh`
* `SPECIALTY_MARKER_DEFS` — re-exported from `adapters.js` as `ADAPTER_MARKERS`. Used by `migrateProfileData()` and `buildMarkerReference()`
* `CHIP_COLORS` — status → CSS color string
* `MODEL_PRICING` — AI model pricing metadata, keyed by provider/model

**Window exports:** none

***

### `adapters.js`

Parser adapter registry for specialty lab detection and normalization. Single source of truth for all specialty marker definitions (OAT, fatty acids, Metabolomix+, BioStarks).

**Key exports:**

* `ADAPTER_MARKERS` — flat object keyed by `"category.markerKey"`: `{ name, unit, refMin, refMax, categoryLabel, icon, group, singlePoint? }` (217 entries). Re-exported from `schema.js` as `SPECIALTY_MARKER_DEFS`
* `getAllAdapterMarkers()` — returns merged marker map from all registered adapters
* `detectProduct(fileName, pdfText)` — runs all adapter `detect()` functions, returns `{ adapter, product: { prefix, label } }` or `null`
* `normalizeWithAdapter(adapter, markers, fileName, pdfText, product)` — dispatches to `adapter.normalize()` for post-AI marker key/category rewriting
* `getAdapterByTestType(testType)` — looks up adapter by AI-returned test type string

**Adapter registry:** array of `{ id, testTypes[], markers, detect?, normalize? }`:

* `fattyAcids` — 29 markers, detects Spadia/ZinZino/OmegaQuant by filename/text, normalizes to product-prefixed categories under "Fatty Acids" sidebar group
* `metabolomix` — no unique markers (reuses OAT + FA), detects Genova Metabolomix+ reports, routes FA add-on markers to `metabolomixFA` prefix
* `oat` — 165 markers, no detect/normalize (AI handles OAT categorization directly)
* `biostarks` — 23 markers, detects BioStarks dried blood spot reports by filename/text, normalizes specialty markers (amino acids, serum FA, intracellular minerals, hormones, vitamins) while passing standard blood markers through to schema categories (hybrid import)

**Window exports:** none

***

### `constants.js`

Static arrays and string constants used across modules.

**Key exports:**

* `CHAT_PERSONALITIES` — array of `{ id, name, icon, promptText }` for the 3 built-in personalities
* `CHAT_SYSTEM_PROMPT` — the base system prompt string injected into all AI chat requests
* `COUNTRY_LATITUDES` — `{ countryCode: latitudeBand }` (\~70 countries, 5 bands: arctic/north/temperate/subtropical/tropical)
* `FAKE_DATA` — synthetic name/address/DOB data for PII obfuscation
* Per-card option arrays: `SLEEP_DURATIONS`, `SLEEP_QUALITIES`, `SLEEP_SCHEDULES`, `SLEEP_ISSUES`, `SLEEP_ENVIRONMENTS`, `SLEEP_PRACTICES`, `LIGHT_AM`, `LIGHT_DAYTIME`, `LIGHT_UV`, `LIGHT_EVENING`, `LIGHT_SCREEN`, `LIGHT_TECH_ENV`, `LIGHT_COLD`, `LIGHT_GROUNDING`, `LIGHT_MEAL_TIMING`, `STRESS_LEVELS`, `STRESS_SOURCES`, `STRESS_MANAGEMENT`, `ENV_SETTING`, `ENV_CLIMATE`, `ENV_WATER`, `ENV_WATER_CONCERNS`, `ENV_EMF`, `ENV_EMF_MITIGATION`, `ENV_HOME_LIGHT`, `ENV_AIR`, `ENV_TOXINS`, `ENV_BUILDING`, `LOVE_STATUS`, `LOVE_RELATIONSHIP`, `LOVE_SATISFACTION`, `LOVE_LIBIDO`, `LOVE_FREQUENCY`, `LOVE_ORGASM`, `LOVE_CONCERNS`, `PERIOD_SYMPTOMS`, `DIET_TYPES`, `DIET_RESTRICTIONS`, `DIET_PATTERNS`, `EXERCISE_FREQUENCIES`, `EXERCISE_TYPES`, `EXERCISE_INTENSITIES`, `EXERCISE_DAILY_MOVEMENT`

**Window exports:** none

***

### `state.js`

Single shared mutable state object. Import `state` to read or write. No logic.

**Key exports:**

* `state` — the mutable singleton:
  ```js theme={null}
  {
    chartInstances: {},        // Chart.js instances by element id
    markerRegistry: {},        // runtime marker lookup cache
    importedData: {            // all user data for the active profile
      entries: [],             // lab results: [{ date, markers: { "cat.key": value } }]
      notes: [],               // [{ date, text }]
      supplements: [],         // supplement timeline entries
      healthGoals: [],         // [{ text, severity }]
      diagnoses: null,         // { conditions: [{ name, severity, since? }], note }
      diet: null,              // structured meal object
      exercise: null,          // structured exercise object
      sleepRest: null,         // structured sleep object
      lightCircadian: null,    // structured light/circadian object
      stress: null,            // structured stress object
      loveLife: null,          // structured relationship/sexual health object
      environment: null,       // structured environment object
      interpretiveLens: '',    // freetext string
      contextNotes: '',        // freetext string
      menstrualCycle: null,    // { cycleLength, periodLength, regularity, flow, periods[] }
      customMarkers: {}        // { "cat.key": { name, unit, refMin, refMax, categoryLabel } }
    },
    unitSystem: 'EU',          // 'EU' | 'US'
    currentProfile: 'default', // active profile id
    profiles: null,            // loaded profiles array
    profileSex: null,          // 'male' | 'female' | null
    profileDob: null,          // 'YYYY-MM-DD' | null
    chatHistory: [],           // current thread messages
    chatThreads: [],           // thread index array
    currentThreadId: null,
    currentChatPersonality: 'default',
    dateRangeFilter: 'all',    // 'all' | '6m' | '1y' | '2y'
    rangeMode: 'optimal',      // 'optimal' | 'reference'
    suppOverlayMode: 'off',    // 'off' | 'on'
    noteOverlayMode: 'off',
    phaseOverlayMode: 'off',
    compareDate1: null,
    compareDate2: null,
  }
  ```

`window._labState = state` is set for debugging in the browser console.

**Window exports:** none (state is accessed via import)

***

### `utils.js`

Shared pure utility functions.

**Key exports:**

* `escapeHTML(str)` — escapes `<>&"'` for safe innerHTML insertion
* `hashString(str)` — djb2 hash, returns integer
* `getStatus(value, refMin, refMax)` — `'normal'` | `'high'` | `'low'` | `'missing'`. Returns `'normal'` when refs are `null`
* `formatValue(value, unit)` — formats a numeric value with appropriate decimal places
* `showNotification(message, type)` — toast notification (`'info'` | `'success'` | `'error'` | `'warning'`)
* `showConfirmDialog(message)` — returns `Promise<boolean>`, styled confirm dialog
* `linearRegression(points)` — `{ slope, intercept, r2 }` from `[{ x, y }]` array
* `hasCardContent(obj)` — generic empty-card gate: returns `true` if any field has content (strings non-empty, arrays non-empty, `note` trimmed). Used by `buildLabContext()` for 7 context card gates
* `bindModalSyncRefresh(opts)` / `bindDetailModalSyncRefresh(kind, refresh)` / `bindDetachedModalSyncRefresh(opts)` — shared `labcharts-sync-applied` modal refresh bindings with dirty-form guards and scroll restoration

**Window exports:** `showNotification`, `showConfirmDialog`, `setDebugMode`, `setPIIReviewEnabled`, `hasCardContent`

***

## Layer 2 — Core Services

### `modal-lifecycle.js`

Shared modal backdrop, focus-trap, and body-scroll lock helpers. Owns the cross-module scroll-lock registry used by Light & Sun modals; `sun.js` re-exports the legacy names for compatibility.

**Key exports:**

* `wireBackdropClose(overlay, closeFn?)` / `_wireBackdropClose(overlay, closeFn?)` — backdrop click close wiring with inside-click guard
* `trapModalFocus(overlay)` — locks background scroll, restores focus/overflow on removal, and handles Escape close

**Window exports:** none directly; `sun.js` still exposes compatibility globals.

### `theme.js`

Theme management and Chart.js color helpers.

**Key exports:**

* `getTheme()` / `setTheme(theme)` / `toggleTheme()` — `'dark'` | `'light'`; `setTheme` sets `data-theme` on `<html>`
* `getChartColors()` — reads live CSS custom properties and returns a chart color config object
* `formatDateLabel(dateStr)` — formats ISO date for chart x-axis
* `getTimeFormat()` / `setTimeFormat(fmt)` — `'24h'` | `'12h'`, stored in `labcharts-time-format`
* `formatTime(timeStr)` — formats a 24h time string for display using the active format
* `parseTimeInput(input)` — accepts both `'14:30'` and `'2:30 PM'`, always returns 24h format

**Window exports:** `toggleTheme`

***

### `hardware.js`

GPU detection and model fitness advisor for Local AI settings. Pure functions, no DOM manipulation.

**Key exports:**

* `detectHardware()` — async, returns `{ gpu: { name, vram, unified, renderer, source }, ram: { gb, source }, cpuThreads }`. GPU detected via WebGL `WEBGL_debug_renderer_info` matched against 75-entry `GPU_DB` (Apple Silicon M1–M4, NVIDIA RTX 30/40/50, AMD RX 6000/7000, Intel Arc, Vega)
* `assessModel(modelObj, hardware)` — returns `{ tier, badge, vramNeeded, label }` where tier is `'fits'` / `'tight'` / `'toobig'` / `'unknown'`
* `assessFitness(modelName)` — rates a model for getbased lab analysis: `{ tier, note }` where tier is `'recommended'` / `'capable'` / `'underpowered'` / `'inadequate'`. Benchmarked against Sonnet 4.6
* `getBestModel(modelDetails, hardware)` — picks the highest-fitness installed model that fits in VRAM
* `getUpgradeSuggestion(modelDetails, hardware)` — returns a pull recommendation if no installed model is "recommended" tier
* `saveHardwareOverride(vram)` / `getHardwareOverride()` — manual VRAM override in localStorage

**Window exports:** none (imported by `settings.js`)

***

### `api.js`

AI provider routing and model management. All AI calls flow through `callClaudeAPI`.

**Key exports:**

* `callClaudeAPI(opts)` — main router: delegates to the active provider based on `getAIProvider()`
* `callOpenRouterAPI(opts)` — OpenRouter via `callOpenAICompatibleAPI`
* `callRoutstrAPI(opts)` — Routstr via `callOpenAICompatibleAPI`
* `callPPQAPI(opts)` — PPQ via `callOpenAICompatibleAPI`
* `callVeniceAPI(opts)` — Venice AI via `callOpenAICompatibleAPI`
* `callOpenAICompatibleLocalAPI(opts)` — Local AI via shared `callOpenAICompatibleAPI` helper
* `callOpenAICompatibleAPI(endpoint, key, model, providerName, opts, extraHeaders)` — shared OpenAI-format helper
* `getAIProvider()` / `setAIProvider(provider)` — `'openrouter'` | `'routstr'` | `'ppq'` | `'venice'` | `'ollama'` (internal key for Local)
* `hasAIProvider()` — returns `true` if any provider is configured; gates all 7 AI features
* `getOpenRouterModel()`, `getRoutstrModel()`, `getPPQModel()`, `getVeniceModel()`, `getOllamaMainModel()`
* `fetchOpenRouterModels()`, `fetchRoutstrModels()`, `fetchPPQModels()`, `fetchVeniceModels()` — dynamic model lists
* `getModelPricing(modelId)` — checks dynamic OpenRouter pricing cache, falls back to `MODEL_PRICING`
* `OPENROUTER_CURATED` — whitelist of latest-gen medically capable models (prefix-matched)
* `OPENROUTER_EXCLUDE` — blocklist filtering codex/audio/image/oss variants

**Window exports:** `getRoutstrNodeUrl` (used by settings UI for node URL access)

***

### `api-transport.js`

Shared AI API transport helpers used by `api.js`.

**Key exports:**

* `fetchWithRetry(url, options, config)` — wraps fetch with request timeout, transient network retry, and 429 backoff
* `createProxyFetch(shouldUseProxy)` — builds the Custom API proxy fetch wrapper
* `readWithStallTimeout(reader, label)` — guards streaming readers from hanging between chunks
* `STREAM_STALL_TIMEOUT_MS`, `FETCH_REQUEST_TIMEOUT_MS`, `AI_IMPORT_REQUEST_TIMEOUT_MS` — public timeout constants re-exported by `api.js`

**Window exports:** none

***

### `cashu-wallet.js`

In-app Cashu eCash wallet for decentralized AI payments. Proofs stored in IndexedDB, BIP-39 seed encrypted in localStorage.

**Key exports:**

* `getMintUrl()` / `setMintUrl(url)` — configured Cashu mint
* `generateWalletSeed()` — creates 12-word BIP-39 mnemonic
* `restoreWalletFromSeed(mnemonic)` — restores proofs from mint via `batchRestore`
* `getWalletBalance()` — sum of unspent proofs for current mint
* `createFundingInvoice(amountSats)` / `checkFundingStatus(quoteId)` — Lightning deposit flow
* `receiveToken(tokenString)` — deposit a Cashu token
* `depositToNode(nodeUrl, amountSats, existingKey)` — swap proofs for a node session key
* `sendAsToken(amountSats)` — withdraw as shareable Cashu token
* `createWithdrawQuote(bolt11)` / `executeWithdraw(quoteId)` — Lightning withdrawal
* `withdrawToAddress(address, amountSats)` — LNURL-pay withdrawal
* `recoverPendingDeposit()` / `recoverPendingWithdraw()` — failed operation recovery
* `exportWallet()` / `importWallet(tokenString)` — backup/restore
* `getFeePct()` — current fee percentage (0 during beta)

**Window exports:** all functions prefixed with `cashu` (e.g., `cashuGetBalance`, `cashuDepositToNode`)

***

### `nostr-discovery.js`

Discovers Routstr AI nodes via Nostr relays (Kind 38421 events).

**Key exports:**

* `discoverNodes(forceRefresh)` — queries relays in parallel, deduplicates, health-checks, returns sorted node array
* `getSelectedNodeUrl()` / `setSelectedNodeUrl(url)` — persisted node selection
* `clearNodeCache()` — force re-discovery on next call

**Window exports:** `nostrDiscoverNodes`, `nostrGetSelectedNode`, `nostrSetSelectedNode`, `nostrClearNodeCache`

***

## Layer 3 — Data & Profile

### `profile.js`

Profile lifecycle and settings persistence.

**Key exports:**

* `profileStorageKey(profileId, suffix)` — builds `labcharts-{profileId}-{suffix}`
* `createDefaultProfileData()` — returns the empty importedData shape used for new/metadata-only profile sync
* `loadProfiles()` / `saveProfiles()` — profile index CRUD
* `switchProfile(profileId)` — loads importedData from localStorage for the given profile
* `deleteProfile(profileId)` — removes all keys for the profile
* `migrateProfileData()` — upgrades legacy field formats on load (old `sleepCircadian` → `sleepRest` + `lightCircadian`, old `fieldExperts`/`fieldLens` → `interpretiveLens`, initializes missing fields with `null`)
* `getProfileLocation()` / `setProfileLocation(country, zip)` — country+ZIP storage
* `getLatitudeFromLocation()` — looks up latitude band from `COUNTRY_LATITUDES`
* `renderProfileDropdown()` — renders the profile selector UI

**Window exports:** `openProfileEditor`, `switchProfile`, `deleteProfile`, `addProfile`, `saveProfile`

***

### `data.js`

The central data pipeline. Every view gets its data from `getActiveData()`.

**Key exports:**

* `getActiveData()` — deep-clones `MARKER_SCHEMA`, merges custom markers, applies sex-specific ranges, populates `values[]` arrays from `importedData.entries`, computes ratios + PhenoAge + cycle phases, applies unit conversion. Returns `data` object with `{ dates[], dateLabels[], categories, phaseLabels? }`
* `saveImportedData()` — persists `state.importedData` to localStorage (or encrypted store), triggers backup
* `buildMarkerReference()` — compact JSON of all known markers for AI system prompts (PDF import)
* `filterDatesByRange(data)` — applies `state.dateRangeFilter` to dates + values arrays in-place
* `registerRefreshCallback(fn)` — registers the refresh function from `app-event-listeners.js`
* `getFocusCardFingerprint()` — djb2 hash of all entries + all 9 context cards + sex + DOB
* Compatibility re-exports from `marker-analysis.js` keep existing `window.*` helper access stable.

**Window exports:** `saveImportedData`, `clearAllData` (via export.js), `filterDatesByRange`

***

### `marker-analysis.js`

Read-only marker range, status, and trend helpers. This module owns pure marker analysis so chart, dashboard, AI context, and export code do not need to import the storage/sync-heavy `data.js` when they only need derived marker calculations.

**Key exports:**

* `getEffectiveRange(marker)` — returns `{ min, max }` respecting `state.rangeMode`
* `getEffectiveRangeForDate(marker, dateIndex)` — phase-aware range lookup; falls back to `getEffectiveRange()`
* `getPhaseRefEnvelope(marker)` — widest span across all cycle phases for chart ref bands
* `getLatestValueIndex(values)` — index of the latest non-null marker value
* `countFlagged(markers)` / `getAllFlaggedMarkers(data)` — out-of-range marker helpers
* `statusIcon(status)` — status-to-symbol mapping used by category cards
* `detectTrendAlerts(data)` — sudden-change (25% of ref range, 2+ values) and linear-regression (slope >0.02, R²>0.5 for 4+ points) alerts
* `getKeyTrendMarkers(filteredData)` — dashboard key-trend selection

**Window exports:** none directly; `data.js` keeps compatibility exports.

***

### `pii.js`

Two-path PII obfuscation for PDF text before AI submission, with streaming review modal.

**Key exports:**

* `sanitizeWithOllamaStreaming(pdfText, onChunk, signal)` — preferred path: SSE streaming via OpenAI-compatible API, calls `onChunk(delta)` per token, supports `AbortSignal`
* `sanitizeWithOllama(pdfText)` — non-streaming path: same endpoint, used when review is disabled
* `obfuscatePDFText(pdfText)` — regex fallback: label-based + pattern-based replacement, returns `{ obfuscated, original, replacements }`
* `reviewPIIBeforeSend(originalText, { obfuscatedText, streamFn })` — review modal: streaming mode (pass `streamFn`) or static mode (pass `obfuscatedText`). Returns edited text or `'cancel'`
* `checkOllamaPII()` — checks PII server availability via `/v1/models`
* `getOllamaConfig()` / `saveOllamaConfig(config)` — local AI config: `{ url, model, apiKey }`
* `showPIIDiffViewer(original, obfuscated, replacements)` — debug diff viewer (requires `labcharts-debug` flag)

**Window exports:** `setOllamaPIIModel`

***

### `image-utils.js`

Shared image utilities for chat attachments and PDF image fallback.

**Key exports:**

* `resizeImage(file, maxDim?, quality?)` — resizes an image to fit within `maxDim` pixels (default 1024), returns `{ base64, mediaType, width, height, origWidth, origHeight, quality_warnings }`
* `isValidImageType(type)` — validates MIME type against `image/(jpeg|png|gif|webp)`
* `formatImageBlock(base64, mediaType, provider)` — returns a provider-specific image content block (`type:'image_url'` for OpenAI-compatible providers)
* `buildVisionContent(imageBlocks, text, provider)` — assembles a content array with image blocks + text block

**Internal:**

* `analyzeImageQuality(ctx, width, height)` — canvas-based pixel sampling (\~100k samples): brightness (dark/overexposed) + Laplacian variance (blur detection). Returns `string[]` warnings

**Window exports:** `resizeImage`, `isValidImageType`, `formatImageBlock`, `buildVisionContent`

***

## Layer 4 — Domain Modules

### `charts.js`

Chart.js configuration and all custom plugins.

**Key exports:**

* `createLineChart(canvasId, marker, data, phaseLabels?)` — creates a Chart.js line chart with all plugins applied
* `destroyAllCharts()` — destroys all instances in `state.chartInstances` to prevent memory leaks
* Five Chart.js plugins (registered globally):
  * `refBandPlugin` — shaded reference range band
  * `optimalBandPlugin` — green dashed optimal range band
  * `noteAnnotationPlugin` — yellow dot annotations at note dates with hover tooltips
  * `supplementBarPlugin` — colored timeline bars for supplements
  * `phaseBandPlugin` — cycle phase vertical shading (menstrual=red, follicular=blue, ovulatory=purple, luteal=yellow, 8% opacity)

**Window exports:** none

***

### `notes.js`

Standalone note management (independent of lab entries).

**Key exports:**

* `openNoteEditor(date?)` — opens the note editor modal, pre-filled if `date` provided
* `saveNote()` — saves the current editor content to `importedData.notes`
* `deleteNote(date)` — removes a note by date

**Window exports:** `openNoteEditor`, `saveNote`, `deleteNote`

***

### `supplements.js`

Supplement and medication timeline, editor, ingredient tracking, and AI impact analysis.

**Key exports:**

* `openSupplementsEditor(index?)` — opens the supplement editor modal
* `saveSupplement(idx)` — persists current form state to `importedData.supplements`
* `deleteSupplement(idx)` — removes by index
* `renderSupplementsSection()` — dashboard timeline bars
* `renderSupplementImpact(supp, editIdx)` — per-supp impact card (shimmer → cached AI summary)
* `computeSupplementImpact(supp, markerKey, ...)` — before/after mean comparison for one marker
* `computeAllImpacts(supp, data)` — impact vectors across all markers, sorted by |pctChange|
* `parseAmount(str)` — extract `{value, unit}` from "890mg" / "5,4 mg" / "500 IU" (handles comma decimals)
* `effectiveTimesPerDay(ing, supp)` — row override wins, else supp-level default
* `ingredientDailyTotal(ing, supp)` — computed `amount × effectiveTimesPerDay`

**Ingredient data model:** each supp has an optional outer `timesPerDay` (default multiplier); each `ingredient` row has `{name, amount, timesPerDay?}` where `timesPerDay` is an optional per-row override. Daily total = `amount × (row.timesPerDay ?? supp.timesPerDay)`.

**Impact cache:** per-supp cache in `labcharts-{profileId}-suppImpact` keyed by supp name with a fingerprint that includes dosage, outer `timesPerDay`, per-ingredient fields, periods, and lab dates — any edit auto-invalidates only that supp's cache entry. Concurrent renders are coalesced via a 50ms debounced queue (`scheduleAnalyze` → `flushAnalyses`).

***

### `cycle.js`

Menstrual cycle tracking, helpers, and dashboard rendering.

**Key exports:**

* `getCyclePhase(dateStr, mc)` — `{ cycleDay, phase, phaseName }` for a date against a cycle object
* `getNextBestDrawDate(mc)` — next early follicular window (days 3–5)
* `getBloodDrawPhases(mc, dates)` — maps lab dates to phases
* `calculateCycleStats(periods)` — auto-computes `{ cycleLength, periodLength, regularity }` from period log
* `detectPerimenopausePattern(mc, dob)` — flags perimenopause pattern (age 35+, 4+ periods, 2+ of 4 indicators)
* `detectCycleIronAlerts(mc, data)` — cross-references heavy flow with ferritin/hemoglobin/iron
* `openMenstrualCycleEditor()` — opens the cycle editor modal
* `saveMenstrualCycle()` — saves to `importedData.menstrualCycle`, triggers cycle tour
* `renderMenstrualCycleSection(data)` — renders the full cycle dashboard section
* `startCycleTour(auto)` — triggers the 8-step cycle spotlight tour

**Window exports:** `openMenstrualCycleEditor`, `saveMenstrualCycle`, `addPeriod`, `deletePeriod`, `startCycleTour`

***

### `context-cards.js`

Lifestyle context card rendering plus AI health dots, Interpretive Lens / Knowledge Base dashboard rows, dashboard CTA pills, context change history, and legacy compatibility exports for summary/editor helpers.

**Key exports:**

* `renderProfileContextCards(data)` — renders the 3-column context card grid on the dashboard
* `renderInterpretiveLensSection()` — renders the lens row (when set) + KB status row (when configured) + AI personalize CTA pill + Data protection CTA pill
* `renderKnowledgeBaseSection()` — pure-render helper for the KB status row (returns `''` when no library configured)
* `renderDataProtectionCta(stateOverride?)` — pure render of the data-protection pill; accepts an explicit state override for testability
* `openPersonalizeAIPicker()` — 2-card picker (Lens / Knowledge Base) shown when both are unset
* `openDataProtectionPicker()` — 3-card picker (Encryption / Sync / Auto-backup) with configured cards grayed out
* `triggerDNAFilePicker()` — programmatic file input trigger that routes through `window.handleDNAFile`; used by the genetics empty-state stub
* `loadContextHealthDots()` — async; fetches AI health ratings for stale cards (per-card fingerprint caching)
* `getCardFingerprint(key)` — djb2 hash of lab data + card data + sex + DOB for cache invalidation
* Per-card editor functions: `openDiagnosesEditor`, `openDietEditor`, `openExerciseEditor`, `openSleepEditor`, `openLightEditor`, `openStressEditor`, `openLoveLifeEditor`, `openEnvironmentEditor`, `openHealthGoalsEditor`; Medical History helpers are compatibility re-exports from `context-card-medical-history-editor.js`, and lifestyle/simple editors are compatibility re-exports from `context-card-lifestyle-editors.js`
* Per-card save functions: `saveDiagnoses`, `saveDiet`, `saveExercise`, `saveSleep`, `saveLight`, `saveStress`, `saveLoveLife`, `saveEnvironment`, `saveHealthGoals`; `saveDiagnoses` is a compatibility re-export from `context-card-medical-history-editor.js`, and lifestyle/simple saves are compatibility re-exports from `context-card-lifestyle-editors.js`
* `selectCtxOption(el, group, multi)` — compatibility re-export from `context-card-editor-ui.js`
* `getSelectedOption(group)` — reads selected value from a `.ctx-btn-group`
* Summary helpers such as `getDietSummary()` and `getEnvironmentSummary()` — compatibility re-exports from `context-card-summaries.js`
* `debounceContextNotes()` — auto-saves the free-form context notes textarea
* `recordChange(field)` — snapshots a context field and appends a timestamped entry to `importedData.changeHistory` (dedup: same-day overwrite, identical skip, 200 cap)

**Window exports:** all open/save functions, `selectCtxOption`, `addCondition`, `deleteCondition`, `addGoal`, `deleteGoal`, `syncDiagnosesNote`, `openInterpretiveLensEditor`, `saveInterpretiveLens`, `renderKnowledgeBaseSection`, `openPersonalizeAIPicker`, `openDataProtectionPicker`, `triggerDNAFilePicker`

***

### `context-card-summaries.js`

Context card metadata, filled-state checks, dashboard summary strings, and the EMF assessment launcher/summary. Kept side-effect-light so card rendering, chat/onboarding callers, and tests can share one source of truth without pulling in every editor modal.

**Key exports:** `CONTEXT_CARD_KEYS`, `getContextCardDefs()`, `isContextFilled(key)`, `getConditionsSummary()`, `getDietSummary()`, `getExerciseSummary()`, `getSleepSummary()`, `getLightCircadianSummary()`, `getStressSummary()`, `getLoveLifeSummary()`, `getEnvironmentSummary()`, `getGoalsSummary()`, `getEMFAssessments()`, `renderEMFAssessmentLauncher()`.

**Window exports:** none directly; legacy globals are assigned by `context-cards.js`.

***

### `context-card-editor-ui.js`

Shared context editor modal shell and field-control render/read helpers used by the 9 card editors.

**Key exports:** `renderContextEditorModal()`, `renderSelectField()`, `selectCtxOption()`, `getSelectedOption()`, `renderTagsField()`, `toggleCtxTag()`, `getSelectedTags()`, `renderNoteField()`, `contextEditorActions()`.

**Window exports:** none directly; legacy globals are assigned by `context-cards.js`.

***

### `context-card-medical-history-editor.js`

Medical History editor module for personal conditions and family history. Owns the condition autocomplete, relative allowlist, add/delete flows, modal rendering, note sync, and save/clear handlers for `importedData.diagnoses`.

**Key exports:** `configureMedicalHistoryEditor()`, `openDiagnosesEditor()`, `renderDiagnosesModal()`, `filterConditionSuggestions()`, `selectConditionSuggestion()`, `closeSuggestionsOnClickOutside()`, `syncDiagnosesNote()`, `addCondition()`, `editCondition()`, `cancelConditionEdit()`, `deleteCondition()`, `addFamilyHistoryEntry()`, `editFamilyHistoryEntry()`, `cancelFamilyHistoryEdit()`, `deleteFamilyHistoryEntry()`, `filterFamilyConditionSuggestions()`, `selectFamilyConditionSuggestion()`, `saveDiagnoses()`, `closeDiagnoses()`, `clearDiagnoses()`.

**Window exports:** none directly; legacy globals are assigned by `context-cards.js`.

***

### `context-card-lifestyle-editors.js`

Lifestyle/simple context editors for Diet & Digestion, Sleep & Rest, Light & Circadian, Exercise, Stress, Love Life, Environment, Health Goals, Interpretive Lens, and the diet contaminant modal. Uses `configureLifestyleContextEditors()` so `context-cards.js` remains the owner of `recordChange()` and `saveAndRefresh()` while the editor module owns modal rendering and save/clear handlers.

**Key exports:** `configureLifestyleContextEditors()`, `renderDietContaminantsBadge()`, `openDietEditor()`, `saveDiet()`, `clearDiet()`, `openSleepRestEditor()`, `saveSleepRest()`, `clearSleepRest()`, `openLightCircadianEditor()`, `saveLightCircadian()`, `clearLightCircadian()`, `openExerciseEditor()`, `saveExercise()`, `clearExercise()`, `openStressEditor()`, `saveStress()`, `clearStress()`, `openLoveLifeEditor()`, `saveLoveLife()`, `clearLoveLife()`, `openEnvironmentEditor()`, `saveEnvironment()`, `clearEnvironment()`, `openHealthGoalsEditor()`, `renderHealthGoalsModal()`, `addHealthGoal()`, `deleteHealthGoal()`, `closeHealthGoals()`, `clearHealthGoals()`, `openInterpretiveLensEditor()`, `saveInterpretiveLens()`, `clearInterpretiveLens()`, `showDietContaminantsModal()`.

**Window exports:** none directly; legacy globals are assigned by `context-cards.js`.

***

## Layer 5 — Feature Modules

### `dna.js`

DNA raw data import: client-side parser, storage, dashboard section, AI context assembly.

**Key exports:**

* `detectDNAFile(text)` — detects format from file header: `'ancestry'` | `'23andme'` | `'livingdna'` | `'csv'` | `null`
* `isDNAFile(file)` — checks filename patterns for known DNA providers
* `isDNAFileByContent(file)` — async, reads first 500 bytes to detect DNA format by content
* `parseDNAFile(file)` — async, runs Web Worker parser, matches against `data/snp-health.json` (41 SNPs), resolves APOE haplotype, returns enriched matches with effect/note per genotype
* `saveGeneticsData(profileData, result)` — stores matched SNPs + APOE in `importedData.genetics`
* `deleteGeneticsData(profileData)` — removes genetics data
* `buildGeneticsContext(genetics, activeMarkerKeys)` — serializes genetics for AI context, filtered to SNPs relevant to active markers
* `renderGeneticsSection()` — full genetics interpretation section for classic/mobile dashboard contexts; returns an in-context "Add your DNA data" empty-state stub (wired to `triggerDNAFilePicker`) when no SNPs/mtDNA exist
* `handleDNAFile(file)` — full import flow: parse → preview modal → confirm → save

Supports: AncestryDNA (2-column alleles), 23andMe, MyHeritage, FTDNA, Living DNA. Genotype reversal handles strand ambiguity (CT ↔ TC).

**Window exports:** `isDNAFile`, `isDNAFileByContent`, `handleDNAFile`, `confirmDNAImport`, `closeDNAImportPreview`, `deleteGeneticsData`, `_buildGeneticsContext`, `_getRelevantSNPs`

***

### `pdf-import.js`

PDF/image/text-to-lab-data import pipeline and confirm/save merge logic.

**Key exports:**

* `extractPDFText(file)` — pdf.js text extraction with x/y coordinates, returns page-aware formatted text
* `parseLabPDFWithAI(pdfText)` — sends text + `buildMarkerReference()` to AI; maps lab results to marker keys
* `handleImageFile(file)` — imports lab reports from JPG/PNG/WebP images via AI image pipeline
* `handleBatchPDFs(files)` — sequential multi-file import with per-file confirm/skip
* `showImportPreview(parsed)` — re-export from `pdf-import-review.js`
* `confirmImport(parsed)` — merges parsed data into `importedData.entries`
* `setupDropZone()` — legacy eager drop-zone binding retained for compatibility; page shells use `import-drop-zone.js` so the PDF import module stays lazy-loaded

**Window exports:** `confirmImport`, `skipImport`, `importNextPDF`, `syncImportStatusFab`, `handleImportStatusClick`, `isImportRunning`

***

### `pdf-import-preflight.js`

Pre-AI import checks for duplicate PDF hashes, previous-model mismatch prompts, and unsupported specialty-test warnings.

**Key exports:**

* `runPreflightChecks(pdfText, fileName)` — returns `false` when the user cancels before token-spending import work starts
* `normalizeImportModelId(id)` — shared model ID canonicalization for cross-provider import mismatch checks

**Window exports:** none directly; `pdf-import.js` calls this during PDF/batch import.

***

### `pdf-import-progress.js`

Import progress bar, header import-button status, and batch progress rendering.

**Key exports:**

* `showImportProgress(step, fileName)` / `hideImportProgress(reason)` — render and clear the import progress UI
* `showBatchImportProgress(step, fileName, current, total)` — render per-file batch progress
* `updateImportProgressPct(pct)` — streaming AI progress update hook
* `syncImportStatusFab()` / `handleImportStatusClick()` / `isImportRunning()` — status bridge used by `pdf-import.js` window exports; the legacy function name now syncs the header import button

**Window exports:** via `pdf-import.js`

***

### `pdf-import-marker-normalization.js`

Shared AI marker normalization for PDF text import and image import.

**Key exports:**

* `normalizeParsedImportMarkers(parsed, options)` — sanitizes AI marker keys, runs product/test-type adapters, applies specialty guard rules, reconciles marker mappings, and returns `{ testType, markers }`

**Window exports:** none directly; `pdf-import.js` calls this after AI JSON parsing.

***

### `pdf-import-persistence.js`

Durable imported-data persistence helpers for PDF import flows.

**Key exports:**

* `snapshotImportedData()` / `restoreImportedDataSnapshot(snapshot)` — rollback guard used when durable import saves fail
* `refreshImportedDataViews()` — rebuilds sidebar/header and restores the current route after imported data changes
* `removeImportedEntry(date)` / `renameImportedEntryDate(oldDate)` — entry delete and date-edit mutations with tombstones, immediate sync save, rollback, and success refresh

**Window exports:** via `pdf-import.js`

***

### `pdf-import-review.js`

Import review modal rendering and interaction state.

**Key exports:**

* `showImportPreview(parsed)` — modal with matched, new custom, unmatched, and excluded marker rows
* `applyManualImportDate(date)` — updates pending review date and confirm button state
* `mapUnmatchedMarkerInput(input)` / `toggleImportRow(button)` — review-row mapping and exclusion controls
* `getPendingImport()` / `getExcludedImportIndices()` — narrow state access used by `confirmImport()`
* `showImportPreviewAsync(result, current, total)` — batch import preview promise bridge

**Window exports:** via `pdf-import.js`

***

### `import-drop-zone.js`

Lazy drop-zone event binding shared by Dashboard, Labs, and mobile dashboard shells. It imports `loadPdfImport()` from `import-loader.js`, classifies dropped files only after interaction, and routes JSON, lab PDFs/images, text imports, and DNA files to the same handlers used by the file input path.

**Key exports:**

* `setupDropZone()` — idempotently binds click, drag, and drop handlers to `#drop-zone`

***

### `import-file-input.js`

Lazy file-picker import binding for the hidden `#pdf-input`. It shares `loadPdfImport()` with the drop-zone path, classifies files on change, clears stale selections on failure, and routes JSON, lab PDFs/images, text imports, and DNA files through the import/DNA handlers exposed on `window`.

**Key exports:**

* `bindImportFileInput()` — idempotently binds `#pdf-input` change handling and document-level drag/drop prevention
* `handleImportInputChange(e)` — routes one file-picker change event through the lazy import classifier

***

### `export.js`

Data export, import, and reset.

**Key exports:**

* `exportToJSON()` — exports v2 JSON for the current profile: `{ version: 2, exportedAt, entries, notes, diagnoses, diet, exercise, sleepRest, lightCircadian, stress, loveLife, environment, interpretiveLens, healthGoals, contextNotes, menstrualCycle, customMarkers, supplements }`
* `buildClientExportObject(profileId, includeChat?)` — module-only helper that returns the reusable v2 single-profile export object used by JSON export and encrypted profile sharing. It includes profile metadata, labs, context, supplements, genetics, biometrics, marker notes, wearables summaries/preferences, and Light/Sun data, while excluding OAuth/wearable connection credentials and raw per-device wearable rows
* `exportClientJSON(profileId)` — exports a single client's data (used from Client List ⋮ menu)
* `exportAllDataJSON()` — exports a full database bundle with all profiles, chat threads, custom personalities, and settings
* `buildAllDataBundle()` — builds the bundle object used by both `exportAllDataJSON()` and folder backup
* `importFromJSON(file)` — merges entries by date, deduplicates notes, overwrites context fields, merges healthGoals by text. Auto-detects database bundles and handles multi-profile merge
* `exportToPDF()` — generates a printable PDF report with all data, charts, and context cards
* `clearAllData()` — confirms and wipes all imported data for the current profile

**Window exports:** `exportToJSON`, `exportDataJSON`, `exportClientJSON`, `exportAllDataJSON`, `importFromJSON`, `exportToPDF`, `clearAllData`

***

### `profile-share.js`

Encrypted single-profile share links. The module creates a profile export through the module-only `buildClientExportObject()` helper, encrypts it in the browser with a password, posts only the ciphertext envelope to `/api/share`, and imports shared profiles from `#share/{id}` deep links after local password decryption. Shared envelopes require at least 100,000 PBKDF2-SHA256 iterations; the normal generated value is 600,000.

**Key exports:**

* `createProfileShare({ profileId, password, expiresDays })` — builds, encrypts, uploads, and records a managed share link for one profile
* `encryptProfileShareEnvelope(exportObj, secret, options?)` / `decryptProfileShareEnvelope(envelope, secret)` — AES-256-GCM envelope helpers using PBKDF2-SHA256
* `generateProfileSharePassword()` — generates a user-copyable random password
* `buildProfileShareUrl(id)` / `parseProfileShareIdFromLocation(loc?)` — link construction and deep-link parsing
* `openProfileShareModal(profileId?)` / `openSharedProfileImportModal(id)` — create/load modal entry points
* `handleProfileShareDeepLink()` / `initProfileShareLinks()` — startup hash handling for shared links

**Storage:** active links created in the current browser are tracked in localStorage under `getbased-profile-shares-v1`. Records store share metadata and the management token needed to stop sharing; they do not store the profile password or plaintext profile data.

**Window exports:** `openProfileShareModal`, `closeProfileShareModal`, `openSharedProfileImportModal`, `createProfileShare`, `deleteProfileShareEnvelope`, `encryptProfileShareEnvelope`, `decryptProfileShareEnvelope`, `parseProfileShareIdFromLocation`, `buildProfileShareUrl`

***

### `chat.js`

Chat public barrel and startup entry point. Importing this module installs `chat-window-bindings.js` for legacy inline handlers, then re-exports the public chat helpers from the feature modules. Per-marker/correlation prompt builders live in `chat-marker-prompts.js`; direct send/streaming and image send integration live in `chat-send.js`; chat transcript rendering lives in `chat-render.js`; empty/onboarding message states live in `chat-empty-state.js`; chat-first onboarding handlers and provider quiz helpers live in `chat-onboarding.js`; multi-persona discussion exports live in `chat-discussion.js`, with public user-action handlers in `chat-discussion-flow.js`, callback bridging in `chat-discussion-callbacks.js`, cleanup/completion helpers in `chat-discussion-lifecycle.js`, round turn execution helpers in `chat-discussion-turns.js`, persona/thread state helpers in `chat-discussion-state.js`, round execution in `chat-discussion-round-runner.js`, round prompt helpers in `chat-discussion-round-prompts.js`, round API request helpers in `chat-discussion-round-request.js`, thread-bound round persistence in `chat-discussion-round-state.js`, live round message DOM helpers in `chat-discussion-round-view.js`, persona picker controls in `chat-discussion-picker.js`, and Discuss button/continue controls in `chat-discussion-ui.js`; panel chrome lives in `chat-panel.js`; FAB nudge state lives in `chat-nudge.js`; personality selection and custom persona editing live in `chat-personalities.js`; current-thread history persistence lives in `chat-history.js`; message action bars live in `chat-actions.js`; chat image attachment state lives in `chat-images.js`; thread index storage and rail rendering live in `chat-threads.js`; thread rail message search and match highlighting live in `chat-thread-search.js`.

**Key exports:**

* `sendChatMessage()` — re-exported from `chat-send.js` for existing callers
* `renderChatMessages()` — re-exported from `chat-render.js` for existing callers
* `askAIAboutMarker(markerKey)` — re-exported from `chat-marker-prompts.js`; opens chat with a marker-specific prompt
* `askAIAboutCorrelations()` — re-exported from `chat-marker-prompts.js`; opens chat with a selected-marker correlation prompt
* Thread management: `createNewThread()`, `loadThread(id)`, `deleteThread(id)`, `renameThread(id)`

**Window exports:** `sendChatMessage`, `setChatPersonality`, `openChatPanel`, `closeChatPanel`, `createNewThread`, `loadThread`, `deleteThread`, `renameThread`, `generateCustomPersonality`, `saveCustomPersonality`, `askAIAboutMarker`, `addImageAttachment`, `toggleHDMode`

***

### `chat-window-bindings.js`

Chat callback wiring and legacy `window.*` compatibility exports. Owns `_resumeAI`, configures `chat-discussion.js`, `chat-onboarding.js`, and `chat-panel.js` callbacks, and exposes the chat handlers needed by inline HTML and cross-module `window.fn()` call sites.

**Key exports:** none; loaded for side effects by `chat.js`.

**Window exports:** `sendChatMessage`, `renderChatMessages`, `setChatPersonality`, `openChatPanel`, `closeChatPanel`, discussion handlers, onboarding handlers, summary handlers, action-bar handlers, and per-marker Ask AI handlers.

***

### `chat-marker-prompts.js`

Per-marker and selected-correlation chat prompt builders. Owns `askAIAboutMarker()` and `askAIAboutCorrelations()`, including phase-aware marker values, effective reference ranges, latest status, and trend text before opening the chat panel with a prefilled prompt.

**Key exports:** `askAIAboutMarker`, `askAIAboutCorrelations`

**Window exports:** assigned by `chat-window-bindings.js` for existing inline Ask AI buttons.

***

### `chat-send.js`

Direct chat send and streaming state. Owns `sendChatMessage()`, Enter-key handling, stop-button abort state, send-button icon mode, typewriter trickle, image attachment payload injection, API message assembly, usage footnotes, live EMF/product recommendation injection, and partial-message persistence on abort. It is imported by `chat.js`, which re-exports its public entry points and passes its typewriter/abort helpers to `chat-discussion.js`.

**Key exports:** `sendChatMessage`, `handleChatKeydown`, `isChatStreaming`, `createTypewriter`, `getChatAbortController`, `setChatAbortController`, `setSendButtonMode`, `updateSendButtonState`

**Window exports:** `updateSendButtonState` is assigned by this module for `chat-images.js`; other user-facing handlers are assigned by `chat-window-bindings.js`.

***

### `chat-render.js`

Chat transcript rendering. Owns `renderChatMessages()` for persisted messages, lens-source disclosure rendering, assistant footnotes, image badges, EMF hints, and persisted recommendation sections. It delegates empty chat and onboarding states to `chat-empty-state.js` and keeps rendering details out of the chat window wiring module.

**Key exports:** `renderChatMessages`, `_getNoDataPrompts`, `_renderLensSources`

**Window exports:** `renderChatMessages` is assigned by `chat-window-bindings.js` for existing inline handlers and cross-module callbacks.

***

### `chat-empty-state.js`

Empty chat and chat-first onboarding message composition. Owns the profile setup bubble, paused-AI prompt, explicit provider quiz entry state, no-lab optional setup cards, no-data prompt selection, and the default empty prompt buttons used before a real thread has messages.

**Key exports:** `renderEmptyChatState`, `_getNoDataPrompts`

**Window exports:** none; called by `chat-render.js`.

***

### `chat-panel.js`

Chat panel chrome and entry-state helpers. Owns open/close/fullscreen behavior, persisted fullscreen preference, web-search toggle persistence/visibility, and composer disabled state. It imports thread/history/personality helpers directly, delegates FAB nudge dismissal to `chat-nudge.js`, and receives the active-discussion restore callback through `configureChatPanel()` to avoid importing `chat-discussion.js`.

**Key exports:** `configureChatPanel`, `toggleChatPanel`, `toggleChatFullscreen`, `openChatPanel`, `closeChatPanel`, `updateChatInputState`, `getChatWebSearchEnabled`, `setChatWebSearchEnabled`, `refreshWebSearchToggle`; also re-exports `setChatNudge` and `updateChatNudge` from `chat-nudge.js` for compatibility.

**Window exports:** assigned by `chat-window-bindings.js` for existing inline handlers and cross-module callbacks.

***

### `chat-nudge.js`

Chat FAB nudge badge state. Owns nudge stage persistence, per-profile dismissal, badge/pulse DOM updates, and the profile/API/data/context card readiness check used by startup, settings, onboarding, DNA import, and panel-open flows.

**Key exports:** `setChatNudge`, `dismissCurrentChatNudge`, `updateChatNudge`

**Window exports:** assigned by `chat-window-bindings.js` for existing inline handlers and cross-module callbacks.

***

### `chat-onboarding.js`

Chat-first onboarding helpers. Owns the provider quiz render helper, onboarding progress crumbs, profile/location/cycle/supplement handlers, lab-import CTA behavior, context-card nudge callback, and prompt CTA helper. It receives `renderChatMessages`, `sendChatMessage`, `closeChatPanel`, and nudge callbacks from `chat-window-bindings.js` through `configureChatOnboarding()` so it can update the shared chat UI without importing chat UI modules directly.

**Key exports:** `configureChatOnboarding`, `useChatPrompt`, `requestOnboardingLabImportProvider`, `startOnboardingLabImport`, `_renderOnboardCrumbs`, `_renderProviderQuiz`, `_countFilledCards`, `setChatProfileSex`, `saveChatProfile`, `saveChatLocation`, `onboardHeightUnitChanged`, `saveChatPeriod`, `addChatSupplement`, `removeChatSupplement`, `setProviderQuizBranch`, `backToProviderQuiz`, `skipProviderSetup`, `skipOnboardingExtras`, `showCycleNoMensesOptions`, `showCyclePeriodEntry`, `saveCycleStatus`, `_updatePeriodBtn`, `onContextCardSaved`

**Window exports:** assigned by `chat-window-bindings.js` for existing inline handlers and `context-cards.js` callbacks.

***

### `chat-discussion.js`

Multi-persona discussion public barrel. Re-exports discussion flow handlers from `chat-discussion-flow.js`, callback configuration from `chat-discussion-callbacks.js`, UI compatibility helpers from `chat-discussion-ui.js`, and persona/thread state helpers from `chat-discussion-state.js`.

**Key exports:** `configureChatDiscussion`, `getThreadPersonaCount`, `updateDiscussButton`, `getCurrentDiscussionState`, `sendDiscussionUserTurn`, `restoreDiscussionContinuePrompt`, `showDiscussContinuePrompt`, `removeDiscussContinuePrompt`, `cleanupDiscussionState`, `startDiscussion`, `startDiscussionFromPicker`, `continueDiscussion`, `endDiscussion`

**Window exports:** assigned by `chat-window-bindings.js` for existing inline handlers and `chat-threads.js` callbacks.

### `chat-discussion-flow.js`

Multi-persona discussion public user-action handlers. Owns manual-message entry, Continue, Discuss, and picker-start actions. It delegates cleanup/completion to `chat-discussion-lifecycle.js`, turn execution to `chat-discussion-turns.js`, and picker/prompt DOM through `chat-discussion-ui.js`.

**Key exports:** `sendDiscussionUserTurn`, `restoreDiscussionContinuePrompt`, `showDiscussContinuePrompt`, `cleanupDiscussionState`, `startDiscussion`, `startDiscussionFromPicker`, `continueDiscussion`, `endDiscussion`

**Window exports:** none directly; `chat-discussion.js` re-exports the public handlers and `chat-window-bindings.js` assigns the compatibility handlers.

### `chat-discussion-lifecycle.js`

Discussion cleanup and completion helpers. Owns active-discussion restore, continuation prompt persistence, transient UI cleanup, explicit end lifecycle, original-persona restore, Discuss button/header refresh, and post-round Continue prompt handoff.

**Key exports:** `restoreDiscussionContinuePrompt`, `showDiscussContinuePrompt`, `cleanupDiscussionState`, `endDiscussion`, `finishDiscussionRound`

**Window exports:** none directly; `chat-discussion-flow.js` re-exports the public lifecycle handlers.

### `chat-discussion-turns.js`

Discussion turn execution helpers. Owns continuation round dispatch, first debate turn dispatch, single joined-persona turn dispatch, joined-persona marker insertion, thread-bound discussion state persistence, and round completion handoff.

**Key exports:** `runDiscussionContinuation`, `runSingleDiscussionTurn`, `runDiscussion`

**Window exports:** none.

### `chat-discussion-callbacks.js`

Discussion callback bridge. Owns `configureChatDiscussion()` and the callback wrappers that let discussion rounds reuse chat-send streaming state, send-button mode, transcript rendering, and typewriter behavior without importing `chat-send.js`.

**Key exports:** `configureChatDiscussion`, `getChatAbortController`, `setChatAbortController`, `renderChatMessages`, `setSendButtonMode`, `createDiscussionTypewriter`

**Window exports:** none directly; `chat-discussion.js` re-exports `configureChatDiscussion` for `chat-window-bindings.js`.

### `chat-discussion-round-runner.js`

Discussion round execution loop. Owns per-persona iteration, streaming API calls, abort-controller/send-button state, auto-message insertion, typewriter updates, final assistant message persistence, usage footnotes, and modal-aware round error rendering.

**Key exports:** `runDiscussionRound`

**Window exports:** none.

### `chat-discussion-round-prompts.js`

Discussion round prompt helpers. Owns the default follow-up prompt, initial-analysis prompt, joined-persona prompt, auto-message shape, joined-persona marker shape, and the first-turn versus rebuttal prompt selection logic.

**Key exports:** `DEFAULT_DISCUSS_PROMPT`, `INITIAL_DISCUSS_PROMPT`, `DISCUSSION_JOIN_PROMPT`, `hasExistingDiscussionResponses`, `getDiscussionPromptText`, `buildDiscussionAutoMessage`, `buildDiscussionJoinMessage`

**Window exports:** none.

### `chat-discussion-round-request.js`

Discussion round API request helpers. Owns lab-context and lens enrichment, active personality prompt assembly, provider/model/web-search snapshots, tagged history messages, assistant message metadata, and usage tracking for multi-persona rounds.

**Key exports:** `buildDiscussionRoundRequest`, `buildDiscussionAssistantMessage`, `trackDiscussionUsage`

**Window exports:** none.

### `chat-discussion-round-state.js`

Thread-bound discussion round persistence helpers. Owns origin-thread activity checks, discussion metadata writes, active-thread repaint handoff, and off-thread history persistence during streaming rounds.

**Key exports:** `isRoundThreadActive`, `persistDiscussionThreadState`, `renderRoundMessages`, `saveRoundChatHistory`

**Window exports:** none.

### `chat-discussion-round-view.js`

Live discussion round DOM helpers. Owns typing indicators, persona labels, streamed assistant message nodes, final markdown replacement, usage footnotes, and active-thread-only error rendering for multi-persona rounds.

**Key exports:** `createDiscussionTypingIndicator`, `createDiscussionPersonaLabel`, `appendRoundPersonaLabel`, `createDiscussionAiMessage`, `renderFinalDiscussionMessage`, `appendDiscussionUsageFootnote`, `renderDiscussionRoundError`

**Window exports:** none.

### `chat-discussion-ui.js`

Multi-persona discussion button and continuation prompt controls. Owns the Discuss button visibility/add-persona state and transient state handoff used by `continueDiscussion()` and `endDiscussion()`. Re-exports picker helpers from `chat-discussion-picker.js` so existing discussion flow imports stay stable.

**Key exports:** `updateDiscussButton`, `showDiscussContinuePrompt`, `removeDiscussContinuePrompt`, `removeDiscussPersonaPicker`, `readDiscussPersonaPickerSelection`, `showDiscussPersonaPicker`

**Window exports:** none directly; `chat-discussion.js` wraps/re-exports public controls and `chat-window-bindings.js` assigns the compatibility handlers.

### `chat-discussion-picker.js`

Multi-persona discussion picker controls. Owns persona picker markup, active-persona locking, checkbox limiting, and picker selection reads.

**Key exports:** `removeDiscussPersonaPicker`, `readDiscussPersonaPickerSelection`, `showDiscussPersonaPicker`

**Window exports:** none.

### `chat-discussion-state.js`

Multi-persona discussion state helpers. Owns per-thread active discussion detection, history fallback persona collection, current-thread lookup, unique assistant-persona counts, and current-thread discussion lifecycle metadata.

**Key exports:** `getThreadPersonaCount`, `collectDiscussionPersonas`, `getCurrentThread`, `clearCurrentDiscussionThreadState`, `reopenCurrentDiscussionThread`, `getCurrentDiscussionState`

**Window exports:** none directly; `chat-discussion.js` re-exports the public count/state helpers and `chat-window-bindings.js` assigns them.

***

### `chat-personalities.js`

Chat personality storage, personality picker rendering, custom persona editor, AI-powered persona generation, and chat header title/model status. The module imports thread index helpers directly and calls back to `window.renderChatMessages?.()` for the two places that need to repaint chat content without creating a `chat.js` import cycle.

**Key exports:** `getActivePersonality`, `getCustomPersonalities`, `saveCustomPersonalities`, `getCustomPersonality`, `getCustomPersonalityText`, `pickPersonaIcon`, `setChatPersonality`, `loadChatPersonality`, `updateChatHeaderTitle`, `updateChatHeaderModel`, `updateSummaryButton`, `updatePersonalityBar`, `togglePersonalityBar`, `generateCustomPersonality`, `saveCustomPersonality`, `startNewCustomPersonality`, `editCustomPersonality`, `deleteCustomPersonality`, `autoResizePersonaTextarea`, `markPersonalityDirty`, `snapshotPersonalityClean`

**Window exports:** assigned by `chat-window-bindings.js` for existing inline handlers and cross-module callbacks.

***

### `chat-history.js`

Thread-aware chat message persistence and clearing. The module owns the legacy storage key helper, encrypted/plain thread message load/save, thread metadata updates, saved-summary cleanup, and the clear-history confirmation flow. It uses `window.renderChatMessages?.()` and `window.updateDiscussButton?.()` callbacks for repaint/discussion hooks that would otherwise create a `chat.js` import cycle.

**Key exports:** `getChatStorageKey`, `loadChatHistory`, `saveChatHistory`, `clearChatHistory`

**Window exports:** assigned by `chat-window-bindings.js` for existing inline handlers and `chat-threads.js` callbacks.

***

### `chat-thread-search.js`

Thread rail message-content search and match highlighting. It owns the debounced per-thread content search, encrypted/plain message reads, result rendering, cache invalidation, jump-to-result behavior, and in-message mark/highlight cleanup while receiving thread key, rail render, and switch callbacks from `chat-threads.js`.

**Key exports:** `configureChatThreadSearch`, `filterThreadList`, `invalidateThreadContentCache`, `jumpToSearchResult`

**Window exports:** re-exported and assigned by `chat-threads.js` for existing inline handlers.

***

### `chat-actions.js`

Chat message action bar rendering and message-level handlers. The module owns `Regenerate`, `Copy`, and context-details toggle behavior while calling back to `window.renderChatMessages?.()`, `window.sendChatMessage?.()`, and `window.isChatStreaming?.()` to avoid importing the streaming/rendering module.

**Key exports:** `buildActionBar`, `regenerateLastMessage`, `copyMessage`, `toggleContextDetails`

**Window exports:** assigned by the module directly and re-exported by `chat.js` for existing inline handlers.

***

### `chat-icons.js`

Shared chat SVG icon strings and icon-button DOM helper extracted from `chat.js`. Keeps action buttons and streaming send/stop button state changes using the same symbols without keeping SVG construction code in the chat orchestration module.

**Key exports:** `CHAT_ICON_COPY`, `CHAT_ICON_REFRESH`, `CHAT_ICON_EDIT`, `CHAT_ICON_X`, `setIconButtonContent`

**Window exports:** none

***

### `chat-attestation.js`

Venice E2EE attestation display helpers shared by the chat header and assistant-message cost footnotes. Keeps lock glyph, attestation tooltip, and verified/failed mark formatting out of the chat orchestration module.

**Key exports:** `attestationTooltip`, `e2eeLockHTML`, `e2eeLockFootnote`

**Window exports:** none

***

### `chat-continuation.js`

Response limit detection and automatic continuation wrapper for chat and discussion calls. Centralizes token-headroom constants, incomplete-response heuristics, continuation prompt construction, and usage merging.

**Key exports:** `CHAT_RESPONSE_MAX_TOKENS`, `callChatAPIWithContinuation`, `isAIResponseTruncated`, `responseLimitNote`

**Window exports:** none

***

### `chat-prompt-context.js`

Pure prompt/message helpers shared by direct chat sends and multi-persona discussion rounds. Centralizes persona prompt additions, cross-persona API-message tagging, web-search/E2EE system hints, system-prompt assembly, and persisted lens-source serialization.

**Key exports:** `buildPersonalityPrompt`, `buildMultiPersonaInstruction`, `buildTaggedChatMessages`, `buildWebSearchHint`, `buildChatSystemPrompt`, `serializeLensSources`, `attachLensSources`

**Window exports:** none

***

### `chat-summaries.js`

Conversation summary generation, saved-summary profile storage, and summary modal actions extracted from `chat.js`.

**Key exports:** `summarizeThread`, `renderSavedSummaries`, `viewSavedSummary`, `deleteSavedSummary`, `closeSummaryModal`, `copySummary`, `downloadSummary`, `printSummary`

**Window exports:** assigned by `chat-window-bindings.js` for existing inline handlers.

***

### `settings.js`

Settings modal shell with profile, display, AI, privacy, data, wearables, and Agent Access tabs. Delegates AI provider panels to `provider-panels.js`, wearables rows to `wearables-settings-panel.js`, sync controls to `settings-sync-panel.js`, and Agent Access rendering/actions to `settings-agent-access-panel.js`.

**Key exports:**

* `openSettingsModal()` / `closeSettingsModal()`
* `initSettingsModelFetch()` — fetches model lists for all providers on modal open
* `saveProfileSettings()` — saves sex, DOB, location from the Profile section
* `setUnitSystem(system)` — `'EU'` | `'US'`

**Window exports:** `openSettingsModal`, `closeSettingsModal`, `saveProfileSettings`, `setUnitSystem`, `setAIProvider`, `startTour`

***

### `settings-sync-panel.js`

Settings subpanel module for cross-device sync. Owns the Settings → Data sync section, sync setup/restore modal, mnemonic and relay controls, and delegates Settings → Agent Access rendering/actions to `settings-agent-access-panel.js`.

**Key exports:**

* `renderSyncSection()` — renders the Settings → Data sync panel
* `renderMessengerSection()` — compatibility export that renders the Settings → Agent Access panel through `settings-agent-access-panel.js`
* `hydrateSettingsSyncPanel()` — loads mnemonic and relay status after the settings modal is painted
* `showSyncSetupModal()` — opens the cross-device sync setup wizard directly; used by the dashboard data-protection picker

**Window exports:** `showSyncSetupModal`, `toggleSync`, `toggleMnemonicVisibility`, `copyMnemonic`, `saveSyncRelay`, `openRestoreMnemonicDialog`, `closeRestoreMnemonicDialog`, `confirmRestoreMnemonic`, `closeSyncSetup`, `syncSetupNew`, `syncSetupRestore`, `syncSetupBack`, `syncSetupDoRestore`, `syncSetupDone`, `toggleMessenger`, `toggleMessengerToken`, `copyMessengerToken`, `regenerateMessengerToken`

***

### `settings-agent-access-panel.js`

Settings → Agent Access renderer and action handler. It owns the enable/disable state, synced Agent Access metadata, read-only relay token display, local context-key display, wearable daily-series window, and the one-paste setup-command builder for supported MCP clients.

**Key exports:**

* `renderMessengerSection()` — renders the Agent Access settings panel, including the target-client chip group
* `buildAgentAccessSetupCommand(client)` — builds the private `gbsetup_v1_...` bootstrap command for `hermes`, `openclaw`, `claude-code`, `claude-desktop`, `cursor`, `cline`, or `codex`
* `handleMessengerAction(action, el)` — delegated action handler for enabling, regenerating, copying secrets, copying setup commands, and series-window changes
* `toggleMessengerToken()` / `copyMessengerToken()` / `regenerateMessengerToken()` — compatibility actions for legacy window bindings

***

### `feedback.js`

In-app feedback modal.

**Key exports:**

* `openFeedbackModal()` / `closeFeedbackModal()`
* `submitFeedback()` — validates and submits feedback (bug report or feature request)

**Window exports:** `openFeedbackModal`, `closeFeedbackModal`, `submitFeedback`

***

### `nav.js`

Sidebar navigation, profile switcher, and mobile sidebar shell.

**Key exports:**

* `buildSidebar(data)` - renders Home, Lenses, Tools, and lab category navigation with marker counts
* `filterSidebar()` - filters lab categories and keeps top-level app routes visible during search
* `toggleNavGroup(groupId)` - expands/collapses sidebar category groups
* `renderProfileDropdown()` / `renderProfileButton()` - profile switcher UI
* `openRecommendationsFromSidebar()` - compatibility helper that routes to the dedicated `recommendations` page

**Window exports:** `buildSidebar`, `filterSidebar`, `toggleNavGroup`, `renderProfileDropdown`, `renderProfileButton`, `toggleMobileSidebar`, `closeMobileSidebar`, `openRecommendationsFromSidebar`

***

## Layer 6 — Orchestration

### `views-router.js`

Route validation, per-profile last-view persistence, mobile tab sync handoff, and element-anchor scroll preservation. `views.js` creates the concrete navigator by passing render handlers into `createNavigate()`.

**Key exports:**

* `createNavigate({ routeHandlers, syncMobileBottomNav })` — builds the app-level `navigate()` function without importing page renderers
* `getInitialView()` — restores the active profile's last valid route, falling back to `dashboard`
* `isKnownRoute(route, data?)` — validates fixed routes and data-backed lab category routes

***

### `lens-pages.js`

Dedicated page renderers for Labs, Genome, Body, Insight, and Recommendations. The module receives dashboard, recommendation, and lens shell helper functions from `views.js` through `createLensPageHandlers()` so page rendering can move out of the main views module without creating new import cycles.

**Key exports:**

* `createLensPageHandlers(deps)` — returns `showLabs`, `showGenomeLens`, `showBodyLens`, `showInsightLens`, and `showRecommendations`

***

### `dashboard-page-view.js`

Dashboard route shell and page-level dashboard orchestration. `dashboard-view-composition.js` creates it with `createDashboardPageView()` after wiring dashboard widget renderers, controls, and registry helpers, so the dashboard route can live outside the main compatibility module without importing the widget control layer directly.

**Key exports:**

* `createDashboardPageView(deps)` — returns `showDashboard(data?)`; owns the populated dashboard shell, empty welcome/demo state, import drop-zone setup calls, mobile dashboard handoff, focus-card hydration, context-dot hydration, and first-visit tour triggers

***

### `dashboard-view-composition.js`

Dashboard route, widget, mobile handoff, marker-modal, and lens-shell composition wiring. `views.js` creates this layer once and keeps compatibility facades, while this module wires `dashboard-page-view.js`, `dashboard-widgets.js`, `dashboard-widget-controls.js`, `dashboard-widget-renderers.js`, and mobile dashboard dependencies together.

**Key exports:**

* `createDashboardViewComposition(deps)` — returns `showDashboard`, dashboard widget control handlers, recommendation helpers, and dashboard render helpers needed by lens pages and recommendation actions

***

### `lens-page-shell.js`

Shared lens page chrome and ordering helpers used by `views.js` and `lens-pages.js`. The module owns lens headers, reorderable page widget wrappers, per-profile lens widget order persistence, page-section move controls, dashboard add/remove toggles, and the delegated action attributes/listener used by shared lens chrome.

**Key exports:**

* `configureLensPageShell(deps)` — injects dashboard widget visibility helpers without importing dashboard modules directly
* `renderLensHeader(title, subtitle, actions?)` — renders the shared lens page header
* `renderLensPageWidgets(route, widgets)` / `renderLensWidget(...)` — render reorderable page widgets and their chrome
* `moveLensPageWidget(route, id, direction)` — persists per-profile page section order and refreshes the active route
* `lensPageActionAttrs(action, attrs?)` — builds escaped `data-lens-page-*` attributes consumed by the shared delegated click listener

***

### `dashboard-widgets.js`

Dashboard widget registry and persistence helpers. `dashboard-view-composition.js` supplies renderer functions from `dashboard-widget-renderers.js` to `createDashboardWidgetRegistry()` so widget metadata, defaults, availability gates, per-profile widget preferences, custom marker widget IDs, and visible-entry generation can live outside the main views module.

**Key exports:**

* `createDashboardWidgetRegistry(renderers, opts?)` — returns dashboard widget definitions plus preference/order helpers
* `DASHBOARD_WIDGET_SOURCE_ORDER` — Lens/source ordering for the widget picker
* `DASHBOARD_WIDGET_DEFAULT_IDS` — new-profile dashboard default order
* `DASHBOARD_MANUAL_BIOMETRIC_METRICS` and `dashboardBiometricSelectionKey()` — biometrics widget selection helpers

***

### `dashboard-widget-controls.js`

Dashboard widget picker, widget chrome, layout actions, and drag/reorder controls. `dashboard-view-composition.js` creates the control layer with registry, marker, and biometrics dependencies; `views.js` re-exports the public handlers for existing `window.*` integrations.

**Key exports:**

* `createDashboardWidgetControls(deps)` — returns render helpers and dashboard widget action handlers

***

### `dashboard-widget-renderers.js`

Dashboard widget body renderers and shared recommendation helpers. `dashboard-view-composition.js` creates the renderer layer with Light helper and mobile-summary dependencies, then passes the returned renderers to `dashboard-widgets.js`, `dashboard-widget-controls.js`, and `lens-pages.js`.

**Key exports:**

* `createDashboardWidgetRenderers(deps)` — returns dashboard body renderers, dashboard context builders, biometrics helpers, custom marker renderer support, and recommendation helper functions

***

### `category-view-renderers.js`

Category chart card, table, heatmap, and fatty-acid profile renderers. `category-page-view.js` calls these helpers for category route content, while `views.js` imports and re-exports them so existing module and `window.*` integrations stay stable.

**Key exports:**

* `renderChartCard(id, marker, dateLabels)` — renders one category/dashboard marker card and registers it for the detail modal
* `renderTableView(cat, dateLabels, categoryKey, dates)` / `renderHeatmapView(cat, dateLabels, dates, categoryKey)` — render category table shells with empty-value add affordances
* `renderFattyAcidsView(cat, categoryKey)` / `renderFattyAcidsCharts(cat)` — render the single-date fatty-acid profile cards and bar chart

***

### `category-page-view.js`

Category route shell and view-mode orchestration. The module owns `showCategory()`, `switchView()`, category card sorting, range-toggle order preservation, chart hydration, and chart-card recommendation loading. It depends on `category-view-renderers.js` for HTML fragments and keeps `views.js` as a thin router/compatibility facade.

**Key exports:**

* `showCategory(categoryKey, data?)` — renders the category header, view tabs, date/layer controls, chart grid, empty marker affordances, and saved table/heatmap mode
* `switchView(view, categoryKey, btn)` — switches between chart, table, and heatmap modes, updates tab ARIA state, destroys stale charts, and rehydrates charts when needed

***

### `category-customization.js`

Category and marker display override helpers. The module owns category rename, marker rename/revert, the emoji picker, and category icon override persistence while `views.js` keeps compatibility exports and injects navigation via `configureCategoryCustomization()`.

**Key exports:**

* `configureCategoryCustomization(deps)` — injects the app-level `navigate()` function without importing `views.js`
* `renameCategory(categoryKey)` / `renameMarker(id)` / `revertMarkerName(id)` — persist display-name overrides into `importedData.categoryLabels` and `importedData.markerLabels`
* `showEmojiPicker(anchorEl, callback, opts?)` — shared category/custom-marker emoji picker used by category views and the marker creation modal
* `changeCategoryIcon(categoryKey)` — persists `importedData.categoryIcons` overrides and mirrors custom marker category icon metadata

***

### `views.js`

Tool page routing and compatibility exports. Dashboard composition lives in `dashboard-view-composition.js`; dashboard route shell lives in `dashboard-page-view.js`; dashboard widget body renderers live in `dashboard-widget-renderers.js`; recommendation action handlers live in `recommendation-actions.js`; category route orchestration lives in `category-page-view.js`; category card/table/heatmap renderers live in `category-view-renderers.js`; category display overrides live in `category-customization.js`; the Light page shell lives in `light-page-view.js`; Light channel pills and drill-down panels live in `light-channel-view.js`; shared lens page chrome lives in `lens-page-shell.js`; public navigation and lens page functions remain exported here for compatibility, backed by `views-router.js` and delegated to route modules where applicable.

**Key exports:**

* `navigate(section, params)` — router facade created from `views-router.js`; calls the appropriate render function
* `showDashboard(data?)` - compatibility facade backed by `dashboard-view-composition.js` and `dashboard-page-view.js`; renders the customizable widget dashboard whose default widget order starts with Biological Coherence, Current Focus, Current Priority, Quick Markers, Key Trends, Recommended Next Steps, Profile Context, Biometrics Overview, Biological Age, Metabolic Flexibility, and Cycle when available
* `showLabs(data?)`, `showGenomeLens()`, `showBodyLens()`, `showInsightLens(data?)`, `showRecommendations(data?)` - compatibility facades delegated to `lens-pages.js`
* `showCategory(categoryKey, data?)` - compatibility facade imported from `category-page-view.js`
* `showLight(data?)`, `renderLightTodayStrip()`, `renderLightChannelsLive()` - compatibility facades imported from `light-page-view.js`
* `showCompare(data?)` / `showCorrelations(data?)` - focused tool pages
* Dashboard widget controls: compatibility facades backed by `dashboard-widget-controls.js`, including `openDashboardWidgetPicker()`, `toggleDashboardOrganizeMode()`, `resetDashboardWidgets()`, `clearDashboardWidgets()`, `addDashboardWidgetFromLens()`, and `removeDashboardWidgetFromLens()`
* `showDetailModal(markerKey, data?)` — opens the marker detail modal

**Window exports:** `navigate`, `showDashboard`, `showLabs`, `showGenomeLens`, `showBodyLens`, `showInsightLens`, `showRecommendations`, `showLight`, `showCategory`, `showDetailModal`, dashboard widget controls, and recommendation page helpers (`openRecommendationDetail`, `discussRecommendation`, `saveRecommendation`, `dismissRecommendation`)

***

### `recommendation-actions.js`

Recommendation modal and action handlers shared by dashboard cards and the Recommendations page. It keeps option-detail rendering, chat prompts, saved/bookmarked state, and dismissed state out of the main route facade.

**Key exports:**

* `createRecommendationActions(deps)` — returns `openRecommendationDetail()`, `discussRecommendation()`, `saveRecommendation()`, and `dismissRecommendation()`

***

### `light-page-view.js`

Light & Sun page shell, Light Today strip, dashboard Light channel pills, Light session log actions, page widget assembly, and async population of page-only Light workbench sections. It imports shared lens chrome from `lens-page-shell.js`, condition rendering from `light-conditions-now.js`, session history from `light-sessions-view.js`, and channel rows/suggestions from `light-channel-view.js`.

**Key exports:**

* `showLight(data?)` — renders the reorderable Light & Sun page widgets
* `renderLightTodayStrip()` — legacy compact Light strip used by embedded/welcome surfaces
* `renderLightChannelsLive()` — refreshes the live channel-pills slot after session updates
* `renderDashboardLightChannelPills()` / `renderLightSessionLogActions()` — dashboard widget helper bodies shared through `dashboard-widget-renderers.js`
* `_expandLightToolsSection()` — compatibility handler for expanding the collapsed Light tools placeholder

***

### `light-channel-view.js`

Light channel pill rows, seven-day sparklines, dashboard-to-Light channel navigation, per-channel drill-down panels, citations, source mix, and channel suggestions. `light-page-view.js` imports the render helpers; `views.js` keeps the legacy `window._toggleChannelDetail` / `window._openChannelOnLightPage` wiring for inline handlers and dashboard widgets.

**Key exports:**

* `mergeTotals(a, b)` — combines sun and device channel totals
* `renderChannelPills(totals7d, totals30d)` — renders the interactive Light page channel row and detail slot
* `_toggleChannelDetail(channelKey)` / `_openChannelOnLightPage(channelKey)` — compatibility handlers for expanding channel detail panels
* `renderSuggestion(totals7d)` — fallback channel-guidance copy when AI synthesis is unavailable

***

### `main.js`

Thin module entry point. Imports startup feature side effects, then starts the startup orchestrator.

**Responsibilities:**

* Imports `app-feature-modules.js` for startup-loaded feature side effects and window exports
* Calls `startApp()` from `startup-orchestrator.js`

**Window exports:** none (all exports come from other modules)

***

### `app-feature-modules.js`

Startup-loaded feature side-effect imports extracted from `main.js`. This module preserves the previous feature import order while keeping `main.js` focused on startup orchestration. Foundation/privacy imports are grouped behind `app-foundation-modules.js`; the Health & Data feature cluster is grouped behind `app-health-data-modules.js`; the Light & Sun feature cluster is grouped behind `app-light-sun-modules.js`; the import/export feature cluster is grouped behind `app-data-io-modules.js`; the AI/chat/settings feature cluster is grouped behind `app-ai-interaction-modules.js`; the UI shell feature cluster is grouped behind `app-ui-shell-modules.js`.

**Key exports:** none

**Window exports:** none directly; imported feature modules attach their existing compatibility handlers.

***

### `app-foundation-modules.js`

Startup-loaded foundation/privacy side-effect imports extracted from `app-feature-modules.js`. This module preserves the previous schema/constants/utils/pii import order while keeping the top-level feature list as named startup clusters.

**Key exports:** none

**Window exports:** none directly; imported foundation modules attach their existing compatibility handlers.

***

### `app-health-data-modules.js`

Startup-loaded Health & Data feature side-effect imports extracted from `app-feature-modules.js`. This module preserves the previous charts/notes/supplements/recommendations/cycle/context/DNA/wearables import order while keeping the top-level feature list easier to scan.

**Key exports:** none

**Window exports:** none directly; imported Health & Data modules attach their existing compatibility handlers.

***

### `app-light-sun-modules.js`

Startup-loaded Light & Sun feature side-effect imports extracted from `app-feature-modules.js`. This module preserves the previous Light & Sun import order while keeping the top-level feature list easier to scan.

**Key exports:** none

**Window exports:** none directly; imported Light & Sun modules attach their existing compatibility handlers.

***

### `app-data-io-modules.js`

Startup-loaded import/export feature side-effect imports extracted from `app-feature-modules.js`. This module owns the compatibility startup load for data export/import handlers while keeping the top-level feature list as named startup clusters.

**Key exports:** none

**Window exports:** none directly; imported data I/O modules attach their existing compatibility handlers.

***

### `app-ai-interaction-modules.js`

Startup-loaded AI/chat/settings feature side-effect imports extracted from `app-feature-modules.js`. This module preserves the previous chat/image/settings/lens/provider helper import order while keeping the top-level feature list easier to scan.

**Key exports:** none

**Window exports:** none directly; imported AI interaction modules attach their existing compatibility handlers.

***

### `app-ui-shell-modules.js`

Startup-loaded UI shell feature side-effect imports extracted from `app-feature-modules.js`. This module preserves the previous feedback/tour/touch-tooltip/client-list/views import order while keeping the top-level feature list easier to scan.

**Key exports:** none

**Window exports:** none directly; imported UI shell modules attach their existing compatibility handlers.

***

### `startup-orchestrator.js`

Startup global wiring and phase ordering. Runs once, installs app-wide startup hooks, and registers the `DOMContentLoaded` startup sequence.

**Responsibilities:**

* Sets the `_getActiveProfileId` startup global
* Installs lazy EMF window handlers through `emf-facade.js`
* Installs app-wide event and refresh wiring through `app-event-listeners.js`
* Delegates encryption, backup, and meteo bootstrap to `startup-foundation.js`
* Initializes startup profile data
* Delegates startup service boot and post-profile maintenance to `startup-maintenance.js`
* Delegates wearable/OpenRouter callback routing to `startup-oauth-callbacks.js`
* Delegates first-render UI bootstrap to `startup-ui.js`

**Key exports:**

* `startApp()` — idempotently installs startup globals/listeners and registers the startup sequence

**Window exports:** `_getActiveProfileId`

***

### `emf-facade.js`

Lazy window facade for the EMF assessment module extracted from `main.js`. It installs async `window.*` handlers for EMF editor and assessment actions, then imports `emf.js` on first use and replaces the facade handlers with the real module exports.

**Key exports:**

* `EMF_LAZY_WINDOW_FUNCTIONS` — list of EMF window handlers covered by the lazy facade
* `installEMFLazyFacade()` — installs lazy handlers during app startup

**Window exports:** EMF handler names from `EMF_LAZY_WINDOW_FUNCTIONS`

***

### `startup-foundation.js`

Blocking startup foundation extracted from `main.js`: encryption unlock, decrypted meteo config cache hydration, cross-tab broadcast setup, and folder backup handle restoration.

**Key exports:**

* `initializeStartupFoundation()` — runs the blocking security/storage bootstrap before wearable services or profile data load

**Window exports:** none

***

### `startup-profile.js`

Profile startup bootstrap extracted from `main.js`: legacy v1 profile migration, encrypted profile-cache warmup, active-profile imported-data load, and unit/sex/range/DOB display state.

**Key exports:**

* `initializeProfileData()` — creates the default profile for legacy users, migrates old imported data through encrypted storage, warms the profile cache, sets `state.currentProfile`, and loads active imported data before OAuth callbacks can persist profile-scoped data
* `applyProfileDisplayState()` — applies saved units/range mode plus sex/DOB metadata to `state` and the startup toggle controls

**Window exports:** none

***

### `startup-oauth-callbacks.js`

Startup OAuth callback routing extracted from `main.js`. Wearable callbacks run first after profile load; if they do not consume the URL code, OpenRouter OAuth handles the same `?code=`/`state` pair.

**Key exports:**

* `handleStartupOAuthCallbacks()` — delegates wearable OAuth callback handling, exchanges OpenRouter codes with state verification, stores the OpenRouter key, switches the active AI provider, and opens chat after init

**Window exports:** none

***

### `startup-maintenance.js`

Non-blocking startup maintenance extracted from `main.js`: wearable runtime config/scheduler boot, sun session self-heal, light-device preset hydration, and legacy biometrics migration.

**Key exports:**

* `initializeStartupServices()` — starts wearable runtime config loading and scheduler setup before profile data loads
* `runPostProfileStartupMaintenance()` — schedules post-profile maintenance jobs without blocking OAuth callbacks or first render

**Window exports:** none

***

### `startup-ui.js`

First-render UI bootstrap extracted from `main.js`: profile display state, theme, footer version, sidebar, sync indicator, initial navigation, deferred sync/catalog warmup, changelog, startup nudges, deferred settings/chat opens, header chrome, chat attachment handlers, and file-picker import binding.

**Key exports:**

* `renderStartupUI()` — runs the initial UI render and schedules non-blocking startup UI work after profile/OAuth startup completes

**Window exports:** none

***

### `app-event-listeners.js`

App-wide browser event wiring that used to live in `main.js`: modal wheel guards, backdrop click handling, global role-button keyboard activation, Escape/Tab modal behavior, app shortcuts, and the `registerRefreshCallback()` bridge back into `navigate(state.currentView || 'dashboard')`.

**Key exports:**

* `installGlobalEventListeners()` — idempotently installs document-level modal, keyboard, shortcut, and click handlers
* `registerAppRefreshCallback()` — registers the data refresh callback that rebuilds sidebar state and re-renders the active route

***

### `tour.js`

Generic spotlight tour engine plus the app tour and cycle tour configurations.

**Key exports:**

* `runTour(steps, storageKey, auto)` — generic engine: creates `#tour-overlay`, `#tour-spotlight`, `#tour-tooltip`; filters steps with missing targets; navigates with Back/Next/Skip/Done
* `startTour(auto)` — launches the 7-step app tour (auto=`true` checks completion flag first)
* `startCycleTour(auto)` — launches the 8-step cycle-specific tour
* `endTour()` — removes tour DOM elements, stores completion flag
* `CYCLE_TOUR_STEPS` — array of 8 step configs for the cycle tour

**Window exports:** `startTour`, `startCycleTour`, `endTour`

***

### `changelog.js`

What's New modal triggered on version bump so users see what changed after each PWA update.

**Key exports:**

* `APP_VERSION` — number matching the SW cache version (e.g., 53)
* `openChangelog(showAll)` — renders and shows the modal; `showAll=true` shows all entries, `false` shows latest 3
* `closeChangelog()` — hides modal, marks current version as seen in localStorage
* `maybeShowChangelog()` — auto-trigger: shows modal if `labcharts-changelog-seen` !== `APP_VERSION`

**Window exports:** `openChangelog`, `closeChangelog`, `maybeShowChangelog`

***

### `client-list.js`

Client List modal for managing multiple profiles.

**Key exports:**

* `openClientListModal()` / `closeClientListModal()` — modal lifecycle
* `renderClientList(query?, sortBy?)` — renders searchable, sortable profile list with inline create/edit form
* Per-profile actions: archive, flag, pin, delete, per-client export

**Window exports:** `openClientListModal`, `closeClientListModal`, `createClientInline`, `saveClientInline`, `toggleArchiveClient`, `toggleFlagClient`, `togglePinClient`, `deleteClient`, `exportClientJSON`

***

### `recommendations.js`

Supplement, lifestyle, light-device, and EMF affiliate recommendations driven by a lazy-loaded catalog. Region-aware: products + URLs + coupons are filtered by user country via a single hierarchy chain (CZ → EU → INTL etc.). The global Recommendations page is rendered by `lens-pages.js`, while `dashboard-widgets.js` registers the dashboard surface and `dashboard-widget-renderers.js` owns its dashboard renderer; this module owns catalog loading, slot rendering, disclosure state, product filtering, and DNA/wearable slot helpers.

**Key exports:**

* `loadCatalog()` — lazy-loads `data/recommendations.json` on first call
* `getUserRegion()` — derives region code (CZ/SK/DE/AT/EU/US/INTL) from profile country
* `regionLookupChain(region)` — returns the lookup chain (most-specific → INTL); used by both product visibility filter and per-region map resolution
* `getProductsForSlot(catalog, slotKey, region)` — region-filtered products
* `_resolveCouponForRegion`, `_resolveHomepageForRegion`, `_resolveProductUrlForRegion` — pick from per-region maps using the chain
* `_addUTMParams(url, content, campaign)` — partner-dashboard attribution
* `renderRecommendationSection(slotKey, opts)` — renders the "What can help" section in the detail modal
* `renderEMFMeterRecs(catalog, opts)` / `renderEMFMitigationRecs(catalog, tags, opts)` — EMF panel surfaces

**Window exports:** none (called via imports from `views.js`, `chat.js`, `context-cards.js`)

***

### `crypto.js`

Data encryption at rest and cross-tab sync.

**Key exports:**

* `encryptedSetItem(key, value)` / `encryptedGetItem(key)` — AES-256-GCM via PBKDF2 passphrase
* `getEncryptionEnabled()` / `setEncryptionEnabled(bool)` — encryption toggle
* `validatePassphrase(p)` — checks 4 strength rules (8+ chars, lowercase, uppercase, special), returns `{ valid, message }`
* `broadcastDataChanged(profileId)` — BroadcastChannel message for multi-tab sync
* `SENSITIVE_PATTERNS` — array of localStorage key pattern strings that get encrypted

**Window exports:** `setEncryptionEnabled`, `changePassphrase`, `exportBackup`, `importBackup`

***

### `backup.js`

IndexedDB auto-backup, folder backup via File System Access API, and backup restore. Extracted from `crypto.js`.

**Key exports:**

* `scheduleAutoBackup()` — debounced 60s trigger; saves up to 5 snapshots to IndexedDB + writes to folder backup if configured
* `buildBackupSnapshot()` — captures all importedData + per-profile preferences
* `loadBackupSnapshots()` — async; populates the Backup & Restore section with IndexedDB snapshots
* `restoreAutoBackup(id)` — writes snapshot to localStorage, reloads
* `saveFolderBackup(snapshot)` — writes `getbased-backup-latest.json` + daily dated snapshot to user-selected folder via File System Access API

**Window exports:** `restoreAutoBackup`, `pickBackupFolder`, `showBackupReminder`

***

### `lab-context.js`

Central AI context serializer. Extracted from `chat.js`.

**Key exports:**

* `buildLabContext()` — serializes all lab entries + all 9 context cards + interpretiveLens + contextNotes + cycle data + notes into a structured AI context string

**Window exports:** `buildLabContext`

***

### `markdown.js`

Block-aware markdown parser for chat rendering. Extracted from `chat.js`.

**Key exports:**

* `renderMarkdown(text)` — block-aware parser: headings, lists, code blocks, HR, paragraphs + inline formatting

**Window exports:** `renderMarkdown`

***

### `provider-panels.js`

AI provider settings behavior for the settings modal. Owns provider selection, key validation flows, provider model dropdown refresh, balances, and local-model advisor wiring. Imports `provider-panel-renderers.js` for provider-specific Settings → AI panel markup.

**Key exports:**

* `renderAIProviderPanel(provider)` — returns the active provider configuration panel HTML
* `switchAIProvider(provider)` — persists provider choice and refreshes the settings panel
* Provider key/model helpers for Venice, OpenRouter, Routstr, PPQ, Custom API, and local OpenAI-compatible servers

**Window exports:** provider panel handlers used by Settings HTML `onclick` attributes, including imported Routstr wallet handlers from `provider-wallet-panels.js`

### `provider-panel-renderers.js`

Provider-specific Settings → AI panel markup for OpenRouter, Routstr, Venice, PPQ, Custom, and Local AI. Keeps static panel HTML and cached-model select markup separate from provider validation, balance polling, and local-model advisor behavior.

**Window exports:** none directly. `renderAIProviderPanel()` is re-exported and attached to `window` by `provider-panels.js`.

### `provider-wallet-panels.js`

Routstr/Cashu wallet UI and node funding actions used inside the Routstr provider panel. Extracted from `provider-panels.js` so wallet deposit, withdrawal, seed backup/restore, mint switching, and node deposit/refund behavior are isolated from general provider key/model rendering.

**Key exports:**

* `configureRoutstrWalletPanels(callbacks)` — injects provider-panel callbacks without introducing a circular module import
* `routstrWalletActionButtons(active)` / `buildRoutstrNodeActions(nodeUrl, hasKey, active)` — render the wallet and node action controls used by the Routstr panel
* `doRoutstrWalletFund()` / `doRoutstrNodeDeposit()` / `doRoutstrNodeWithdraw()` / withdraw and restore helpers — Cashu wallet and Routstr node action handlers

**Window exports:** exported through `provider-panels.js` for legacy inline handlers.

### `provider-qr.js`

Shared QR-code loader for provider payment surfaces.

**Key exports:**

* `ensureQRCode()` — lazy-loads `/vendor/qrcode-generator.js` and returns the global `qrcode` factory

***

### `lens.js`

Knowledge Base / Interpretive Lens — RAG endpoint config + multi-query rewrite + dedicated modal. Two backends under one UI: `'in-browser'` (transformers.js + OPFS via `lens-local.js`) and `'external-server'` (user-configured URL + Bearer key).

**Key exports:**

* `hasLens()` — true when a lens is configured AND has indexed content
* `queryLens(hint, opts?)` — single-query retrieval; routes to in-browser worker or remote server based on backend
* `queryLensMulti(hint, opts?)` — multi-query orchestrator: rewrites the query into N=3 paraphrases via the active LLM, fans out to `queryLens` for each, fuses results with reciprocal-rank scoring (k=60). Falls back to single-query when no AI provider, when rewrite fails, when query is \<3 words, or when the toggle is off
* `getLensConfig()` / `saveLensConfig(partial)` / `getLensKey()` / `saveLensKey(key)`
* `getLensSummary()` — synchronous status object `{configured, backend, displayName, docCount, chunkCount, multiQueryOn, aiAvailable}` used by the dashboard KB row
* `openKnowledgeBaseModal()` / `closeKnowledgeBaseModal()` — dedicated modal that wraps `renderCustomLensSection()`. Replaces the previous Settings → AI inline section
* `buildLensSnippet(chunks, sourceName)` — formats retrieved chunks for AI prompt injection
* `testLensConnection()` / `clearLensCache()`
* `renderCustomLensSection()` — full settings markup (URL/key inputs, backend toggle, library picker, ingest UI, multi-query toggle); rendered inside the KB modal
* `_resetRewriteCache()` / `_fuseChunksRRFForTest()` / `_dedupeQueriesForTest()` — test surface

**Window exports:** `hasLens`, `queryLens`, `queryLensMulti`, `buildLensSnippet`, `testLensConnection`, `clearLensCache`, `openKnowledgeBaseModal`, `closeKnowledgeBaseModal`, plus all the in-modal save/toggle/library handlers

***

### `wearable-adapters.js`

Canonical wearable-metric registry + per-vendor adapter registry. Source of truth for OAuth client IDs, redirect URIs, scopes, and metric → endpoint mappings.

**Key exports:**

* `ADAPTERS` — array of `{id, displayName, authType, oauth?, apiHost?, metrics, accountInfo?}` for the 8 supported sources (Oura / Withings / Ultrahuman / WHOOP / Fitbit / Polar / Apple Health / manual)
* `CANONICAL_METRICS` — `{id: {label, sub, unit, worseWhen}}` for every metric the strip can render
* `DEFAULT_METRIC_ORDER` — preferred order when multiple sources contribute
* `adapterById(id)` / `visibleAdapters(connectedIds)` / `adapterSupportsMetric(adapterId, metricId)` / `metricsForSources(sourceIds)` / `canonicalMetric(id)`
* `applyOAuthOverrides(map)` — merges a `{adapterId: clientId}` map into the runtime override store; called from `loadWearableRuntimeConfig()`
* `getOAuthClientId(adapterOrId)` — returns the runtime override if set, falling back to the adapter's hardcoded `oauth.clientId`. Every consumer reads through this helper so self-host overrides apply uniformly
* `_resetOAuthOverrides()` — test surface

**Window exports:** none

***

### `wearables-connect.js`

Connect/disconnect/backfill orchestration. OAuth dispatch table, scheduled stale-source sync, runtime config bootstrap.

**Key exports:**

* `OAUTH_DISPATCH` — `{adapterId: {begin, isCallback, complete, withFreshToken, fetchAccountInfo, fetchRange, displayName, postConnect?, commitAfterWrite?}}` — generic OAuth orchestration table
* `beginConnectOAuth(adapterId)` — kicks off the auth flow; reads `clientId` via `getOAuthClientId(adapter)`
* `handleOAuthCallbackOnLoad()` — runs via `startup-oauth-callbacks.js` init; reads `pending.clientId` from sessionStorage (set at begin time, NOT from runtime config)
* `getConnection(adapterId)` / `listConnectedSources()` / `disconnectAdapter(adapterId)`
* `syncNow(adapterId, opts?)` / `forceBackfill(adapterId, days)`
* `initWearableScheduler()` — visibility-change + 6h interval poll; awaits `runtimeConfigReady()` before first sync to prevent race against override fetch
* `loadWearableRuntimeConfig()` — POSTs `{wearable_runtime_config: true}` to `/api/proxy`, applies the returned `*_CLIENT_ID` overrides via `applyOAuthOverrides`. Memoized as a promise raced against a 1.5s soft timeout

**Window exports:** none

***

### Wearables vendor adapters

Each connected source ships as a pair: `wearables-<vendor>.js` (read API) + `wearables-<vendor>-auth.js` (OAuth dance). Apple Health and Manual skip the auth half — Apple is file-import only, Manual is fully local. Tokens never leave the device; `sync-payload.js` strips `wearableConnections` from the synced payload (and again on pull as defense-in-depth).

**OAuth2 (server-side secret):** `wearables-oura.js` + `…-oura-auth.js`, `…-withings.js` + `…-withings-auth.js`, `…-ultrahuman.js` + `…-ultrahuman-auth.js`, `…-polar.js` + `…-polar-auth.js`. Secrets live in `.env.local` + `/api/proxy`.

**OAuth2 PKCE (no secret):** `wearables-whoop.js` + `…-whoop-auth.js`, `wearables-fitbit.js` + `…-fitbit-auth.js`. Code verifier + S256 challenge per the IETF spec.

**No OAuth:** `wearables-apple-health.js` (file-import — `parseAppleHealthXml` reads the `export.xml` from a Health zip), `wearables-manual.js` (`logManualMetric` / `logManualBP` + `MANUAL_TAGS` whitelist for what users can log by hand).

**L1 storage + summary derivation:** `wearables-store.js` (per-profile IndexedDB at `labcharts-wearables-{profileId}`; two-phase upsertDailyBatch), `wearables-summary.js` (L2 derivation; vendor sources read a 90-day window, while sparse manual rows read all history so older hand-entered BP/pulse/weight entries stay visible; write gate — 5% d7 shift / trend flip / 14d force-refresh / source flip / metric removal triggers).

**UI surface:** `wearables.js` (dashboard strip, source picker, reorder mode, empty-card manual-log forms, and legacy window facade), `wearables-detail-modal.js` (metric detail modal, Chart.js series, manual-entry list/add/delete, focus trap, EMF sleep nudge), `wearables-formatters.js` (shared value/date formatting), `wearables-manual-form-ui.js` (shared manual chip/note form snippets), `wearables-settings-panel.js` (Settings → Wearables rows, connect/sync/backfill/disconnect actions, Apple Health file-import UI, manual-source counts/delete action, strip visibility toggle), `brand-assets.js` (per-vendor logo registry — `iconLight/Dark` for in-app rows, `signInLight/Dark` for landing-site Connect buttons, `mono` SVG fallback while a vendor logo is gated).

***

### Knowledge Base in-browser stack

`lens.js` is the dispatcher; the 4 sibling `lens-local-*` modules are the in-browser implementation. The dispatcher routes between in-browser and external-server backends per the user's selection.

* `lens-local.js` — main-thread shell: spawns a module worker, posts ingest/query/list/activate/delete messages, exposes a Promise-based API to `lens.js`.
* `lens-local-worker.js` — module worker. Loads transformers.js (currently from jsdelivr — see `update-vendor.sh` notes), runs WebGPU embedding when available with WASM fallback, owns OPFS persistence (`FileSystemSyncAccessHandle`), library CRUD, MMR re-rank (λ named `MMR_LAMBDA`), per-library model selection from a 4-model catalog.
* `lens-local-utils.js` — pure helpers: `chunkText`, `mmrSelect`, hashing, vector-pack helpers. Importable from both threads.
* `lens-local-parsers.js` — main-thread document parsers (PDF via `pdfjs-loader.js`, DOCX via mammoth, ZIP via JSZip, plus plain text/markdown/CSV).

***

### Other recently-extracted modules

Not separately documented because their exports are best read from source — kept thin on purpose.

* `chat-images.js` — image attachment lifecycle (`getPendingAttachments`, `clearAttachments`, etc.). Owns the `_pendingAttachments` queue. Extracted from `chat.js` in v1.21.9.
* `chat-threads.js` — conversation thread CRUD, list rendering, autoname, `onChatSaved` debounce trigger. Extracted in v1.21.9.
* `markdown.js` — XSS-safe markdown rendering (`applyInlineMarkdown` for spans, `renderMarkdown` for full blocks). 34 dedicated XSS test assertions.
* `lab-context.js` — lab-data → AI-prompt context assembly; memoized via fingerprint that includes wearable summary, change history, and all 9 context cards.
* `provider-panels.js` — Settings → AI provider behavior, validation, balances, dropdown refresh, globals.
* `provider-panel-renderers.js` — Settings → AI per-provider panel markup (Venice / OpenRouter / Routstr / PPQ / Local AI / Custom).
* `pdfjs-loader.js` — cached dynamic import of vendored pdf.js ESM. Pins `isEvalSupported: false` defense-in-depth on every `getDocument` call.
* `sun-body-silhouette.js` — anatomical sun-session body-region picker, stock-figure region-map hit testing, and async selection overlay. Re-exported by `sun.js` for compatibility.
* `sun-active-session.js` — quick-log/start modal, live active-session ticker, Simpson live-dose integration, and active-session compatibility handlers. Dependency-injected from `sun.js` so persistence and hydration stay in the owner module.
* `sun-session-ui.js` — saved sun-session row/detail rendering, detailed past-session modal, channel chips, delete, and edit-duration UI. Dependency-injected from `sun.js` so storage, hydration, and dose math stay in the owning module.
* `light-device-setup-modal.js` — add-device preset picker, custom-device form, and URL/photo AI spec extraction. Dependency-injected from `light-devices.js` so device persistence and channel defaults stay in the owning module.
* `light-device-session-modal.js` — log/start light therapy device session modal; dependency-injected from `light-devices.js` so session persistence and active timer state stay in the owning module.
* `light-env-audits.js` — saved Light Environment snapshots, audit comparison renderer, and audit-specific window handlers. Dependency-injected from `light-env.js` so rooms/screens remain owned by the environment module.
* `light-env-evening.js` — canonical room after-sunset exposure helpers; normalizes legacy boolean room rows to numeric `eveningHoursAfterSunset` while leaving screen `eveningUseAfterSunset` numeric.
* `light-tool-camera.js` / `light-tool-camera-modals.js` — shared camera lock, row-banding, aiming guides, lux calibration, and the camera-backed Light tool modal flows. Dependency-injected from `light-tools.js` so measurement persistence stays in the owning module.
* `supplement-warnings.js` / `food-contaminants.js` — keyword scanners that build "harm flag" lists for the AI context.
* `emf.js` — Baubiologie SBM-2015 EMF assessment as a sub-module of the Environment context card.
* `sync.js` — Public sync entry/barrel; exports sync lifecycle, actions, diagnostics, relay, identity, and cutover APIs while delegating dependency wiring to `sync-configure.js`.
* `sync-configure.js` — Sync subsystem dependency wiring; configures relay health, push/pull/subscription/tombstone/diagnostic/UI/action/recovery/reconcile modules and browser window bindings.
* `sync-push.js` — outbound profile push path, in-flight push watchdog, Phase 2 drift auto-revert, and Evolu profileData insert/update.
* `sync-push-deltas.js` — push-side per-row delta planning/application; walks DELTA\_ARRAYS/MAPS/SCALARS before blob writes, advances snapshots after onComplete, and records delta telemetry.
* `sync-pull.js` — inbound pull orchestration and row loop; delegates one-time cleanup, active-profile UI refresh, and rebroadcast scheduling to helper modules.
* `sync-pull-merge.js` — pull row recovery/dedupe, importedData blob/per-row merge, wearable-token preservation, metadata-only genetics blob protection, and profile metadata merge helpers.
* `sync-pull-maintenance.js` — pull-path one-time migrations such as stale `-sync-hash` localStorage cleanup.
* `sync-pull-active-refresh.js` — active-profile in-memory update, migration, chat reload, sidebar rebuild, current-view refresh, toast, and sync-applied event after a pull.
* `sync-pull-rebroadcast.js` — pull-side rebroadcast gating, budget checks, push-pending guard, and delayed active-profile push scheduling.
* `data-merge.js` — importedData blob merge helpers, shared record freshness comparison, tombstone state merge, and sync-aware array mutation helpers (`appendImportedArrayItem`, `replaceImportedArrayItem`, `deleteImportedArrayItem`, `deleteImportedArrayItems`, `clearImportedArray`) used by feature modules so identity edits/deletes record the right tombstones before the next sync push.
* `sync-cutover.js` — readiness-gated Phase 2 lean-sync cutover enable/disable bridge over payload flags and delta readiness.
* `sync-delta.js` — per-row CRDT delta facade; config fan-out, apply helpers, and compatibility re-exports for planners, snapshots, merge, telemetry, and readiness.
* `sync-delta-planners.js` — push-side planner facade; configures shared planner dependency context and compatibility re-exports for array/map/scalar planners.
* `sync-delta-planner-context.js` — shared Evolu/itemRow query dependency access for push-side delta planners.
* `sync-delta-array-planner.js` — push-side array planner; diffs array items against snapshots and emits itemRow insert/update/tombstone ops with tombstone-storm protection.
* `sync-delta-map-planner.js` — push-side keyed-map planner; diffs keyed objects against snapshots, preserves original raw keys in payloads, and guards genetics SNP empty-map races.
* `sync-delta-scalar-planner.js` — push-side scalar planner; handles singleton fields, most-recent canonical row selection, and null-transition tombstones.
* `sync-delta-snapshot.js` — delta snapshot keying, reads, plannedAt-gated writes, and snapshot clear helpers.
* `sync-delta-merge.js` — pull-side itemRow grouping and merge-shape dispatch into importedData after blob merge.
* `sync-delta-merge-shapes.js` — pull-side row overlay facade; compatibility re-exports for array/map/scalar merge helpers.
* `sync-delta-row-codec.js` — shared itemRow payload decoder for pull-side delta merge helpers, including gzip envelope handling.
* `sync-delta-array-merge.js` — pull-side array row overlay helper; owns item tombstones, nested-path writes, dedupe, composite caps, and pull delta telemetry.
* `sync-delta-map-merge.js` — pull-side keyed-map row overlay helper; owns raw-key preservation, synth-id tombstones, proto guards, nested-path writes, and pull delta telemetry.
* `sync-delta-scalar-merge.js` — pull-side scalar row overlay helper; owns singleton LWW selection, null tombstones, dotted-path writes, genetics SNP preservation, and pull delta telemetry.
* `sync-delta-registry.js` — delta registry facade; compatibility re-exports for surfaces, per-surface config, and identity helpers.
* `sync-delta-surfaces.js` — DELTA\_ARRAYS/MAPS/SCALARS importedData surface lists for per-row sync.
* `sync-delta-surface-config.js` — per-surface itemId/keyId overrides for array/map delta planners and pull overlays.
* `sync-delta-id.js` — shared per-row identity helpers, stable hash, allowlist guard, and proto-pollution key rejection.
* `sync-delta-observability.js` — delta observability facade; compatibility re-exports for telemetry, pull snapshots, readiness, and injected Evolu query access.
* `sync-delta-observability-context.js` — shared Evolu/itemRow query dependency access for delta observability helpers.
* `sync-delta-pull-snapshot.js` — in-memory pull-side row-count snapshots for Sync Diagnose delta replication checks.
* `sync-delta-telemetry.js` — per-push delta telemetry storage, aggregation, reset, and latest pull-snapshot reporting.
* `sync-delta-readiness.js` — Phase 2 cutover readiness checks across array/map/scalar delta surfaces.
* `sync-apply.js` — inbound sync apply helpers for AI provider settings and display prefs, with compatibility re-exports for chat apply helpers.
* `sync-chat-apply.js` — inbound chat thread/message application, chat delete tombstones, and local freshness locks that protect just-edited local chat from stale remote rows.
* `sync-tombstones.js` — remote profile delete propagation helpers, inbound tombstone application, batched-delete quarantine, and apply/reject pending tombstone actions for Settings.
* `sync-messenger.js` — Agent Access token/context-key helpers, owner-bound encrypted context writes, revoke helpers, and debounced lab-context push to the Agent Access context gateway.
* `sync-environment.js` — relay URL selection, relay connectivity probing, and browser capability checks for Evolu sync.
* `sync-identity.js` — BIP-39/QR lazy loaders plus mnemonic read/restore helpers for Evolu owner identity.
* `sync-diagnostics.js` — Evolu diagnostics facade; stable exports for configuration, row snapshots, and copy-text formatting.
* `sync-diagnostics-context.js` — Injected Evolu/query/subscription state accessors for sync diagnostics.
* `sync-diagnostics-snapshot.js` — Evolu row diagnostics snapshot assembly, including live/tombstone rows, active imported counts, delta telemetry, and lean-sync readiness.
* `sync-diagnostics-text.js` — Plain-text Sync Diagnose copy formatting for support/debug handoff.
* `sync-diagnose-ui.js` — Sync Diagnose modal lifecycle, copy snapshot handling, and compatibility re-exports for Diagnose action handlers.
* `sync-diagnose-render.js` — Pure Sync Diagnose panel/table markup for identity, relay health, relay storage, delta telemetry, lean-sync readiness, and local Evolu rows.
* `sync-diagnose-actions.js` — Sync Diagnose action facade; keeps the public imports stable while delegating to focused action modules.
* `sync-diagnose-actions-context.js` — Shared injected dependencies for Diagnose actions, including sync enable/restore, push, cutover toggles, and modal refresh.
* `sync-diagnose-relay-actions.js` — Sync Diagnose relay storage refresh and self-serve compaction handlers.
* `sync-diagnose-identity-actions.js` — Sync Diagnose identity rotation modal, BIP-39 mnemonic generation, QR display, copy fallback, and local apply flow.
* `sync-diagnose-cutover-actions.js` — Sync Diagnose telemetry reset plus lean-sync enable/backfill/disable handlers.
* `sync-actions.js` — user-triggered sync actions, all-profile initial push, and compatibility re-exports for save hooks and storage cleanup.
* `sync-save-hooks.js` — save/chat/profile-metadata debounce hooks, AI-setting push scheduling, per-profile timer cleanup, and profile importedData reads for initial push.
* `sync-storage-cleanup.js` — emergency sync storage compaction; clears model-list caches, trims oversized `changeHistory`, saves the active profile, and reports freed bytes.
* `sync-ui.js` — header sync badge, popover rendering, status subscription, and activity-log copy helpers.
* `sync-payload-collectors.js` — Local AI/chat/display settings collection for sync payloads, including synced setting allowlists and chat thread tombstone keys.
* `sync-payload.js` — Evolu wire-payload helpers; outbound envelope assembly, Phase 2 cutover payload shape, gzip envelope handling, inbound payload parsing, and local-only data stripping for wearable credentials and SNP map rows.
* `sync-relay-health.js` — Evolu relay helper boundary; client-side quota estimate, signed `/self/owner-storage` and `/self/compact-owner` calls, and push-landed health verdict for the silent-reject detector.
* `sync-state.js` — in-memory sync status pub-sub, header badge display-state derivation, recent sync activity ring buffer, and per-profile rebroadcast budget guard.
