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

# Deployment

> Vercel, service worker, CSP, PWA, and release checks.

# Deployment

getbased is deployed on Vercel. The browser app is shipped as static files, and Vercel also runs small same-origin API routes from `api/` for features that need hosted infrastructure. Vercel serves the app files directly, executes those API routes, and injects security headers.

## Vercel configuration

`vercel.json` uses a deploy-time catalog fetch plus the legacy `routes` array (not `rewrites` or `headers`):

```json theme={null}
{
  "buildCommand": "node scripts/fetch-catalog.mjs",
  "outputDirectory": ".",
  "routes": [
    {
      "src": "/(.*)",
      "headers": { "...CSP and security headers..." },
      "continue": true
    },
    { "src": "^/guide/(.*)", "status": 301, "headers": { "Location": "/docs/guide/$1" } },
    { "src": "^/docs/guide/getting-started(?:\\.html)?/?$", "status": 301, "headers": { "Location": "https://docs.getbased.health/quickstart" } },
    { "src": "^/docs/guide/charts(?:\\.html)?/?$", "status": 301, "headers": { "Location": "https://docs.getbased.health/guides/biomarker-charts" } },
    { "src": "^/docs/guide/folder-backup(?:\\.html)?/?$", "status": 301, "headers": { "Location": "https://docs.getbased.health/guides/backup" } },
    { "src": "^/docs/guide/json-export-import(?:\\.html)?/?$", "status": 301, "headers": { "Location": "https://docs.getbased.health/guides/export-import" } },
    { "src": "^/docs/guide/ai-providers(?:\\.html)?/?$", "status": 301, "headers": { "Location": "https://docs.getbased.health/ai-providers" } },
    { "src": "^/docs/guide/([^.]+?)(?:\\.html)?/?$", "status": 301, "headers": { "Location": "https://docs.getbased.health/guides/$1" } },
    { "src": "^/docs(?:/.*)?$", "status": 301, "headers": { "Location": "https://docs.getbased.health/" } },
    { "src": "^/app/?$", "dest": "/index.html" }
  ]
}
```

| Route                            | Destination                                                                                                          |
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `/`                              | `index.html` — the application (served by Vercel filesystem default)                                                 |
| `/api/share`                     | `api/share.js` — encrypted profile share envelope storage                                                            |
| `/api/proxy`                     | `api/proxy.js` — same-origin helper for wearable runtime config, CAMS/light data, and allowlisted external API calls |
| `/api/commit`                    | `api/commit.js` — deployed commit/version helper used by diagnostics and smoke checks                                |
| `/guide/*`                       | 301 redirect to the equivalent old `/docs/guide/*` compatibility path                                                |
| `/docs`                          | 301 redirect to `https://docs.getbased.health/`                                                                      |
| `/docs/guide/getting-started`    | 301 redirect to `https://docs.getbased.health/quickstart`                                                            |
| `/docs/guide/charts`             | 301 redirect to `https://docs.getbased.health/guides/biomarker-charts`                                               |
| `/docs/guide/folder-backup`      | 301 redirect to `https://docs.getbased.health/guides/backup`                                                         |
| `/docs/guide/json-export-import` | 301 redirect to `https://docs.getbased.health/guides/export-import`                                                  |
| `/docs/guide/ai-providers`       | 301 redirect to `https://docs.getbased.health/ai-providers`                                                          |
| `/docs/guide/*`                  | 301 redirect to the equivalent Mintlify guide path                                                                   |
| `/app`                           | `index.html` — app route used when the landing-page project owns `/`                                                 |
| Everything else                  | Served as-is from the filesystem (JS, CSS, images, manifest)                                                         |

User documentation is not built from this repository anymore. It lives in the separate Mintlify docs project at `docs.getbased.health`; this app keeps only compatibility redirects for old `app.getbased.health/docs/*` links.

## API routes

Vercel functions live in `api/`. They must stay same-origin, minimal, and avoid handling plaintext health data unless a feature explicitly requires it.

### `/api/share`

`api/share.js` stores password-protected profile share links. The browser builds a single-profile export, encrypts it locally with AES-GCM using a key derived from the user-provided password, and uploads only the encrypted envelope. The password is never sent to the route and is not embedded in the link.

| Method                     | Purpose                                                                            |
| -------------------------- | ---------------------------------------------------------------------------------- |
| `POST /api/share`          | Store a new encrypted envelope and management-token hash                           |
| `GET /api/share?id=...`    | Return the encrypted envelope for a recipient to decrypt locally                   |
| `DELETE /api/share?id=...` | Delete the hosted envelope when the creating browser supplies the management token |
| `OPTIONS /api/share`       | CORS preflight for allowed same-origin/app origins                                 |

Production requires a Vercel Blob write token configured as a sensitive environment variable. The route uses a private Blob store namespace, caps payload size, rejects shares longer than 30 days, rejects weak PBKDF2 iteration counts, rate-limits anonymous share creation, and returns `no-store` JSON responses. The route never has the password or plaintext profile JSON.

Local development mirrors the endpoint in `dev-server.js` with an in-memory store so the modal and deep-link flow can be smoke-tested without Blob credentials. That local store is process-local and disappears when the dev server restarts.

### `/api/proxy`

`api/proxy.js` is the controlled same-origin proxy boundary. It exists for browser flows that cannot safely call a third-party endpoint directly from `app.getbased.health`.

Current responsibilities:

* wearable runtime configuration and OAuth helper calls where client IDs/secrets must not be hardcoded into docs;
* CAMS/light-data relay paths used by Sun & Light calculations;
* Custom API proxying with public-host and allowlist checks so it cannot become a general SSRF tunnel;
* CORS and method handling for the app's own allowed origins.

