# Connect an AI provider to getbased
Source: https://docs.getbased.health/ai-providers
Connect cloud, private-enclave, crypto-paid, custom, local, and remote compatible AI backends, then compare their lab-import accuracy and speed.
getbased supports OpenRouter, PPQ, Routstr, Venice, Custom API, Local AI, and **CLI agents** for chat and supported AI features. Some providers also expose private or encrypted modes, such as PPQ Private TEE and Venice Encrypted TEE Mode. You can switch in **Settings → AI** without losing your data. Direct-provider requests go from the browser to the selected endpoint without a getbased relay fallback. CLI routes go through your local Companion to the selected agent or personal gateway. Supported browser credentials are wrapped with a device key when passphrase encryption is off and with your passphrase-derived key when it is on.
Already use an installed agent? [Connect it through Companion](/guides/cli-agents). **Local** means a compatible model server; **CLI agents** means an agent harness with its own configuration and account. Neither label guarantees on-device inference.
Voice uses a separate service chosen in **Settings → Voice**. **Same as chat** can reuse a connected OpenRouter, PPQ, or Venice account and otherwise falls back on-device. You can also select on-device voice, a compatible voice server, one of those AI accounts, xAI, or ElevenLabs independently from the provider that generates chat answers. See [Use voice in AI chat](/guides/voice).
Before the first request to a cloud AI or cloud voice recipient, getbased shows a recipient-specific consent prompt. Nothing is sent if you decline. An approval also covers later requests you choose to initiate with that recipient; review or withdraw approvals at any time in **Settings → Privacy**.
## Which features need AI?
| Feature | Requires AI? |
| - | - |
| PDF import | Yes |
| Meal-photo or nutrition-label analysis | Yes, with an image-capable model |
| Manual meal entry, targets, and nutrition history | No |
| Meal model benchmarks | Yes, with at least one configured image-capable model |
| AI chat panel | Yes |
| Focus card (dashboard insight) | Yes |
| Live context-card summaries and status dots | Yes (demo profiles include precomputed versions) |
| Live AI-generated card tips | Yes (demo profiles include precomputed versions) |
| Web search in chat | Yes (OpenRouter, PPQ, Venice) |
| Test AI models with the built-in report | Yes |
| Charts, tables, trend alerts | No |
| Manual entry | No |
| JSON export / import | No |
| Correlations, compare dates | No |
All non-AI features work fully without any provider configured. You can load a demo profile, enter results manually, and explore charts and trends before setting up AI.
The built-in demo profiles ship with precomputed context-card insights. Local AI can recalculate edited demo cards automatically, while cloud and other potentially paid providers stay off for demo-card recalculation until you explicitly enable live AI for the selected provider and model. The consent is session-only, so a reload or model/provider change requires a new confirmation. See [Health context](/guides/context-cards#demo-profiles-and-ai-cost).
## Set up a provider
Open **Settings → AI → CLI agents**, run the displayed **Copy connection command** on the same computer as your browser, and choose **Check connection**. Enable an available Codex, OpenCode, Hermes, Grok Build, or OpenClaw agent. Node.js 20+ and an installed, configured agent are required; the hosted app can use this connection without moving your data to localhost.
The Companion reuses the agent's existing sign-in. Models and supported reasoning choices are available in Settings and beside the chat microphone. Images and non-chat features require declared adapter/model capabilities; personal gateways support chat and, when advertised, text-only feature jobs, but not image imports or the local tool bridge. Read [CLI setup, automatic startup, privacy, and troubleshooting](/guides/cli-agents) before connecting.
OpenRouter is the easiest way to get started. It gives you access to 200+ models — Claude, GPT, Gemini, DeepSeek, Grok, Qwen, and more — with a single account. Pay with a card or USDC. Supports web search in chat.
**Connect with OAuth (easiest):**
Open **Settings** (gear icon in the header) and go to the **AI** tab. Select **OpenRouter**.
Click the **Connect with OpenRouter** button. You'll be redirected to OpenRouter to authorize getbased. No API key needed — the app handles the token exchange automatically.
Back in Settings, pick a model from the curated dropdown. Recommended models are sorted first and marked with a star.
**Connect with an API key:**
Create an account and generate an API key at [openrouter.ai](https://openrouter.ai).
Open **Settings → AI**, select **OpenRouter**, and paste your API key.
Pick a model from the dropdown, or type any OpenRouter model ID into the custom input field.
The OAuth connect button also appears in the chat panel when no provider is configured — you can set up OpenRouter without going to Settings.
PPQ is a pay-per-query AI aggregator with 300+ models. No subscription, no KYC, no identity verification. Top up directly in the app with Bitcoin, Lightning, Monero, Litecoin, or Bitrefill gift cards. Supports web search in chat.
Open **Settings → AI** and select **PPQ**.
Click **Create Account**. This is instant and requires no signup form — PPQ accounts are fully anonymous.
Copy your API key and store it somewhere safe. PPQ accounts are anonymous with no recovery mechanism — if you lose the key, you lose access to your balance.
Click **Top Up** and follow the payment flow. The app shows a QR code and polls for payment confirmation. Supported: Lightning, Bitcoin, Monero, Litecoin, Aqua, and Bitrefill gift cards.
Pick a model from the curated dropdown. Your balance is displayed with color coding so you always know what you have available.
You can also paste an existing API key from [ppq.ai](https://ppq.ai) if you already have an account.
**Private TEE mode:** When PPQ advertises supported private models, enable **Private TEE mode** to encrypt prompts and responses between your browser and a verified Tinfoil enclave. No local proxy is required. The green **🔒✓** indicator appears after attestation succeeds. Web search is disabled while Private TEE mode is active.
Routstr is a decentralized AI network powered by Bitcoin micropayments. getbased has a built-in Cashu eCash wallet — fund it with Lightning, then connect to any Routstr node discovered via Nostr relays. Your prompts go directly from your browser to the node you choose. No account, no subscription.
Open **Settings → AI** and select **Routstr**.
Click **Deposit** and pay the Lightning QR code (or paste a Cashu token from an external wallet). Your wallet balance is shown in the settings panel.
The app discovers online Routstr nodes via Nostr relays. Click **Connect** on any node in the list.
Choose how many sats to deposit to the node for your session. You receive a session key that authenticates your requests.
Your wallet balance and your node session balance are shown separately. You can move sats between them freely at any time.
**Wallet features:**
* **Seed phrase** — a 12-word BIP-39 mnemonic is generated on your first deposit. Write it down — it is the only way to recover your wallet on another device.
* **Lightning withdraw** — send sats to a Lightning address or pay a BOLT11 invoice.
* **Cashu token send/receive** — withdraw as a shareable Cashu token, or deposit one from an external wallet.
* **Node withdraw** — pull remaining sats back from a node into your local wallet.
Your seed phrase is shown once when you first deposit. You can view it again from the wallet menu under Seed & Restore. Without it, your wallet cannot be recovered.
For the full wallet, refund, token export, and recovery behavior, see [Routstr AI provider](/guides/routstr).
**Private TEE mode:** Nodes that support attested private models show a **Private TEE** badge. Enable the toggle to encrypt prompt and response bodies in your browser before the node forwards them to a verified Tinfoil enclave. The node can still see your session credential, selected model, request timing and size, network metadata, and billing information. Web search and image attachments are disabled.
Venice is a hosted AI provider that advertises a no-log policy. That is a provider policy, not something getbased independently verifies. Venice also proxies access to third-party models. Web search is available with standard Venice models but disabled in encrypted mode.
Create an account and generate an API key at [venice.ai](https://venice.ai).
Open **Settings → AI** and select **Venice**.
Enter your API key in the field provided.
Pick a model from the dropdown. The model catalog identifies which current models support Venice's encrypted protocol.
**Encrypted TEE Mode:** Message content is encrypted in your browser with ECDH, HKDF-SHA256, and AES-256-GCM. Before connecting, getbased requires full Intel TDX verification with DCAP and signed NVIDIA GPU evidence through NRAS. If either check fails, the encrypted session does not start. The **🔒 TEE + GPU** badge means both required checks passed; hover, focus, or tap it for the individual results and remaining limits. The checks do not prove that the TDX environment and GPU run together, enforce an approved Venice code-measurement allowlist, or independently verify the source of every response. The API key, model, roles, request settings, timing, sizes, and network metadata remain visible to Venice. Web search and image attachments are disabled.
Connect any OpenAI-compatible API endpoint with your own base URL and API key. Works with OpenAI, Mistral, Groq, Together, xAI, Fireworks, Deepinfra, vLLM, LiteLLM, and any service that implements the `/v1/chat/completions` standard.
Open **Settings → AI** and select **Custom**.
Type the base URL of your endpoint — for example, `https://api.openai.com/v1`.
Paste your API key for the service.
Click **Save & Validate**. The app checks your key and fetches the model list from your endpoint. Pick a model from the dropdown, or type a model ID manually if your endpoint doesn't expose a `/v1/models` listing.
Use Custom when you have a direct API key for a service that is not one of the built-in providers. If the service is available through OpenRouter or PPQ, those are easier — they handle model discovery and show pricing automatically.
Custom API endpoints are always called directly from your browser. The endpoint must allow requests from the exact getbased origin. If browser CORS or network policy blocks the request, getbased reports the direct-connection failure and does not fall back to a getbased relay.
Connect directly to an OpenAI-compatible server. That server can run on this device, another machine on your LAN, or a remote host. getbased identifies the execution location in Settings so you can distinguish truly local models from LAN, remote, and cloud-hosted models.
**Local AI describes the connection type, not a privacy guarantee.** Only a loopback server such as `localhost`, `127.0.0.1`, or `[::1]` running a downloaded model keeps requests on this device. LAN and remote endpoints send data off this browser device, and Ollama `:cloud` models run on Ollama's servers.
Install a server locally or obtain the address of a compatible endpoint:
* [Ollama](https://ollama.com) — command-line, easiest setup
* [LM Studio](https://lmstudio.ai) — GUI with drag-and-drop model loading
* Unsloth Studio — compatible local server with automatic model and runtime discovery
* [Jan](https://jan.ai) — open-source desktop app
* llama.cpp, LocalAI, or any OpenAI-compatible server
Download a model to run locally. In Ollama, for example:
```bash theme={null}
ollama pull qwen2.5:14b
```
For reliable lab PDF parsing, use a model with at least 14B parameters. The built-in Model Advisor in Settings shows which of your installed models are suitable for lab analysis.
Open **Settings → AI** and select **Local**.
The default is `http://localhost:11434` for Ollama. Enter the base URL for another local, LAN, or remote server. Add an API key if that endpoint requires one.
Click **Test**. getbased detects native Ollama and LM Studio servers when possible, recognizes Unsloth Studio through its compatible model metadata, and otherwise uses the standard OpenAI-compatible model list. Choose a text-generation model from the discovered list.
### Native and first-class server support
Ollama and LM Studio expose extra information beyond the compatible API. When detected, getbased can show loaded state, context length, quantization, model size, and VRAM use. It can also request a larger context for complex imports and release a loaded model when switching between supported servers on the same machine. Unsloth Studio is recognized as its own Local AI provider, exposes its active runtime when available, and can offer image-capable models for meal analysis.
On a current LM Studio server, a large import can reload the selected model at the planned context, verify the context LM Studio actually fitted into memory, and generate through the streaming compatible endpoint. If the previous instance cannot be unloaded, getbased checks whether it is really gone and otherwise stops with manual-unload guidance instead of loading a duplicate model into memory.
Generic compatible servers continue to work through `/v1/models` and `/v1/chat/completions`, but they may not expose loaded state, context controls, native performance metrics, or model unloading.
If getbased says the loaded context is too small, reload the model with a larger context or use a smaller or chunked input. Imports can request a larger native context from supported Ollama and LM Studio servers. getbased also rejects marker output cut off by the output or context limit instead of accepting a partial report.
### CORS setup
The server must allow requests from the exact origin where you opened getbased. For the hosted app, that origin is `https://app.getbased.health`. Avoid `*` when the server supports an explicit origin, especially if API authentication is enabled.
| Server | How to enable CORS |
| - | - |
| Ollama (Linux) | `OLLAMA_ORIGINS=https://app.getbased.health ollama serve` |
| Ollama (macOS) | `launchctl setenv OLLAMA_ORIGINS "https://app.getbased.health"` then restart Ollama |
| Ollama (Windows) | Add `OLLAMA_ORIGINS` = `https://app.getbased.health` as a system environment variable, then restart Ollama |
| LM Studio | Enable CORS and API authentication in LM Studio Settings |
| Jan | Settings → Advanced → Enable CORS |
If you self-host getbased at another origin, replace `https://app.getbased.health` with the origin shown in your browser, including its scheme and port.
An HTTPS page cannot call a plain HTTP LAN or remote server because browsers block mixed content. Use an HTTPS endpoint, a loopback address, or an encrypted local tunnel. A self-hosted getbased page served over plain HTTP can call an HTTP LAN server when CORS permits it, but that traffic is unencrypted unless a VPN or tunnel protects it.
The Model Advisor appears for discovered Local AI endpoints. It rates suitable text models for lab analysis and shows the metadata the server exposes. Ollama can also suggest a model to pull; remote servers let you enter their available VRAM for a fit estimate.
## Recommended models
The Settings and chat model pickers scope choices to the active provider and privacy mode. Search is available for long catalogs. Supported catalogs show curated groups:
* **Recommended** — the latest, most capable models for lab interpretation, sorted first
* **Other** — all remaining available models
Recommendations are starting points, not a guarantee of clinical accuracy, entitlement, or suitability for a particular report. Check results and compare models on your own task.
Recommendation families and exact IDs change with the catalog. Use the in-app list rather than a fixed list in this guide. CLI catalogs are scoped further to the selected agent and execution target; personal gateways identify their current/default model instead of importing recommendations from unrelated providers. Explicitly unavailable entries are excluded, but an agent may only discover an account restriction when it sends a request.
When the active model exposes reasoning controls, adjust them in Settings or in the chat picker. Unsupported controls stay unavailable rather than guessing from a model name. A model can reason internally without offering a separate effort setting. Supported settings also pass through the selected private/TEE transport; a reasoning slider does not add privacy protection.
Meal-photo selectors are separate: they show only models reported as image-capable and can follow the chat assistant or use a dedicated route. Following a CLI assistant uses its actual selected model and capability, not the last direct-provider model. If the route cannot process images, select a compatible feature route explicitly. Use [Meal Benchmarks](/guides/meals-nutrition#compare-meal-estimates) to compare those models on the same images.
**Use the same model for all your imports.** When you import a lab PDF, the AI generates marker keys (like `biochemistry.glucose`) to map results. Different models may generate slightly different keys for the same marker, which can cause the same biomarker to appear as two separate entries in your charts. Pick a model and stick with it. If you do switch, getbased runs a pre-flight check before each import and warns you if your model has changed since the last import.
## Test before you choose
Open **Settings → Data → Test models on lab reports** to run the same verified 68-result report through different setups. You can compare raw accuracy, getbased's corrected result, speed, tokens, and runtime details side by side.
The comparison works across local models, hosted compatible endpoints, and built-in cloud providers. You can also import the same personal report with different setups and compare the review effort each one required. See [Test and compare AI models](/guides/ai-model-testing).
## How much does it cost?
AI providers usually charge for input and output tokens, and image-capable models may also price image input. Catalogs and prices change frequently. getbased uses the current pricing metadata returned by supported providers when available and shows tracked tokens and estimated cost on chat, meal analysis, and benchmark results. A provider can still bill a canceled or failed request even when it returns no usage count.
| Path | Provider fee |
| - | - |
| Built-in cloud provider | Current provider/model price; shown when usage and pricing are available |
| Custom or hosted compatible endpoint | Determined by that endpoint |
| CLI agent or personal gateway | Existing agent account, subscription limits, or configured API billing; usage reporting depends on the adapter |
| Downloaded model on this device or your own server | No per-request provider fee; hardware and electricity costs remain |
Review the model's current provider price before a large import or multi-model meal benchmark. When the provider returns usage, getbased records it per result so you can compare cost with review quality.
Run a downloaded model on your own hardware with [Ollama](https://ollama.com), [LM Studio](https://lmstudio.ai), Unsloth Studio, or [Jan](https://jan.ai) and pay no provider fee. Hardware needs depend on the model and quantization; the Model Advisor shows what the connected server reports and what may fit.
# Developer documentation
Source: https://docs.getbased.health/developers
Developer guide index for getbased: architecture, data model, AI surfaces, feature internals, testing, deployment, and release gates.
# Developer documentation
getbased is authored in TypeScript and compiled with TypeScript 7. Local development loads the emitted JavaScript as native ES modules; hosted deployments use Rolldown bundles. Source-checkout development requires Node.js 24 and dependencies installed with `npm ci`. npm also provides tests, type checks, architecture checks, Playwright, and deployment tooling.
If you are new here, start with [CONTRIBUTING.md](https://github.com/elkimek/get-based/blob/main/CONTRIBUTING.md) for setup, local runs, tests, and PR guidelines. Use this section when you need implementation details that are too technical for the user guides.
## Start here
| Page | Use it for |
| - | - |
| [Architecture](/developers/architecture) | How the source-module graph, startup phases, lazy features, and production bundles fit together |
| [Module Reference](/developers/module-reference) | Finding the TypeScript module that owns a feature or window binding |
| [Cross-Module Patterns](/developers/cross-module-patterns) | Shared patterns: window exports, state access, dependency boundaries |
## Data model and sync
| Page | Use it for |
| - | - |
| [Marker Model and Category Placement](/developers/marker-model) | Generated schema authoring, stable marker identities, visual placement, and compatibility boundaries |
| [Laboratory Terminology Registry](/developers/marker-terminology) | LOINC, NPU, NČLP, and UCUM mapping isolation and update rules |
| [Data Pipeline](/developers/data-pipeline) | How `getActiveData()` builds the data used by charts and AI context |
| [Lab Markers and Range Internals](/developers/lab-markers-ranges-internals) | Marker identity, range precedence, collection and cycle context, calculations, and migrations |
| [Storage Schema](/developers/storage-schema) | localStorage, importedData, IndexedDB, export/import, and Agent Access state |
| [Report Data Contract](/developers/report-data) | Versioned selected-data snapshot shared by report rendering, summaries, and authorized integrations |
| [Sync and Delta Internals](/developers/sync-delta-internals) | Cross-device sync surfaces, tombstones, import snapshots, and merge rules |
| [Context Assembly](/developers/context-assembly) | How labs, context cards, wearables, notes, and Agent Access context are assembled |
## AI and agents
| Page | Use it for |
| - | - |
| [AI Surfaces Map](/developers/ai-surfaces-map) | Which features use AI and which are deterministic |
| [AI Provider Internals](/developers/ai-provider-internals) | Direct provider routing, cloud consent, private transports, Local AI, OAuth, and web search |
| [CLI agent providers](/developers/cli-agent-providers) | Loopback Companion, adapter and gateway boundaries, capabilities, tools, and lifecycle security |
| [System Prompt Catalog](/developers/system-prompts) | Every developer-controlled instruction family, source location, output contract, and review workflow |
| [Agent Access Relay](/developers/agent-access-relay) | Owner-bound encrypted context relay, setup payload, and local MCP decrypt behavior |
| [Local Lens Internals](/developers/lens-local-internals) | In-browser Knowledge Base storage, chunks, embeddings, and worker behavior |
| [Lens Endpoint Contract](/developers/lens-endpoint-contract) | Wire spec for an external Knowledge Source server |
## Feature internals
| Page | Use it for |
| - | - |
| [Biology Scores](/developers/biology-scores) | Score engine, coverage planner, context gate, adapters, UI, and tests |
| [Chat Internals](/developers/chat-internals) | Composer drafts, thread storage, editing and forks, discussions, personas, sync, and backups |
| [Genome Evidence Methodology](/developers/genome-evidence-methodology) | Catalog claim scope, evidence and relevance grades, privacy boundaries, and contribution review |
| [Supplements and Medications Internals](/developers/supplements-medications-internals) | Therapy lifecycle, reviewed imports, quality evidence, bounded AI context, migration, and sync |
| [Meals and Nutrition Internals](/developers/nutrition-internals) | Meal analysis, editable estimates, encrypted thumbnail storage, summaries, targets, benchmarks, sync, and AI context |
| [Sun Spectrum Model](/developers/sun-spectrum-model) | Bird-Riordan spectral reconstruction for Light sessions |
| [Wearables Internals](/developers/wearables-internals) | OAuth, local daily rows, synced summaries, runtime config, and Agent Access series |
## Operations and release checks
| Page | Use it for |
| - | - |
| [Testing](/developers/testing) | Test commands, browser specs, and how to add assertions |
| [Deployment](/developers/deployment) | Vercel config, CSP, service worker, API routes, and PWA behavior |
| [Legal Consent](/developers/legal-consent) | First-launch Terms/Privacy gate, re-consent, startup ordering, and release checks |
| [Routstr/Cashu Safety](/developers/routstr-cashu-safety) | Money-path invariants and required tests for wallet/node/session flows |
| [Codex PR Coverage Workflow](/developers/codex-pr-coverage-workflow) | PR review and coverage workflow for agent-assisted changes |
## Maintenance rule
When adding a user-visible feature, update both sides in this repo:
* the user guide that explains what the feature does and how to use it;
* the developer page that explains storage, sync, privacy, tests, and owner modules.
Public guides belong here. The app repository keeps `AGENTS.md`, `ARCHITECTURE.md`, the generated `MODULE_MAP.md`, and maintained engineering contracts alongside executable checks. Task progress, checkpoints, and audit logs stay in its ignored `.local-notes/` directory.
# Agent Access relay
Source: https://docs.getbased.health/developers/agent-access-relay
Developer contract for owner-bound Agent Access writes, encrypted context envelopes, token mapping, and MCP decryption.
# Agent Access relay
Agent Access is not the Evolu CRDT sync path. It is a separate `/api/context` relay path that must prove its own privacy boundary.
The safe contract is:
```text theme={null}
Browser
├─ resolves the current Sync owner
├─ renders the same context text used by in-app AI
├─ encrypts that text with a dedicated Agent Context key
├─ signs the write with the Sync owner write key
└─ POSTs the encrypted envelope to /api/context
Context gateway
├─ verifies the owner signature against the Evolu relay DB
├─ maps sha256(GETBASED_TOKEN) → ownerId
├─ stores ciphertext under ownerId/profileId quota
└─ never receives the Agent Context key
Local MCP
├─ fetches ciphertext with GETBASED_TOKEN
└─ decrypts locally with GETBASED_AGENT_CONTEXT_KEY
```
The user-facing setup path is generated by `settings-agent-access-panel.ts`. It builds a private one-paste command:
```bash theme={null}
curl -fsSL https://getbased.health/install.sh | bash -s -- connect --setup 'gbsetup_v1_...'
```
Supported `` values are `hermes`, `openclaw`, `claude-code`, `claude-desktop`, `cursor`, `cline`, and `codex`. The `gbsetup_v1_...` payload is base64url JSON containing the Agent Access token, Agent Context key, gateway URL, selected client, version, and creation timestamp. Installers and logs must redact the setup payload; never print the blob after `--setup`.
## Capabilities
| Value | Purpose | Must not be used for |
| - | - | - |
| Sync owner write key | HMAC proof that the browser controls the Sync owner | Encrypting Agent Access context or leaving the browser |
| `GETBASED_TOKEN` | Bearer authorization for reading from the relay | AES key material or storage namespace |
| `GETBASED_AGENT_CONTEXT_KEY` | Local AES-GCM decryption key for MCP clients | Relay authorization or server-side storage lookup |
## Browser write shape
The context gateway keeps the legacy top-level `context` string field for compatibility. The string must contain encrypted-envelope JSON, not plaintext context.
```json theme={null}
{
"ownerId": "owner-id-from-sync",
"profileId": "profile-id",
"timestamp": 1782290000000,
"signature": "hex-hmac",
"context": "{\"encryptedContext\":{\"version\":2,\"alg\":\"AES-256-GCM\",\"keyDerivation\":\"raw-256-bit-key\",\"keyId\":\"...\",\"iv\":\"...\",\"ciphertext\":\"...\"}}"
}
```
The write signature is:
```text theme={null}
HMAC-SHA256(
owner.writeKey,
"agent-context:{ownerId}:{timestamp}:{sha256(GETBASED_TOKEN)}:{profileId}:{sha256(context)}"
)
```
The relay rejects unsigned writes, stale timestamps, token-owner mismatches, profile-limit excess, token-limit excess, and owner quota excess.
## Context assembly contract
The browser renders Agent Access text through `buildLabContext({ skipGroupFilter: true, ignoreContextToggles: true, ... })`. It is the same serializer used by in-app AI, but Agent Access intentionally bypasses in-app Data sources because **Settings → Agent Access** is the separate external-sharing permission.
Do not maintain a second context-card formatter for the relay. Stored values that mean “answered negatively” must survive just like positive answers: Diet & Digestion serializes values such as `none` and `normal`, and Light & Circadian serializes `skinType`. Tests should decrypt the outgoing envelope and assert those fields in the plaintext, not merely assert that a POST occurred.
## Envelope shape
Version 2 envelopes use a random raw 256-bit Agent Context key. There is no v2 salt because no token/password KDF is involved.
```json theme={null}
{
"encryptedContext": {
"version": 2,
"alg": "AES-256-GCM",
"keyDerivation": "raw-256-bit-key",
"keyId": "sha256-prefix-base64url",
"iv": "base64",
"ciphertext": "base64"
}
}
```
AES-GCM additional authenticated data is:
```text theme={null}
getbased-agent-context-v2:{profileId}
```
## MCP read/decrypt behavior
`getbased-mcp` fetches the relay with:
```http theme={null}
GET /api/context?profile={profileId}
Authorization: Bearer {GETBASED_TOKEN}
```
Then it parses `data.context`, detects `encryptedContext.version === 2`, and decrypts locally with `GETBASED_AGENT_CONTEXT_KEY`. Wrong or missing keys must fail closed. The MCP may retain legacy plaintext/v1 decrypt compatibility only for rollout, not as the expected path.
## Wearable daily-series section
Agent Access always receives the compact wearable summary when wearable context is enabled. The Settings → Agent Access panel can also sync a daily-series window: off, 7 days, 30 days, or 90 days. That preference is stored with the profile as `agentAccessWearableSeriesDays` and travels through encrypted Sync.
When enabled, the browser pushes a separate machine-readable section for MCP clients, shaped as `wearables-series-{N}d`: one metric per line, daily values oldest → newest, `—` for missing days, and source labels in parentheses. The hosted relay still stores only the encrypted context envelope; the MCP decrypts and exposes the section locally.
## Clean-slate deployment note
If an old context gateway ever stored plaintext summaries, do not migrate those files into the owner-bound layout. Delete the legacy context store and require fresh browser pushes from updated clients. The fixed relay is intentionally fail-closed for old clients that do not send owner proof.
## Verification gates
Before marking Agent Access ready:
1. Run relay unit tests for signature validation, stale timestamp rejection, owner quota, profile limits, and token limits.
2. Run browser/app tests proving the POST body contains `context: JSON.stringify({ encryptedContext })`, not plaintext.
3. Decrypt a captured browser envelope and prove redesigned context-card fields—including explicit negative/normal digestive answers and skin type—match in-app `buildLabContext()` output.
4. Run MCP tests proving v2 decrypt works with `GETBASED_AGENT_CONTEXT_KEY` and wrong-key/token-alone decrypt fails.
5. Run a live relay E2E against the deployed gateway:
* insert or create a disposable owner;
* POST an encrypted sentinel;
* fetch the relay payload directly;
* prove the sentinel is absent from the serialized relay response;
* prove the envelope has `version: 2`, `keyDerivation: raw-256-bit-key`, `iv`, `ciphertext`, `keyId`, and no salt;
* prove `getbased-mcp` decrypts locally with the right key;
* prove wrong key fails closed;
* delete/revoke the test token mapping.
Good production E2E summary shape:
```json theme={null}
{
"postStatus": 200,
"fetchStatus": 200,
"relayPlaintextAbsent": true,
"relayHasEncryptedEnvelope": true,
"envelopeVersion": 2,
"keyDerivation": "raw-256-bit-key",
"hasSalt": false,
"mcpDecryptContainsSentinel": true,
"mcpWrongKeyFailsClosed": true
}
```
# AI model testing internals
Source: https://docs.getbased.health/developers/ai-model-testing-internals
Developer contract for import benchmark capture, reference scoring, comparison identity, local-only persistence, and fixture maintenance.
The model-testing feature instruments the real lab-import pipeline. It records enough information to compare provider/model setups without turning runtime diagnostics into synced health data.
## Module map
| Module | Role |
| - | - |
| `js/import-benchmarks.ts` | Starts, updates, finishes, persists, recovers, and deletes model-test records |
| `js/import-reference-benchmark.ts` | Loads the bundled fixture, runs the real parser, and scores raw and reconciled output |
| `js/settings-import-benchmark-controller.ts` | Owns the modal lifecycle, reference-test action, selection, comparison, and deletion |
| `js/settings-data.ts` | Normalizes saved records and renders cards, metrics, discrepancies, and comparison tables |
| `js/pdf-import.ts` | Captures timings and diagnostics during normal imports |
| `js/pdf-import-commit.ts` | Finalizes review-quality metrics when the user confirms an import |
| `js/sync-payload.ts` | Removes device-local benchmark arrays from outbound sync payloads |
| `js/sync-pull-merge.ts` | Preserves local benchmark arrays during inbound merges |
The bundled assets are `data/import-benchmark-reference-us-v2.pdf` and `data/import-benchmark-reference-us-v2.gold.json`. `scripts/generate-import-reference-pdf.mts` regenerates the PDF from the manifest.
## Record lifecycle
`startImportBenchmark()` appends a record before the expensive work begins. Import stages update the same record with transport, timing, usage, and parser diagnostics. A normal run becomes comparable only after the user confirms the preview.
Comparable statuses are:
* `confirmed` for a normal report import;
* `reference-scored` for a completed reference run with differences; and
* `reference-passed` for an exact reference match.
Failed, cancelled, preview-only, or otherwise incomplete records remain diagnostic-only. The UI groups them under **Tests that didn't finish** and does not offer comparison checkboxes.
Records are capped at 50 per profile. Deletion writes a bounded tombstone list so legacy import snapshots do not immediately reconstruct a deleted diagnostic. Deleting a benchmark never mutates lab entries or per-file import snapshots.
## Reference fixture and protocol
The current fixture identity is `getbased-reference-us-v2`, fixture version `2`, benchmark protocol version `2`, with 68 expected markers across a three-page text PDF.
`runBundledImportReferenceBenchmark()`:
1. captures the provider and model selected at the start;
2. loads the manifest and PDF without browser caching;
3. extracts the PDF text through the production extractor;
4. calls the production AI parser with raw-output capture and deterministic benchmark mode;
5. scores both raw model output and the reconciled pipeline result; and
6. persists either a completed score or a scrubbed failure record.
The gold card is generated in code by `getBundledImportReferenceGoldBenchmark()`. It is a locked comparison target, not a stored inference run.
When the fixture content or expected output changes, update the fixture ID or version. When parsing or scoring semantics change, bump `IMPORT_REFERENCE_PROTOCOL_VERSION`. Runs with different fixture/protocol identities must not compare.
## Scoring layers
Reference scoring deliberately keeps two layers:
* **Raw model** measures missing and unexpected results, exact marker fields, precision, recall, F1, marker mapping, values, units, reference ranges, date, and report type before deterministic correction.
* **Pipeline result** measures the final output after deterministic marker reconciliation.
Do not overwrite raw scores with corrected values. That would hide provider/model errors and turn the benchmark into a test of the reconciliation layer alone.
Exact discrepancy groups retain a bounded, escaped representation of expected and actual fields for review. The UI must continue to state that the result is not a clinical safety certification.
Normal report imports have no gold answer. Their quality signals come from the confirmation review: kept, clean, corrected mapping/value/unit, excluded, unmatched, and date-correction counts.
## Comparison identity
`importBenchmarksUseSameInput()` is the comparison gate:
* normal imports match by input hash;
* reference runs match by fixture ID, fixture version, and protocol version.
The first selected run is the baseline. Every later column shows the provider, model, and delta from that baseline. Provider remains part of the displayed setup identity, so the same model on OpenRouter and Venice produces two distinguishable columns.
Never allow a filename alone to establish equality. Different files can share a name, and the same report can be renamed.
## Runtime diagnostics
All providers contribute provider/model identity, timings, and usage when available. Local AI can additionally capture normalized adapter metadata such as:
* native versus OpenAI-compatible API path;
* local, LAN, remote, or cloud execution location;
* context and maximum context length;
* quantization and parameter size;
* allocated VRAM and loaded-at-start state; and
* native generation speed, load duration, and first-response timing.
Endpoint capabilities vary, so all optional metrics must render safely when absent.
## Persistence and privacy invariants
Benchmark arrays live inside the active profile's imported-data blob, but they are deliberately device-local:
* `persistImportBenchmarks()` calls `saveImportedData({ immediate: true, skipSync: true })`;
* `stripLocalOnlyProfileData()` removes `importBenchmarks` and `deletedImportBenchmarkIds` before legacy and delta sync serialization; and
* inbound merge restores the local values after merging remote data.
Full database and folder backups retain the imported-data blob. The curated single-profile export does not include `importBenchmarks`.
Failure text is limited to 500 characters and scrubs bearer tokens and `sk-`-style secrets. Keep raw prompts, report text, API keys, authorization headers, and provider response bodies out of benchmark records.
## Verification
Run the full suite after changing this surface:
```bash theme={null}
./run-tests.sh
```
The primary browser contract is `tests/playwright/import-benchmarks.spec.ts`. It covers modal behavior, same-input gating, cloud-provider identity, raw-versus-corrected scoring, exact discrepancy review, background completion, normal-import capture, deletion, secret scrubbing, sync exclusion, and fixture integrity.
Local runtime metadata depends on the provider adapter contract covered by `tests/api-provider-contracts.test.ts` and `tests/test-provider-local-ai-runtime.ts`.
When changing the reference manifest, regenerate the PDF and run the fixture-integrity tests:
```bash theme={null}
node scripts/generate-import-reference-pdf.mjs
./run-tests.sh
```
The generated PDF and gold manifest are application assets. Keep their runtime URLs in the precache inventory authored in `service-worker.ts`, and check the production build and offline behavior. Build identity invalidates changed assets; update `version.ts` and release notes only for an intentional versioned release.
# AI provider internals
Source: https://docs.getbased.health/developers/ai-provider-internals
Developer contract for direct provider routing, cloud consent, private transports, Local AI adapters and lifecycle, model storage, OAuth, and web-search boundaries.
AI provider code touches health context, API keys, browser transport, private TEE modes, web search, import parsing, and chat streaming. Treat it as a privacy/security surface, not just a dropdown.
## Provider map
CLI agents are a distinct backend, not another `/chat/completions` endpoint. See [CLI agent provider internals](/developers/cli-agent-providers) for the Companion, structured tools, gateways, and lifecycle authorization.
| Provider | Main path | Notes |
| - | - | - |
| OpenRouter | direct API / OAuth token | PKCE/state validation for OAuth; supports web search where model/provider allows |
| PPQ | direct API key | anonymous account/top-up flow; regular and private model selections are separate |
| PPQ Private TEE | Tinfoil/private endpoint wrapper | browser-encrypted request body, attestation/lock state, web search disabled |
| Routstr | Cashu/Nostr node session | wallet and node balances are separate; real-funds paths need safety tests |
| Venice | standard or Encrypted TEE Mode | Encrypts message content; requires TDX binding, Intel DCAP, NVIDIA NRAS, and NRAS-token signature checks; metadata remains visible; web search/images are disabled |
| Custom API | direct browser request | endpoint must support browser CORS; no getbased relay fallback |
| Local AI | direct browser request to a compatible endpoint | endpoint can be loopback, LAN, remote, or cloud-tagged; Ollama, LM Studio, and Unsloth Studio have identified adapter capabilities |
## Core modules
The shared chat model picker (`chat-model-controls.ts`, `chat-model-preferences.ts`) follows the active provider and privacy mode immediately. Recommended groups are scoped to that catalog, not merged with other saved providers. `reasoning-capabilities.ts` normalizes explicit reasoning metadata and narrow documented fallbacks; unsupported models must not get an invented effort control. The same selection is carried inside attested private transports when supported.
OpenRouter supplies per-model controls, Venice supplies reasoning metadata, and PPQ/Routstr/Custom retain supported compatible metadata. Ollama and LM Studio can expose native thinking controls. Jan, Unsloth Studio, llama.cpp, and other compatible endpoints only get controls their metadata/serving contract supports. Built-in reasoning is not evidence of configurable effort.
For non-chat feature calls use `ai-feature-routing.ts` so CLI selection, global pause, text/image capability, and no-silent-fallback behavior remain consistent. Keep direct transport functions in the API modules; do not implement a second CLI dispatcher per feature.
| Module | Role |
| - | - |
| `api.ts` | provider routing, model lists, streaming calls, PPQ private transport creation |
| `api-provider-storage.ts` | localStorage keys for provider, model, private-mode, and key state |
| `provider-panel-renderers.ts` | Settings → AI panel markup, PPQ Private TEE UI, model dropdowns |
| `provider-model-controls.ts` | dropdown changes, model persistence, E2EE/private-mode switching |
| `provider-ppq-panels.ts` | PPQ account, balance, top-up, and private-model panel behavior |
| `provider-local-ai-controls.ts` | Local AI URL validation, discovery UI, CORS help, Advisor, and server switching |
| `local-ai-provider-registry.ts` | normalized Local AI adapter registry and detection precedence |
| `local-ai-provider-{ollama,lmstudio,openai-compatible}.ts` | backend discovery, inference, context, performance, and unload implementations |
| `local-ai-provider-shared.ts` | URL normalization, location classification, model normalization, and secret redaction |
| `local-ai-discovery.ts` | cached discovery orchestration and runtime metadata updates |
| `api-local.ts` | Local AI prompt sizing, context planning, native request selection, and normalized diagnostics |
| `local-ai-lifecycle.ts` | memory-only runtime tracking and same-machine model handoff/unload |
| `chat-attestation.ts` | lock/attestation status shown in chat |
| `api-custom.ts` | Browser-direct Custom model discovery, validation, and chat transport |
| `cloud-ai-consent.ts` | Versioned recipient/origin-scoped approval before cloud AI or voice transport |
| `proxy-runtime.ts` | Narrow compatibility-service routing for non-AI operations such as operated NRAS/wearables/CAMS |
## Local AI adapter contract
`LOCAL_AI_PROVIDER_ADAPTERS` registers Ollama, LM Studio, Unsloth Studio, and generic compatible adapters with a stable `id`, user-facing `label`, normalized `capabilities`, and `discover()` method. Adapters can also implement `infer()`, `prepareNativeRequest()`, `loadWithContext()`, and `unload()`.
| Capability | Ollama | LM Studio | Unsloth Studio | Generic compatible endpoint |
| - | - | - | - | - |
| Native discovery | Yes | Yes | Compatible models + identified runtime | No |
| Loaded-model state | Yes | Yes | Yes when status is available | No |
| Context override | Yes | Yes | No | No |
| Native streaming | Yes | No | Compatible streaming | No native path |
| Structured output | Yes | No | Compatible request hint | Compatible request hint |
| Native performance stats | Yes | Yes | Endpoint-dependent | Endpoint-dependent |
| Model unload | Yes | Yes | No | No |
Code outside the adapters must branch on capabilities or optional methods, not hard-coded product names. Unknown providers fall back to the compatible adapter.
### Detection order
Discovery normalizes the base URL before caching or probing.
1. Probe LM Studio's native API and `/v1/models` in parallel.
2. If the native LM Studio API succeeds, merge its richer metadata with compatible model IDs and do not send Ollama-only probes.
3. If compatible models identify `owned_by: unsloth-studio`, query `/api/inference/status`, enrich loaded/context/vision state when available, and do not send Ollama-only probes.
4. Otherwise probe Ollama's `/api/tags` and `/api/ps` endpoints.
5. Prefer Ollama when it is available with models; otherwise use the identified or generic compatible result.
Successful discovery is cached for 30 seconds and failed discovery for 3 seconds. Explicit connection tests force refresh. Cache entries include the normalized URL and API key so results cannot cross credentials.
### Location and privacy classification
`getLocalAiExecutionLocation()` reports:
* `local` for `localhost`, `127.0.0.1`, and IPv6 loopback;
* `lan` for `.local`, private IPv4, link-local, and private/link-local IPv6 hosts;
* `remote` for other hosts; and
* `cloud` for cloud-tagged model names, regardless of the URL.
This is UI disclosure and diagnostic metadata, not endpoint attestation. A remote operator can misrepresent what runs behind an endpoint. Plain HTTP outside loopback must show an unencrypted-transport warning unless an underlying VPN or tunnel provides encryption.
Use the exact browser origin in CORS help. Do not recommend wildcard origins. Mixed-content preflight must continue to reject an HTTP LAN/remote endpoint from an HTTPS page while allowing the browser's loopback exception.
### Context planning and inference
`api-local.ts` estimates prompt tokens, adds a safety reserve, and clamps requested output to the loaded context. If the remaining context cannot satisfy the minimum output, it fails with the current and maximum context sizes instead of sending a predictably truncated request.
Imports and meal-photo analysis opt into discovery and native context planning because a full report or structured nutrition result can exceed a default loaded context. Dense lab tables use a conservative three-characters-per-token estimate; routine chat avoids an extra discovery probe before the first token.
`nutrition-ai-settings.ts` keeps an optional meal-specific model route separate from the main chat model while remaining on the active provider, and filters its catalog to image-capable models. Benchmarks can select models from multiple configured providers. Meal requests use recipient consent kind `meal-photo`, structured JSON output, no automatic retry, and their own abort signal. See [Meals and Nutrition internals](/developers/nutrition-internals).
Ollama can pass `num_ctx`. For a current LM Studio server, `prepareNativeRequest()` selects a target context and `loadWithContext()` unloads the previous instance, loads the model through `/api/v1/models/load`, forces discovery, and returns the context LM Studio actually fitted. `api-local.ts` replans output against that verified context, then generates through streaming `/v1/chat/completions`. A missing load route falls back to native non-streaming inference with a 15-minute ceiling. An unload failure may proceed only after forced discovery proves the old instance is gone; otherwise it fails before allocating a duplicate.
Local streaming gets a 15-minute allowance only for the first reader result because prompt prefill can be silent. Every later read keeps the shared 30-second stall guard. Provider finish reasons and native LM Studio token accounting normalize to `truncated`; PDF text and image imports reject that result before JSON review so a partial marker list cannot be confirmed.
All adapters return the common text/usage result plus optional diagnostics. Generic compatible endpoints and Custom API use direct browser requests to `/v1/chat/completions`.
### Runtime handoff and VRAM
`local-ai-lifecycle.ts` keeps only the last base URL, adapter ID, and model in module memory. Before using a different endpoint/model on the same machine, it discovers the previous runtime and unloads its loaded model when the adapter supports that operation.
Do not persist runtime-use state: it becomes stale after refresh and can expose misleading loaded-model claims. If a supported unload operation fails while changing servers in Settings, keep the new selection unsaved and return an actionable manual-unload error. An adapter without unload support can proceed without automatic release. Never attempt to unload cloud-tagged models or models on a different machine.
Model-test runtime capture is documented in [AI model testing internals](/developers/ai-model-testing-internals).
## Model storage rules
Regular and private/encrypted modes may need separate model keys. Do not let a model selected for a public provider path leak into a private-mode request if the private endpoint has a smaller model list.
When switching encryption/private modes:
* clear or reset incompatible sessions;
* restore the last valid model for that mode when possible;
* hide unavailable controls rather than sending unsupported requests;
* update pricing/usage hints after the final model is selected.
## Web search boundary
Web search can add external search results to the prompt. It must be disabled when the active transport promises private/encrypted prompt handling that would be broken by search, including PPQ Private TEE and Venice Encrypted TEE Mode.
User docs should say this plainly, and UI state should match it: no visible Web toggle when search is unavailable.
## Custom API direct-transport boundary
`api-custom.ts` calls `/models` and `/chat/completions` directly from the browser with `credentials: 'omit'`. `callOpenAICompatibleAPI()` receives `{ useProxy: false }`, and connection errors explicitly state that no server retry occurred.
Remote Custom origins require recipient/origin-scoped cloud-processing consent before chat data is sent. Loopback and private-network Custom origins are treated as local for that consent gate, though the configured machine can still read the request. The endpoint must allow the exact app origin with CORS and must satisfy mixed-content rules.
Do not reintroduce an implicit relay fallback. It would change which operator can see prompts and credentials and would require a new implementation, privacy disclosure, consent route, abuse controls, and security review.
## OpenRouter OAuth invariants
OpenRouter OAuth must bind state to the browser session and use PKCE/state validation. Do not accept callback tokens without matching the pending state. OAuth errors should leave existing manually-entered keys untouched unless the user explicitly clears them.
## Verification checklist
Before shipping provider changes:
* run targeted provider tests for the touched provider;
* run `tests/api-provider-contracts.test.ts` for adapter inference and redaction contracts;
* run `tests/test-provider-local-ai-runtime.ts` for discovery, context, location, and lifecycle behavior;
* run chat streaming tests if transport code changed;
* run Settings panel tests if dropdowns/toggles changed;
* verify Local AI connection copy for loopback, LAN, remote, cloud, CORS, and mixed-content cases;
* verify capability-optional fields render safely for a generic compatible endpoint;
* verify switching same-machine Ollama/LM Studio runtimes unloads only when supported;
* verify Local AI imports distinguish first-token prefill from mid-stream stalls and reject truncated marker lists;
* verify web search visibility for public vs private modes;
* verify wrong/missing API key errors are clear and do not erase saved keys;
* verify private/E2EE attestation status is visible and fail-closed;
* verify no raw keys, tokens, prompts, or setup blobs appear in logs or docs.
# AI surfaces map
Source: https://docs.getbased.health/developers/ai-surfaces-map
Where getbased uses AI versus deterministic code.
# AI in getbased — what's powered, what isn't
This page is the canonical map of where AI runs in the app. Three sections:
1. **[Light & Sun verdicts](#where-verdicts-appear)** — ten per-state and per-event surfaces inside the Light & Sun lens. Some run automatically; individual Sun and device-session interpretations are deliberately on-demand.
2. **[AI elsewhere in the app](#ai-elsewhere-in-the-app)** — the chat panel, Interpretive Lens, context-card dots, PDF + photo import, EMF interpretation, and others.
3. **[What is NOT AI](#what-is-not-ai-so-you-can-trust-the-numbers)** — the deterministic-math layer (channel doses, %MED, IU yields, trend alerts, PhenoAge, calculated markers). Big section because the math is auditable and reproducible — those numbers are NOT generated by an LLM.
Every live generative-health surface below requires a configured provider in **Settings → AI**. Demo profiles are the exception for context-card display: bundled summaries, dots, and tips can render without inference. The chat panel + Interpretive Lens read your enabled assembled lab context; per-row verdicts read just their own data slice. Agent Access uses a separate external-sharing permission and context path.
Voice transcription and speech synthesis are a separate subsystem configured in **Settings → Voice**. They can use downloaded browser models, a compatible server, a supported OpenRouter/PPQ/Venice AI connection, xAI, or ElevenLabs. Dictation inserts text into the composer; the normal chat provider and context path begin only when the user sends that text.
This page maps where AI runs. The [system prompt catalog](/developers/system-prompts) documents the exact or parameterized instruction contract, source, parser, and review concerns for each surface.
## Light & Sun verdict shape
Every Light & Sun verdict has the same shape:
* **A colored dot** — green / yellow / red / gray (more on these below)
* **A tip** — one sentence (≤14–18 words) summarizing the verdict
* **A detail** — 1–4 sentences of context citing your specific numbers and the biology behind them
* **A refresh button (↻)** — re-runs the analysis with a fresh API call
Dots:
| Dot | Meaning |
| - | - |
| 🟢 green | The feature-specific prompt found no supplied warning or clear concern; it does not certify safety, sufficiency, or protocol compliance |
| 🟡 yellow | A prompt-defined caution, uncertainty, or data-backed gap is present |
| 🔴 red | A strong entered pattern or supplied deterministic warning meets that prompt's red rule; camera proxies alone cannot create red where the prompt forbids it |
| ⚪ gray | Insufficient evidence, or an intentionally neutral pattern review such as the weekly channel synthesis |
## Where verdicts appear
Automatic surfaces use input fingerprints so unchanged data does not repeatedly spend tokens. Individual Sun-session and light-device-session interpretations are manual because the automatic Today and weekly summaries already cover those records.
### Automatic event and setup verdicts
* **Light Tool measurement** — fires for a saved Lux Meter, Flicker Detector, CCT Meter, Spectrum Classifier, Sleep Darkness, or Glass Transmission reading. Aggregate audit rows and brightness-proxy records are excluded. The verdict lives below the reading row in the room panel.
* **Audit verdict** — fires when you save a Light Audit (a frozen snapshot of your environment). Appears at the top of the audit detail. A small colored dot also appears in the audit card header for at-a-glance status across multiple audits.
* **Onboarding plan** — fires when you complete the Light & Sun setup card (skin type, eyewear, home lighting, Ott burden audit). Generates a personalized starting plan with three concrete first-week actions. Appears below the saved-setup chips.
### On-demand session verdicts
* **Sun session row** — runs only when the user requests an interpretation in the completed session detail. Today and weekly synthesis already cover the broader pattern.
* **Light-device-session row** — runs only when requested for a completed panel, SAD lamp, dawn simulator, UVB/UVA, or targeted PBM session.
### Per-state verdicts (fire on render when you have enough data)
These surfaces don't have a clean "I'm done" trigger — the user is editing chip-pickers, adding rooms, the daily rollup shifts as new sessions land. Pre-2026-05-08 they had a manual *Analyze* button to avoid burning API calls during edit sessions; the per-render auto-fire now uses a fingerprint of the underlying state plus a per-tab-session guard so it fires once per meaningful state shift, not on every keystroke.
* **Light Today daily hero (+ dashboard chip)** — fires the first time you visit the Light & Sun page each day, and re-fires when the cache is stale. Synthesizes your day's full picture (sun + devices + tools + environment + recent biomarker context) into a single verdict at the top of the page. The same verdict appears as a compact chip on the dashboard's Light Today strip.
* **Light Environment room** — auto-fires when a room has a primary source set OR at least one measurement. Empty rooms skip auto-fire so a freshly-added blank room doesn't burn an API call before you finish editing. Lives inside the room's expanded body.
* **Per-screen** — auto-fires when a device type is set (default 'phone'). Lives inside the expanded screen card.
* **Indoor-burden summary** — auto-fires when you have at least one room or screen mapped. Lives at the top of the Light Environment block (above the audit list).
* **Channel-mix synthesis** — auto-fires when at least one channel has non-zero exposure in the rolling 7 days. Brand-new users with no logs skip until they have data worth interpreting. Lives in the "Your light, by what it does" section.
## Caching and force-refresh
Every verdict is cached against a fingerprint of the underlying data. Ordinary render paths reuse an unchanged valid verdict without a fresh API call. The refresh button (↻) deliberately bypasses that cache and requests a new verdict even when the fingerprint is unchanged.
When the data has changed (you edited a room, logged a new session, completed a measurement), the fingerprint mismatch invalidates the cache. The next render shows a "refresh AI verdict — your setup changed" CTA.
## Cross-device sync
Verdicts live on the same row as the data they describe — sun-session verdicts on the session row, room verdicts on the room row, and so on. They sync to your other devices via the same per-row CRDT path the rest of your data uses. Latency is typically sub-10 seconds (the engine pushes immediately after writing, skipping the usual debounce).
## What if the verdict seems wrong?
The AI is reasoning over your inputs, including measurement context. A few common failure modes worth knowing about:
* **Camera placement and source**: camera-derived lux, CCT, RGB/spectrum, darkness, flicker, and glass-transmission results are screening proxies, not calibrated dose measurements. A one-point lux calibration stays approximate and source-specific. Follow each tool's aiming guide and use the appropriate dedicated meter before a strong conclusion.
* **Cached vs current**: if you edited a room recently and the verdict still references old numbers, the fingerprint should have invalidated and the CTA should say "your setup changed". Click ↻ to regenerate.
* **Brand-name endorsement**: the AI is instructed to never name specific brand products, only categories ("DC-dimmable LED", "incandescent or halogen"). If a verdict mentions a specific product brand, that's a regression — please open an issue.
## Hardware advice
Seven verdict prompts share a load-bearing hardware block. A camera-banding result cannot identify flicker frequency, modulation depth, or health risk. Advice starts with comparisons—full versus reduced brightness, bypassed versus engaged dimming, or a different known fixture—and recommends product specifications or a purpose-built flicker meter before a strong conclusion. The prompt does not assume that any LED, smart bulb, dimmer, incandescent, or halogen source is flicker-free, does not name brands, and explicitly forbids open-flame recommendations.
## Provider requirements
Verdicts require an AI provider configured in **Settings → AI**. Supported providers: OpenRouter, PPQ, Routstr, Venice, Local AI (Ollama / LM Studio / Jan / Unsloth Studio / compatible endpoints), and Custom. Provider pricing and actual token use vary by prompt, context, model, and retry behavior. A downloaded model running on the user's own hardware has no provider fee; remote or cloud-tagged Local AI models may charge.
## Disabling verdicts
If you want to keep your AI provider configured for the chat panel and Interpretive Lens but pause the per-row verdicts (e.g., during a budget-sensitive month):
```
window.DISABLE_AI_VERDICTS = true
```
Run that in DevTools Console. All ten surfaces will short-circuit until you remove the flag (or reload — the flag doesn't persist).
## AI elsewhere in the app
The ten Light & Sun verdicts above are one corner of the app's AI surface. Below are every place AI runs in the rest of the app. All require a configured provider in **Settings → AI**.
### Text-generation surfaces
| Surface | Where | What it does |
| - | - | - |
| **AI chat panel** | Slide-out at bottom-right, every page | Free-form streaming conversation grounded in enabled profile context, with per-thread drafts, search, bounded rendering, latest-turn edit/retry, linked forks, custom personas, and sequential multi-persona discussions. |
| **Interpretive Lens** | Dashboard top, "Lens" pill | Routes a focused query through your knowledge base (research papers, notes, books). Uses the on-device transformers.js RAG by default, or an external server you configure. |
| **Focus Card** | Dashboard, top of the page | A 3–5 sentence "what to focus on right now" rollup synthesized across your goals + recent biomarker drift + active light/wearable signals. Auto-fires when its fingerprint changes. |
| **Context-card AI assessment** | 9 health-context cards | A reported-facts summary, health dot, and short marker-linked tip per stale card. Demo profiles use bundled results; Local AI can refresh edited demos automatically, while cloud/paid demo refresh requires session-only consent. |
| **Biology Score interpretations** | Insight → Biology Scores | Automatic missing/stale assessments use bounded shared batches and saved input fingerprints. Independent short summaries and full explanations describe deterministic scores. Saved comparison variants survive reloads, backups and sync; a card refresh updates that card, while Coherence refresh updates the group. See [Biology Scores](/guides/biology-scores). |
| **Report AI overview** | PDF report builder | Initially selected when a compatible AI route is available. **Generate AI overview & preview** sends the selected report snapshot; uncheck AI to preview without a new request. The overview remains labeled as AI-generated after editing. See [Health reports](/guides/health-reports). |
| **Guided chat setup** | First-time visitors, before any data | A chat-shaped setup flow for profile basics, optional provider choice, extra context, context cards, and import routing. The deterministic setup UI does not itself spend tokens. |
| **Custom persona draft** | Chat personality picker → New Personality | Generates a 400–500 word editable persona draft from a person, archetype, or communication style. It receives no lab context. |
| **Custom-marker description** | Marker detail modal, when AI is configured | One-sentence (≤30 words) explanation of what the marker measures + why it matters clinically. Cached per-marker so it only fires the first time. |
| **EMF assessment interpretation** | EMF assessment editor → Interpret | Synthesizes a room-by-room read of your EMF measurements with severity, source attribution, and mitigation recommendations. Manual trigger. |
| **EMF comparison interpretation** | EMF editor → Compare two snapshots | Side-by-side delta read between two saved assessments — what got better, what got worse. Manual trigger. |
| **Supplement mitochondrial context** | Current supplements and medications | **ask AI for context** opens a constrained question based on matched claim-level primary studies and the user's mitochondrial/energy biomarkers. Manual trigger. |
| **Nutrition History Ask AI** | Body → Meals & Nutrition → History | Builds an editable 30-day, 3-month, 6-month, 1-year, or all-time aggregate from reviewed meals. The request receipt prevents the normal nutrition context block from being included twice. |
### Vision-AI surfaces (image-in)
| Surface | Where | What it does |
| - | - | - |
| **PDF lab import** | Drop a PDF on the dashboard or sidebar | Extracts text → obfuscates PII (regex + optional local-AI streaming sanitizer) → AI parses values + maps to schema markers. Specialty labs then pass through deterministic product-aware normalization before custom-marker persistence. |
| **Built-in model test** | Settings → Data → Test models on lab reports | Runs the production PDF parser against a synthetic 68-result reference report, preserving raw model output so deterministic scoring can compare it with the verified answer key. |
| **Photo lab import** | Same drop-zone, accepts JPG / PNG / HEIC | Vision-extract path — same downstream as PDF, but starts from a phone snap of a printed lab report. |
| **Custom-marker suggestion (from PDF)** | Inside the PDF import flow | When a parsed value can't be matched to a known marker, AI suggests a key + name + unit + reference range based on the surrounding PDF context. |
| **Supplement/medication product review** | **Supplements & Meds → Review photos** | Reviews up to four product, Facts, directions, or COA images. Separates active ingredients, excipients/capsule materials, laboratory results, and warnings into a draft that the user must review and then save. |
| **Meal photo analysis** | Body → Meals & Nutrition → Log meal | Sends up to four selected meal photos through the optional dedicated nutrition model route and returns an editable nutrient/component estimate. Running analysis may continue in the background or be canceled. |
| **Nutrition-label analysis** | Body → Meals & Nutrition → Log meal | Extracts serving and nutrient facts from selected label images into the same review-first meal draft. |
| **Meal benchmark** | Body → Meals & Nutrition → Meal benchmarks | Runs selected configured models independently against benchmark photos. Each model run can be canceled, retried, or replaced without interrupting the others; deterministic code scores reviewed estimates against known values. |
| **Spectrum Classifier** | Light Tools → "What is this light?" | Camera-based light-source classifier (LED cool / LED warm / fluorescent / incandescent / daylight). Uses RGB ratios + flicker variance — the deterministic part is the classifier itself; the AI only kicks in for the verdict on the saved measurement (covered in Light & Sun above). |
## What is NOT AI (so you can trust the numbers)
A lot of the app is deterministic math, not AI inference. If you see a number on a chart or a tier on a pill, it came from one of these:
| Surface | Source |
| - | - |
| **Channel pills** (Vitamin D / Body clock / Cell energy & repair / Blood vessels / Skin & mood / Outdoor eye light) | Browser-side spectrum reconstruction → action-spectrum convolution → logged source/rhythm signal. Deterministic model, not a measured body response or target-progress score. |
| **% MED burn estimate** | Modeled erythemal action spectrum × Fitzpatrick reference threshold. It is a caution estimate, not a personal safe-time guarantee. |
| **Vitamin-D-effective estimate** | Modeled UVB reaching logged uncovered skin. It is not measured synthesis, serum status, or an oral-dose equivalent. |
| **Reference ranges** (`refMin` / `refMax`) on lab charts | Whatever the lab printed on the PDF, OR your own override. Never AI-generated. |
| **Trend alerts** ("dropped 25%" / "trending up") | Linear regression slope + R² thresholds in `data.ts`. Algorithmic, not LLM. |
| **PhenoAge biological age** | Levine 2018 formula over 9 biomarkers + age. Closed-form math. |
| **Calculated markers** (Free Water Deficit, BUN/Creatinine ratio, HOMA-IR) | Published formulas with conversion math for unit systems. |
| **Channel doses on dashboard / per-session** | Same Bird-Riordan reconstruction; no AI. |
| **Trend sparklines on context cards** | Direct plot of stored values. |
| **Light-environment screening tiers** | Deterministic heuristic from disclosed room/screen patterns and source-aware sensor, meter, or camera observations. It is not measured dose or diagnosis. The following narrative is AI-generated. |
| **Custom marker creation, sidebar edits, chip-picker changes, manual-value entry** | Local data writes. No AI call fires; the AI sees them on the next chat turn. |
| **Supplement/medication lifecycle, daily quantity math, contaminant grouping, and mitochondrial evidence matching** | Period/status rules, compatible-unit arithmetic, and claim-catalog lookup are deterministic. AI is used only for reviewed product extraction, requested context, or impact summaries. |
| **Genome import, association/evidence/relevance labels, APOE, and mtDNA resolution** | Raw/report/manual parsing and catalog lookup are deterministic and local. **Ask AI** on a finding is an explicit editable prompt that supplies the scoped catalog baseline; it is not part of import or classification. |
| **Nutrition totals, targets, timing, fasting windows, Fuel Mix, and response trends** | Computed from reviewed meal records in browser code. Target progress colors and meal-history charts do not come from a model. |
| **Meal component arithmetic and benchmark scoring** | Component totals, known-value comparison, error percentages, and winner ranking are deterministic after the model estimates have been normalized. |
## What runs on-device vs cloud
CLI-backed features use `ai-feature-routing.ts` and the capability-aware execution route instead of bypassing the selected assistant through a direct-provider call. Local CLI agents receive the same enabled context plus scoped getbased tools; personal gateways receive context and may advertise text-only feature support, but do not receive the local tool bridge. Image support does not imply audio support, and automatic voice remains on-device for CLI chat. See [CLI agent providers](/developers/cli-agent-providers) for the dispatch and approval contract.
| Path | Where it runs |
| - | - |
| **Knowledge Base (Interpretive Lens default)** | On-device transformers.js + OPFS — your knowledge documents never leave the browser. |
| **Local AI provider** (Ollama / LM Studio / Jan / Unsloth Studio / compatible endpoints, configured in Settings → AI → Local AI) | Direct from the browser to the configured endpoint. Loopback runs stay on the device; LAN, remote, and cloud-tagged setups leave it. |
| **PII obfuscation (compatible streaming sanitizer)** | Optional. It stays on-device only with a loopback endpoint and downloaded model, then passes the prepared content to the active provider. |
| **Voice — On this device** | Downloaded Whisper and Kokoro models run in browser workers. Raw recordings are transient and model files are rebuildable caches. |
| **Voice — compatible server / OpenRouter / PPQ / Venice / xAI / ElevenLabs** | The selected voice service receives only the recording being transcribed or assistant reply being synthesized. All are called directly from the browser; remote recipients require cloud-processing consent where applicable. **Same as chat** resolves supported AI accounts or falls back on-device. Private chat transports do not automatically cover ordinary voice endpoints. |
| **Cloud providers** (PPQ / Routstr / OpenRouter / Venice / Custom) | The browser calls the selected endpoint directly after recipient-scoped consent. Venice Encrypted TEE Mode encrypts message content with ECDH + AES-GCM and requires Intel TDX DCAP plus signed NVIDIA NRAS checks, but Venice still sees request metadata. The checks do not prove TDX/GPU co-location, approved code measurements, or the source of every response. |
| **Camera frames and selected images** (light tools, lab import, meal photo, nutrition label) | Prepared in the browser, then either kept on-device for light-tool metric extraction or sent to the configured AI provider for the requested vision call. Meals accept up to four images. Full-size meal images are request-only and are not saved; small reviewed thumbnails may be stored with the meal. |
## Caching + privacy notes
* Every per-row verdict caches against a fingerprint of the underlying data. If nothing's changed, the cached verdict is returned without a fresh API call.
* Import model-test records stay device-local and are stripped from sync payloads because timing, loaded state, and hardware metadata do not transfer meaningfully. See [AI model testing internals](/developers/ai-model-testing-internals).
* The chat panel pulls from `buildLabContext()` — the assembled section block filtered by **Manage → Context → Data sources** (`contextSourceSettings`). Those switches gate Insight Context Cards, Supplements & Medications, Blood marker results, per-specialty lab groups, Genome tiers, Light & Sun, Meals & Nutrition, and Wearable recovery context for in-app AI, score context, and source-driven warnings.
* Interpretive Lens and Knowledge Base live in the Answer grounding panel, separate from data-source gates. Agent Access is separate again: the browser pushes an encrypted gateway summary only after **Settings → Agent Access** is enabled, and that push calls `buildLabContext({ skipGroupFilter: true, ignoreContextToggles: true })` so external agents do not inherit the in-app Data sources switches.
* Custom-marker creation, manual-value entry, sidebar edits, and chip-picker changes do NOT trigger AI calls — they update local data, AI sees them on the next chat turn or the next per-state verdict render.
## Related guides
* [Sun Sessions](https://docs.getbased.health/guides/sun-sessions) — how outdoor light sessions work
* [Light Environment](https://docs.getbased.health/guides/light-environment) — rooms + screens + audits
* [Light Tools](https://docs.getbased.health/guides/light-tools) — the measurement tool ecosystem
* [AI Providers](https://docs.getbased.health/ai-providers) — how to configure a provider
* [AI Chat](https://docs.getbased.health/guides/ai-chat) — the chat panel that complements the per-row verdicts
* [Meals & Nutrition](/guides/meals-nutrition) — meal analysis, reviewed storage, trends, benchmarks, and AI context boundaries
* [Supplements and medications](/guides/supplements-medications) — therapy lifecycle, reviewed product import, quality evidence, and AI boundaries
# Architecture
Source: https://docs.getbased.health/developers/architecture
How the native-module getbased app is structured, started, tested, and layered.
# Architecture
## TypeScript authoring and JavaScript runtime
Edit canonical `.ts` and `.mts` sources. TypeScript 7 emits ignored `.js` and `.mjs` siblings at the existing runtime paths. Local development loads these emitted native ES modules; production uses Rolldown to bundle the startup graph and retain lazy feature chunks. The browser does not load TypeScript directly.
Use Node.js 24. `npm ci` installs dependencies and compiles through `prepare`; `npm run dev-server`, `npm test`, and the production build/check commands also compile first. With a server already running, compile after source edits before reloading. Direct Node and Playwright commands need the same compilation step.
```bash theme={null}
npm ci
npm run dev-server -- 8000
# After editing sources while the server is running:
npm run typescript:build
npm run typecheck:migration
npm run typecheck:migration-tests
npm run architecture:check
npm run quality
npm test -- tests/.test.ts
npx playwright test tests/playwright/.spec.ts
```
Keep `.js` and `.mjs` specifiers in source imports, dynamic imports, worker URLs, HTML scripts, and runtime commands. For example, `import './data.js'` in `profile.ts` resolves to the authored TypeScript for checking and to emitted JavaScript at runtime. Do not edit or commit emitted siblings. Third-party vendor bundles retain their own source formats.
The production and test compiler projects are separate and strict. The app, server, worker, and editor projects also retain their environment-specific safety checks; migration completion does not permit weakened flags or suppression comments. See the app's [source and compiler contract](https://github.com/elkimek/get-based/blob/main/ARCHITECTURE.md#typescript-migration-source-contract).
Source modules remain the ownership boundary. Regenerate [`MODULE_MAP.md`](https://github.com/elkimek/get-based/blob/main/MODULE_MAP.md) when modules or imports change. GitHub Actions owns the exhaustive browser and combined-coverage matrix.
## File layout
```
index.html — HTML structure only; script/CSS includes; SEO meta tags
styles.css — Core CSS: tokens, base resets, shared components, responsive/touch rules
css/app-shell.css — App chrome: header, sidebar navigation, footer, mobile shell, profile and sync controls
css/*.css — Split shared/feature CSS for import/upload flow, EMF assessment, dashboard shell/widgets, category views, modal shell/utilities, context/profile cards, and high-churn surfaces such as settings, mobile dashboard, menstrual cycle, Meals & Nutrition, marker detail modal, client list, chat panel, wearables, Light/Sun, and redesign catch-up
api/share.ts — bounded legacy Blob → standalone share-service transition adapter
api/proxy.ts — same-origin compatibility endpoint for self-hosts and legacy clients
api/commit.ts — current deployed commit/version helper
server/compat-proxy-server.ts — dedicated operated compatibility service entry point
server/profile-share-server.ts — dedicated opaque-envelope SQLite service entry point
manifest.json — PWA manifest (installable as a native app)
service-worker.ts — PWA cache strategies, API bypass rules
data/
demo-female.json — Female demo profile (Sarah)
demo-male.json — Male demo profile (Alex)
tests/
*.test.ts / test-*.ts — Native unit tests and compiled legacy fixtures
playwright/ — Playwright browser specs and browser-script runner
verify-modules.ts — Module integrity assertions
js/
main.ts — Thin module entry point
app-feature-modules.ts — Compatibility composition for foundation, cold loaders, and UI shell
app-foundation-modules.ts — Startup-loaded foundation/privacy side-effect imports
health-data-loader.ts — Route- and data-aware lazy health feature loading
app-health-data-modules.ts — Explicit full health feature composition
light-sun-loader.ts — Lazy Light & Sun behavior, UI, and stylesheet loading
app-light-sun-modules.ts — Full Light & Sun feature composition
import-loader.ts / export-loader.ts — Lazy import and export entry points
chat-loader.ts / app-ai-interaction-modules.ts — First-use Chat loading and composition
app-ui-shell-modules.ts — Startup-loaded UI shell feature side-effect imports
app-event-listeners.ts — App-wide modal, keyboard, shortcut, and refresh wiring
startup-orchestrator.ts — Startup global wiring and phase ordering
startup-foundation.ts — Encryption, meteo cache, broadcast, and folder backup bootstrap
startup-profile.ts — Profile migration, active-profile load, and display-state bootstrap
startup-oauth-callbacks.ts — Wearable/OpenRouter callback routing during startup
startup-maintenance.ts — Wearable startup services and post-profile maintenance jobs
startup-ui.ts — First-render UI bootstrap after profile/OAuth startup
emf-runtime.ts — Configured lazy EMF module and stylesheet entry points
marker-schema/ — Authored core category definitions, stable order, and immutable built-in marker identities
marker-schema.ts — Generated TypeScript marker catalog; never edit directly
schema.ts — Compatibility facade for the marker catalog, ranges, conversions, identities, and thresholds
custom-marker-identity.ts — Stable custom:* identity creation and legacy migration
marker-placement.ts — Storage-independent visual category placement engine
marker-terminology/ / marker-terminology.ts — Optional terminology sources and generated registry
constants.ts — Option arrays, CHAT_PERSONALITIES, fake data, COUNTRY_LATITUDES
chat-system-prompt.ts — Lazy Chat-only base analyst instruction
state.ts — Single shared mutable state object
utils.ts — escapeHTML, hashString, getStatus, formatValue, showNotification, linearRegression
theme.ts — Theme get/set/toggle, getChartColors, time format helpers
api.ts — AI provider routing, OpenRouter, PPQ, Routstr, Venice, Local AI, Custom API, private modes, model management
api-transport.ts — shared AI API fetch retry/timeouts and streaming read stall guard
api-local.ts — Local AI prompt/context planning and adapter orchestration
local-ai-provider-registry.ts — normalized Ollama, LM Studio, Unsloth Studio, and generic compatible-endpoint adapter registry
local-ai-discovery.ts / local-ai-lifecycle.ts — cached capability discovery and same-machine model handoff
profile.ts — Profile CRUD, sex/DOB/location, migrateProfileData, profile dropdown
data.ts — Cached getActiveData() projection, unit conversion, date range, and persistence
data-calculated-markers.ts — Direct-result-first ratios, indices, fluid balance, and biological-age calculations
marker-analysis.ts / marker-context-ranges.ts — Effective dated ranges, statuses, trends, and context resolvers
cycle-draw-phases.ts — Recorded per-draw cycle phase normalization
pii.ts — PII obfuscation: local AI (OpenAI-compatible) + regex fallback, diff viewer
charts.ts — Chart.js plugins (5), createLineChart, destroyAllCharts
notes.ts — Note editor: open/save/delete
supplements.ts — Supplement/medication editor orchestration, persistence, and lifecycle actions
supplement-medication-domain.ts — Stable identity, period/status rules, schedule exposure, units, compatibility migration
supplement-form-ui.ts / supplement-import-controller.ts — Structured editor and reviewed link/photo import workflow
supplement-import-draft.ts / supplement-quality.ts — Strict source normalization, evidence merging, and quality-result semantics
supplement-context.ts / supplement-warnings.ts — Bounded AI projections and claim-level mitochondrial evidence
supplement-dashboard.ts / supplement-impact.ts — Current dashboard, historical overlays, and lab-impact analysis
nutrition-context.ts / nutrition.ts — Lazy Meals & Nutrition facade, feature composition, dashboard/Body entry points, and context integration
nutrition-analysis.ts / nutrition-ai-settings.ts — Multimodal meal/label extraction, schemas, dedicated vision route, and provider/model selection
nutrition-store.ts / nutrition-sync-sanitize.ts — Device-encrypted meal cache, thumbnail-only profile mirror, reconciliation, archives, and tombstones
nutrition-summary.ts / nutrition-summary-context.ts — Coverage-aware windows, history, timing/fasting, targets inputs, and bounded aggregate AI context
nutrition-benchmark-workspace.ts / nutrition-comparison*.ts — Independent benchmark source, per-model request lifecycle, deterministic comparison, and result handoff
wearables.ts — Wearable dashboard strip, source picker, inline manual log forms, window facade
wearables-detail-modal.ts — Wearable metric detail modal, chart, manual entries, focus trap
wearables-formatters.ts / wearables-manual-form-ui.ts — Shared wearable UI helpers
cycle.ts — Menstrual cycle helpers, editor, dashboard rendering
context-cards.ts — context card dashboard facade, save/refresh lifecycle, demo AI controls
context-card-health-dots.ts — summaries, health-dot AI, cache fingerprints, demo-provider safeguards
context-card-summaries.ts — card definitions, filled-state helpers, summaries
context-card-editor-ui.ts — shared context editor modal and field controls
context-cards-runtime.ts — storage-safe history recording and navigation callback bridge
context-card-medical-history-editor.ts — Medical History editor, conditions, and family history
context-card-lifestyle-editors.ts — cold-safe lifestyle editor facade
context-card-lifestyle-editors-impl.ts — main diet, sleep, exercise, stress, relationship, and environment editors
context-card-lifestyle-special-editors.ts — light, goals, interpretive lens, and diet contaminant modal
pdf-import.ts — PDF/image/text import pipeline, AI analysis, confirm/save merge
pdf-import-preflight.ts / pdf-import-progress.ts — import preflight prompts and progress/status UI
pdf-import-ai-utils.ts — import stream fallback, reading/writing progress estimates, performance persistence, and truncation-safe parsing helpers
pdf-import-marker-normalization.ts — shared AI marker sanitization, adapter normalization, specialty guards
pdf-import-organic-acid-normalization.ts / mosaic-oat-catalog.ts — lazy product-scoped OAT normalization and Mosaic OAT/MOAT catalogs
pdf-import-marker-mapping.ts / pdf-import-ratio-units.ts — canonical alias matching, importable calculations, and ratio convention metadata
pdf-import-unit-conversions.ts / pdf-import-review-formatting.ts — review-safe unit conversion and responsive unit/range controls
pdf-import-commit.ts — Snapshot-aware live values, direct calculations, adopted ranges, and collection-context ownership
pdf-import-persistence.ts — imported-data snapshot, rollback, refresh, remove, and rename save flows
pdf-import-review.ts — import review modal rendering, row filtering/mapping UI state
import-benchmarks.ts / import-reference-benchmark.ts — device-local import diagnostics and verified sample scoring
import-file-input.ts — lazy file-picker import binding and import routing
import-drop-zone.ts — lazy import drop-zone binding shared by page shells
export.ts — JSON export/import (single, per-client, database bundle), PDF report, clearAllData
profile-share.ts — Password-protected single-profile share links, official VPS routing, active link management, deep-link import
proxy-runtime.ts — Official dedicated-origin vs independent same-origin compatibility routing
app-extension-runtime.ts — Single trusted build-time edition extension contract; core defaults to no-op
app-extension-bootstrap.ts — Public/self-host build bootstrap; edition builds may replace it at composition time
unique-id.ts — Web-Crypto-only collision-resistant identifiers; fails closed when unavailable
chat.ts — Chat public barrel and entry point
chat-window-bindings.ts — Chat callback wiring and legacy window exports
chat-marker-prompts.ts — Per-marker and selected-correlation AI prompt builders
chat-continuation.ts — Response limit detection and automatic continuation
chat-prompt-context.ts — shared persona, system-prompt, history-tagging, and source helpers
chat-panel.ts — panel lifecycle, layout, focus, and active-thread restoration
chat-composer.ts / chat-draft-storage.ts — growing composer and per-thread device-local drafts
chat-send.ts / chat-stream-status.ts — thread-bound streaming sends and concise live status
chat-render.ts / chat-render-range.ts / chat-scroll.ts — bounded transcript rendering and user-owned scroll state
chat-actions.ts / chat-message-edit.ts — message actions, latest-turn edit-and-retry, and linked forks
chat-personalities.ts — personality selection, header state, and persona workflow facade
chat-personality-editor.ts — custom persona dialog and official-host agreement policy
chat-personality-storage.ts / chat-personality-merge.ts — encrypted persona persistence, tombstones, and per-item Sync merge
chat-storage-safety.ts — normalized bounds for imported, restored, and synced chat records
chat-recommendation-disclosure.ts — collapsed recommendation discovery and finite new-content cue
chat-discussion.ts — Multi-persona discussion public barrel
chat-discussion-flow.ts — Multi-persona discussion public user-action handlers
chat-discussion-callbacks.ts — Multi-persona discussion callback bridge
chat-discussion-lifecycle.ts — Multi-persona discussion cleanup and completion helpers
chat-discussion-turns.ts — Multi-persona discussion turn execution helpers
chat-discussion-round-runner.ts — Multi-persona discussion round execution loop
chat-discussion-round-prompts.ts — Multi-persona discussion prompt helpers
chat-discussion-round-request.ts — Multi-persona discussion API request helpers
chat-discussion-round-state.ts — Multi-persona discussion round persistence helpers
chat-discussion-round-view.ts — Multi-persona discussion live message DOM helpers
chat-discussion-state.ts — Multi-persona discussion persona/thread state helpers
chat-discussion-picker.ts — Multi-persona discussion persona picker controls
chat-discussion-ui.ts — Multi-persona discussion button and continuation controls
chat-empty-state.ts — Empty chat and onboarding message renderer
chat-nudge.ts — Chat FAB nudge badge and dismissal state
chat-threads.ts / chat-history.ts — thread index CRUD and per-thread message persistence
chat-thread-search.ts — rail name/message search, bounded-range reveal, and match highlighting
client-list.ts — Client List modal: search/sort/filter profiles, inline CRUD, archive/flag/pin
dna.ts / dna-runtime.ts — lazy Genome orchestration, persistence, AI context, and shell dependencies
dna-parser.ts / dna-genotype.ts / dna-file-detection.ts — raw/report parsing and strand-aware catalog matching
dna-evidence.ts / dna-mtdna.ts / dna-ui.ts — evidence contract, maternal-line model, and Genome presentation
recommendations.ts — Supplement & lifestyle recommendations, lazy catalog, 3 touchpoints
cashu-wallet.ts — In-app Cashu eCash wallet: BIP-39 seed, IndexedDB proofs, Lightning fund/withdraw
nostr-discovery.ts — Nostr relay queries for Routstr nodes (Kind 38421), health checks, caching
settings.ts — Settings modal shell: profile, display, AI providers, privacy, security
settings-import-benchmark-controller.ts — model-test modal lifecycle, selection, comparison, and deletion
settings-sync-panel.ts — cold Settings Sync/Agent facade and in-place placeholder hydration
settings-sync-panel-impl.ts — Cross-device Sync setup, mnemonic, relay status, and recovery UI
sync-settings-state.ts / sync-lifecycle.ts — Configured/active/paused state and identity-preserving pause/resume orchestration
sync-backup-restore-state.ts — Restore-safe dirty/snapshot reset and publish-before-pull handoff
settings-agent-access-panel.ts — Agent Access state, setup payload, token/key, and wearable-series controls
feedback.ts — Feedback modal (bug reports, feature requests)
tour.ts — Guided tour spotlight engine (app tour + cycle tour)
nav.ts — Sidebar, date range filter, chart layers dropdown
views-router.ts — route validation, last-view persistence, scroll-anchor navigation
dashboard-view-composition.ts — dashboard route/widget/control wiring
dashboard-page-view.ts — dashboard route shell, empty-state onboarding, widget page composition
lens-pages.ts — Labs, Genome, Body, Insight, and Recommendations page renderers
lens-page-shell.ts — shared lens header, widget chrome, ordering, dashboard toggles
dashboard-widgets.ts — dashboard widget registry, defaults, widget prefs
dashboard-widget-controls.ts — widget picker, layout actions, drag/reorder controls
dashboard-widget-renderers.ts — dashboard widget body renderers and recommendation helpers
recommendation-actions.ts — recommendation modal/chat/save/dismiss handlers
category-page-view.ts — category route shell, view switching, card-order preservation
category-view-renderers.ts — category chart cards, table/heatmap shells, fatty-acid renderers
category-customization.ts — category/marker rename helpers and category icon picker
light-page-view.ts — Light & Sun page shell, Light Today strip, dashboard Light pills
light-channel-view.ts — Light channel pill rows, detail panels, citations, suggestions
sun-body-silhouette.ts — anatomical sun-session body picker, region-map hit testing, selection overlay
light-device-session-modal.ts — log/start light therapy device session modal
light-tool-camera.ts — shared camera lock, row-banding, aiming guide, and lux calibration helpers
light-tool-camera-modals.ts — camera-backed Light tool modal flows injected from light-tools.ts
views.ts — route wiring and compatibility exports
crypto.ts — passphrase and device-key AES-GCM storage; BroadcastChannel data-change notifications only
backup.ts — IndexedDB auto-backup, folder backup (File System Access API), backup restore
backup-chat-storage.ts — raw thread index/message collection for complete locked backups
lab-context.ts — buildLabContext() central AI context serializer
lab-context-collection.ts — explicit per-draw time, fasting, cycle-day, and recorded-phase section
lab-context-change-timeline.ts — profile-context history serializer
lab-context-wearables.ts — wearable summaries and sleep-context mismatch flags
markdown.ts — renderMarkdown() block-aware parser (extracted from chat.ts)
provider-panels.ts — AI provider settings behavior: selection, validation, model dropdown refresh, balances
provider-panel-renderers.ts — Settings → AI provider panel markup for OpenRouter, Routstr, Venice, PPQ, Custom, and Local AI
```
## Entry points
```text theme={null}
index.html
└──