Skip to main content

Architecture

Native modules in development, production bundles in deployment

Local development loads the source as native ES modules (<script type="module">), so most UI work remains edit → reload. Hosted deployments run the Rolldown production build: it collapses the static startup graph into a hashed entry bundle while preserving feature-level lazy chunks. That does not mean the repo has no tooling. The current repository uses npm for tests, type checks, Playwright, Vercel helpers, and a deploy-time catalog fetch. The practical development workflow is:
  1. Edit a file
  2. Reload the browser for quick local feedback
  3. Run the relevant checks before committing
Common change-scoped commands:
The production build is deployment machinery, not the authoring format: source modules remain the ownership boundary and the generated MODULE_MAP.md records their direct imports. GitHub Actions owns the exhaustive browser and combined-coverage matrix.

File layout

Entry points

That is the development entry point. The hosted artifact rewrites it to the hashed Rolldown entry generated by scripts/build-production.mjs; dynamically imported features remain separate lazy chunks. main.js imports startup-loaded feature modules from app-feature-modules.js and starts startup-orchestrator.js. Foundation/privacy imports are grouped behind app-foundation-modules.js; Health & Data startup feature imports are grouped behind app-health-data-modules.js; Light & Sun startup feature imports are grouped behind app-light-sun-modules.js; import/export startup feature imports are grouped behind app-data-io-modules.js; AI/chat/settings startup feature imports are grouped behind app-ai-interaction-modules.js; UI shell startup feature imports are grouped behind app-ui-shell-modules.js. The orchestrator installs startup globals, the lazy EMF facade, app-wide event listeners, and the refresh callback, then registers the DOMContentLoaded sequence. Encryption unlock, meteo cache hydration, cross-tab broadcast, and folder backup setup are delegated to startup-foundation.js; profile migration, cache warmup, and active-profile data loading are delegated to startup-profile.js; wearable/OpenRouter callback routing lives in startup-oauth-callbacks.js; wearable runtime config/scheduler boot and non-blocking post-profile maintenance live in startup-maintenance.js; initial theme/sidebar/navigation/sync/changelog/header/chat/file-input UI bootstrap lives in startup-ui.js.

Marker catalog and placement boundary

The marker catalog uses an authoring → generated runtime → compatibility facade chain. Category modules under js/marker-schema/ are the source of truth, scripts/build-marker-schema.mjs generates js/marker-schema.js, and long-lived consumers continue to import from js/schema.js. This keeps a large catalog maintainable without forcing a broad import-graph refactor. Marker storage and display are separate boundaries. Profile values and companion metadata remain keyed by native category.markerKey; immutable gb:marker:* and custom:* IDs let marker-placement.js apply a per-profile visual category only after getActiveData() completes calculations and unit conversion. Renderers may use displayCategoryKey, but mutations must resolve through storageDotKey. The terminology registry follows a separate authoring and generation path. It is optional metadata and is intentionally outside startup, persistence, import/export, backup, sharing, and sync. See Marker model and category placement and Laboratory terminology registry. The sidebar has three conceptual groups:
  • Home: dashboard, the customizable cross-lens overview.
  • Lenses: labs, genome, body, light, insight, and recommendations. views-router.js validates and dispatches these routes. Dashboard route/widget wiring is composed in dashboard-view-composition.js, with the dashboard shell rendered through dashboard-page-view.js; lab category routes render through category-page-view.js; Labs, Genome, Body, Insight, and Recommendations pages are rendered through lens-pages.js; shared lens page chrome and ordering live in lens-page-shell.js; the Light page shell lives in light-page-view.js, while channel pills and drill-down panels live in light-channel-view.js.
  • Tools: focused utilities such as compare dates, correlations, knowledge base, custom markers, and EMF assessment entry points.
The dashboard is not a replacement for lens pages. It is a user-composed overview made from lens/tool widgets. Default widgets are ordered for a new user as: Biological Coherence, Current Focus, Current Priority, Quick Markers, Key Trends, Recommended Next Steps, Profile Context, Biometrics Overview, Biological Age, Metabolic Flexibility, and Cycle when available. Users can reorder, hide, reset, clear, and add widgets. Lens pages expose Add/Remove Dashboard toggles for widgets that can appear in the overview. Recommendations have both a dashboard widget and a dedicated recommendations route. The page aggregates data-linked recommendation candidates from Labs, Body, Light, and Genome signals, while product option rendering and affiliate disclosure remain owned by recommendations.js.

6-layer dependency graph

Modules in a higher layer may import from lower layers. Modules in the same layer must not import from each other — cross-layer calls within the same layer use window.fn() to avoid circular dependencies.

Circular dependency avoidance

The main tension is between views.js (the compatibility/router facade) and modules like data.js and charts.js (which view modules depend on but which also need to trigger re-renders). Two mechanisms break cycles: registerRefreshCallback(fn) in data.jsapp-event-listeners.js registers the refresh function at startup, so data.js can trigger re-renders without importing views.js:
window.fn() calls — functions exposed via Object.assign(window, {...}) are callable from any module without creating an import edge:

External dependencies

Bundled locally under vendor/: AI providers (OpenRouter, Routstr, PPQ, Venice, Local AI) are called directly from the browser for normal chat/import flows. Same-origin API helpers under api/ cover hosted infrastructure such as OAuth/runtime proxying and encrypted profile share envelope storage, but they must not receive AI prompts or plaintext profile data unless explicitly documented for a new feature.