Do not add a new proxy mode without documenting the target host, the caller allowlist, what secrets it can see, and which tests cover the branch.

### `/api/commit`

`api/commit.js` returns the deployed app identity used by smoke checks, diagnostics, and “which build am I looking at?” debugging. Keep it cheap, no-store, and free of private deployment-provider details.

## Domain layout

The app and landing page are deployed as two separate Vercel projects on the same domain:

| Subdomain             | Vercel project   | Repo                                                                |
| --------------------- | ---------------- | ------------------------------------------------------------------- |
| `getbased.health`     | `get-based-site` | [elkimek/get-based-site](https://github.com/elkimek/get-based-site) |
| `app.getbased.health` | `get-based`      | [elkimek/get-based](https://github.com/elkimek/get-based)           |

DNS points the root, app, and www hostnames at the Vercel projects. Keep provider-specific DNS account details out of public docs.

The landing page is self-contained (all CSS/JS inline) and depends only on three icon files. CTA links point to `https://app.getbased.health`. A small inline script rewrites these to `/app` on `localhost` for local development.

### Local dev server

`node dev-server.js` mirrors the production layout. If the site repo is cloned as a sibling (`../get-based-site`), the server routes:

| Path                  | Destination                                                         |
| --------------------- | ------------------------------------------------------------------- |
| `/`                   | Landing page from `../get-based-site/index.html`                    |
| `/app`                | App from `index.html`                                               |
| `/api/share`          | In-memory encrypted profile share endpoint                          |
| `/api/proxy`          | local proxy/runtime-config mirror with LAN/same-origin safety gates |
| `/api/commit`         | local commit/version helper                                         |
| `/api/deploy-catalog` | local git/Vercel deploy-catalog helper for operator workflows       |
| `/docs/*`             | 301 redirect to `docs.getbased.health`                              |

Without the sibling repo, `/` serves the app directly. Override the site path with `SITE_DIR=/path/to/site node dev-server.js`.

## CSP headers

The Content-Security-Policy allows what the current app needs:

```
default-src 'self'
script-src  'self' 'unsafe-inline' 'wasm-unsafe-eval' blob:
            https://umami-iota-olive.vercel.app
            https://cdn.jsdelivr.net
style-src   'self' 'unsafe-inline'
font-src    'self'
connect-src 'self' https: wss:
            http://localhost:*
            http://127.0.0.1:*
            ws://localhost:*
            ws://*.onion
            http://*.onion
img-src     'self' data: blob:
worker-src  'self' blob:
manifest-src 'self'
frame-src   'none'
object-src  'none'
base-uri    'self'
```

`'unsafe-inline'` is required for scripts because `index.html` has inline bootstrapping and legacy window-exported action hooks. `'wasm-unsafe-eval'` and `blob:` are needed for in-browser model/vector/worker paths such as local Knowledge Base and private transports.

Most JS libraries are bundled locally in `vendor/`, but the CSP currently permits `https://cdn.jsdelivr.net` for explicitly allowed browser-runtime dependencies. Run `./update-vendor.sh` when bumping vendored assets, and update `vercel.json` whenever a provider or runtime dependency needs a new host.

`localhost:*` and `127.0.0.1:*` in `connect-src` allow local AI servers (Ollama, LM Studio, Jan, etc.) running on the same machine. LAN IPs (e.g. `192.168.x.x`) are not supported from the hosted HTTPS app due to browser mixed-content blocking — this is a browser security fundamental, not a CSP limitation.

If a new AI provider, wearable provider, private transport, worker runtime, or relay host is added, review `connect-src`, `script-src`, service-worker bypass rules, and `api/proxy.js` together.

`vercel.json` also sets `Onion-Location` for Tor discovery and `Link` service descriptors for `/.well-known/mcp.json` and `/.well-known/agent-skills/index.json`.

## Service worker

The service worker (`service-worker.js`) manages PWA caching. The cache name includes a version number:

```js theme={null}
const CACHE_NAME = 'labcharts-v55';
```

**When to bump the version:** Any time you change an app file — JS, CSS, HTML, manifest, images. Incrementing the version busts the cache for existing users, who will download fresh files on next visit.

The service worker uses three caching strategies:

| Resource type                                             | Strategy                                                                                                                           |
| --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| AI API calls (OpenRouter, Routstr, PPQ, Venice, Local AI) | **Bypass** — `return` without `event.respondWith`. Streaming ReadableStreams must go directly to the page without SW IPC buffering |
| App shell (HTML, CSS, JS, vendor libs, fonts, images)     | **Stale-while-revalidate** — serve cached, update in background                                                                    |

The API bypass is critical for streaming. If the service worker intercepts a streaming SSE response, the IPC pipe between the SW and the page buffers the chunks, breaking the streaming experience. The bypass (returning without calling `event.respondWith`) routes requests directly to the network. Local/private hosts are bypassed only when they are cross-origin, so same-origin app-shell requests can still be cached. Normal localhost development unregisters the SW to avoid stale module caches; use `/app?dev-sw=1` for an explicit local offline smoke test.

## PWA manifest

`manifest.json` makes the app installable as a native app on desktop and mobile:

```json theme={null}
{
  "name": "getbased",
  "short_name": "getbased",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#0f1117",
  "theme_color": "#0f1117",
  "icons": [
    { "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" },
    { "src": "/icon.svg",     "sizes": "any",     "type": "image/svg+xml" }
  ]
}
```

## Vendor dependencies

Chart.js, pdf.js, and Google Fonts are bundled locally in `vendor/`. To update:

1. Edit the version pins at the top of `update-vendor.sh`
2. Run `./update-vendor.sh`
3. Bump `version.js` to bust the SW cache
4. Commit the updated `vendor/` directory
