Deployment
The current official deployment separates the static browser app from several narrow, independently deployed VPS services. Vercel still builds and serves the app, compatibility redirects, diagnostics, and bounded transition adapters, but current official clients do not send wearable/CAMS traffic or new profile shares through the static app host. Cross-device Sync and Agent Access use the dedicated relay stack.Production build
Source deployments require Node.js 24.npm ci compiles canonical .ts and .mts sources with TypeScript 7 through prepare; the emitted .js and .mjs files are ignored in Git. vercel.json enables Git deployment only for main and then runs:
vercel.json gives hashed js/bundle-*.js files immutable cache headers and applies the CSP and security headers before the legacy documentation redirects and /app rewrite.
Production catalog fetch variables are build-time secrets, not browser runtime configuration:
With no URL, the script accepts an existing readable catalog or copies the public example stub. When a URL is present but a preview build has no token, it also uses the stub. A configured production fetch that returns invalid JSON or the wrong catalog shape fails the build rather than silently shipping it.
For local production verification, use
npm run production:build or npm run production:check; both compile first. Direct execution of scripts/*.mjs after source edits requires npm run typescript:build.
Standalone compatibility and profile-share Docker images use a build stage with compiler dependencies, then copy compiled runtime modules into a non-root Node runtime stage. Rebuild from source when updating these services; copying uncompiled TypeScript into the runtime image is insufficient. Browser imports, worker URLs, service routes, and Companion download paths retain their JavaScript runtime contracts. Vercel’s function configuration refers to canonical API sources such as api/proxy.ts.
Official service topology
js/proxy-runtime.ts routes official hosts to the integrations service and independent self-hosts to same-origin /api/proxy. js/profile-share.ts routes current vps1_... links from official hosts directly to the share service while older IDs and self-hosted links remain same-origin.
Sync relay stack
The sync stack is released separately through immutable getbased-relay tags. The app’s compatibility CI currently pins v2.0.0, whose dependencies include@evolu/common 8.9.0 and @evolu/nodejs 3.2.0, with Docker pinned to Node.js 24.20.0. These describe the tested pin, not every later relay release or a live deployment revision. Evolu source is installed from the locked npm dependencies; it is not vendored into the relay.
The browser app and relay use independent adapter packages and version streams. The app currently uses @evolu/common 8.7.0 with @evolu/web 3.1.0, while the CI-pinned relay uses the versions above. They share the Evolu 8 protocol generation; matching package numbers are neither expected nor required. The app’s Sync compatibility workflow pins the exact relay release commit and exercises both the Evolu 8 default client and the temporary Evolu 7 rollback client against a disposable real relay.
The public TLS origin routes three surfaces while keeping private administration off the network:
The reverse proxy must overwrite the gateway’s dedicated client-IP header from the real connection peer. It must not trust a caller-supplied value. The relay database and context store use separate persistent volumes, and the gateway mounts only the verifier socket—not the relay database.
Before a relay major upgrade, record the current release and image, stop the stack, and create verified backups of both data volumes and the deployment configuration. Keep the previous images available. Rollback may require restoring the matching data backup rather than changing only the image.
Vercel routes that remain
User documentation is not built from the application repository. It lives in the separate Mintlify project.
Compatibility proxy service
The standalone entry point isserver/compat-proxy-server.ts; deployment assets live in deploy/compat-proxy/. The container listens on its internal port and should be published only on host loopback behind a TLS reverse proxy that exposes the exact /api/proxy path.
The operated policy accepts only classified operations:
- official Oura, Withings, Polar, and existing legacy Fitbit OAuth/API requests;
- the exact NVIDIA NRAS GPU-attestation operation;
- CAMS atmosphere requests with coordinates forced to a 0.1° grid;
- bounded credential-free public-page fetches used by reviewed application features.
Standalone compatibility variables
Never expose the container port directly, add request-body logging, or broaden an operated allowlist without documenting the target, data visible to the service, origin policy, limits, and tests.
Profile-share service
The current official store isserver/profile-share-server.ts with a SQLite adapter in lib/profile-share-sqlite-store.ts; deployment assets live in deploy/profile-share/. The browser compresses and encrypts a single-profile export before upload. The service accepts at most a 3.75 MB envelope and a 30-day expiry, while the reverse proxy caps the HTTP body at 4 MB.
Cleanup runs at service start, hourly, after creation, and when an expired record is read. The database ceiling defaults to 512 MiB. The operated policy intentionally keeps no retained database backup, so service loss can invalidate outstanding links without affecting source profiles.
Standalone share variables
The service must run in its own container and writable volume, separate from the compatibility proxy, Evolu relay, and UV service. Do not log request bodies, query strings containing share IDs, or management material.
Legacy Blob transition
Existing Vercel Blob envelopes are not copied.api/share.ts implements a bounded transition configured with:
Before cutover, the adapter retains legacy behavior. During the window, it redirects new writes, serves live legacy reads/deletes from Blob, and redirects legacy misses. After the deadline, all supported operations redirect and Blob credentials can be removed. Do not turn the transition into an indefinite fallback.
Self-hosted /api/proxy
An independent deployment keeps the same-origin endpoint and its own provider credentials. Relevant variables are:
Client secrets never enter runtime config. The browser receives only public client IDs and capability booleans. A missing or incomplete fail-closed provider configuration must block connect, exchange, refresh, sync, and backfill while leaving disconnect available.
For CAMS, set
UVDATA_UPSTREAM explicitly on an independent self-host or choose browser-direct Open-Meteo. UVDATA_BEARER is required only when that selected upstream expects it.
Local development
The production build also emitsgetbased-companion.mjs for the CLI-provider bootstrap. Serve that artifact alongside the app, but run Companion on each browser user’s machine, not on the public web server. Hosted discovery grants chat-only credentials; the current service deliberately reserves lifecycle controls for installation-authorized local connections. Custom page origins require an explicit Companion allowlist. See CLI agent build and security.
npm run dev-server -- 8000 compiles TypeScript, builds the Companion download, serves emitted native modules, and mirrors same-origin APIs. Open /app; direct node dev-server.js invocation requires already compiled files.
Use
.env.local for untracked local secrets. Environment variables already present in the process take precedence.
The local-only /api/deploy-catalog workflow has a separate set of maintainer variables. They are not browser configuration and are unrelated to the production build’s CATALOG_FETCH_* inputs:
Missing configuration skips the corresponding operation. The development endpoint validates the deploy-hook URL shape and never exposes these values to the browser.
Content Security Policy
The current hosted policy allows self scripts, Wasm evaluation, blob workers, the approved analytics origin, jsDelivr for explicitly retained runtime needs, HTTPS/WebSocket connections, and loopback Local AI connections. It denies frames, objects, and foreign base URLs. The exact source of truth isvercel.json.
When adding a provider or browser runtime, review the CSP, direct-CORS requirements, service-worker network-only list, proxy policy, and public privacy disclosure together. CSP permission does not authorize use of the compatibility relay.
Service worker
Each deployed build has an identity derived from its commit and deployment ID, embedded in emittedversion.js and the worker by the production builder. New builds use separate caches even when the user-facing APP_VERSION is unchanged. Semver labels are for releases and changelogs; a version bump is not required to deliver every patch.
While visible, the app checks for updates every five minutes and on return. It installs the new app shell in the background and offers Reload / Later only when that build is ready. Choosing Later keeps the current build running. A failed download leaves the active build usable. Initial installation does not produce a false update prompt.
Required shell assets are precached with bounded concurrency/retries, including offline settings artwork and the bundled Fitbit fallback icon. Keep this inventory aligned with production build output.
Activation cleans obsolete app caches. Version 1.19.3 provides the migration signal for older version-based clients. Local development normally unregisters the worker; use
/app?dev-sw=1 only for deliberate offline verification.
For release tags, do not move historical tags that happen to share a semver label. The repository already uses descriptive tags such as v1.19.0-cli-agents to disambiguate its older history.
PWA manifest
The current manifest contract is:any maskable; the SVG is an any icon. Keep the manifest, service-worker /app fallback, installed-PWA smoke test, and landing-page routes aligned.