Personal health desktop app. The Cloudflare Worker pulls Oura, fuses + scores daily snapshots into D1, and this Vite app reads them. Goals, chapters, and context flags remain writable on the sync API.
NicoBase iOS is paused — do not block web work on phone sync. When mobile resumes, it should consume the same API rather than own ingestion.
Full design: NicoBase-web-spec.md. Cross-repo notes: LINKING.md. Open work: NicoBase-web-todo.md. Web-first proposal: docs/web-first-proposal.html.
- Repository is consolidated on a single branch:
main. - CI is green on
mainfor token drift, server tests, and app/server TypeScript builds. - Production Worker, D1, CORS, and Worker secrets are provisioned.
CLOUDFLARE_API_TOKENis set; push-to-main triggers real Pages + Worker deploys. deploy-worker.ymlapplies pending D1 migrations (wrangler d1 migrations apply nicobase --remote) before each Worker deploy.- Active direction (2026-07-26): web-first Oura ownership. Worker cron at 05:00 HKT + sidebar Refresh / Settings sync. Day keys Asia/Hong_Kong. Access-only login on sudx.io. iOS / HealthKit deferred.
POST /v1/admin/oura/sync accepts from / to (YYYY-MM-DD, HKT day keys). Default window is the last 30 days. Nightly cron uses a 14-day window.
Workers have CPU time limits — prefer ≤30-day chunks for first-time or catch-up backfills, and wait for each request to finish before starting the next. Re-running an overlapping range is safe (upserts). There is no skip-if-fresh optimization yet; expect full recompute for the requested days.
├── app/ Vite + React + TypeScript frontend, local port 5173
├── server/ Hono Cloudflare Worker + D1 sync layer, local port 8788
├── shared/ Zod DTO schema + design tokens
├── scripts/ token codegen + dev data seeding
├── artifacts/ios/ Tokens.generated.swift export for nicobase-ios
└── .github/workflows CI + conditional Cloudflare deploy workflows
# 1. Install workspace deps
pnpm install
# 2. Start the local worker. This uses in-memory data, no D1 required.
cd server && bun --hot dev-bun.ts
# 3. In another shell, start the frontend. Vite proxies /v1 to :8788.
cd app && pnpm dev
# 4. From the repo root, seed plausible dev data after the worker is running.
node scripts/seed-dev.mjs 30Open http://localhost:5173 — no login screen. Vite proxies /v1 to the local worker and injects the dev web key (web.local-dev) on each request.
Dev-only phone seed/write key (curl / scripts, not the browser UI):
phone.local-dev
For local Wrangler/D1 instead of dev-bun.ts:
cp server/.dev.vars.example server/.dev.vars
cd server
npx wrangler d1 migrations apply nicobase --local
pnpm devThe app always calls same-origin /v1/*:
- Local: Vite proxies to
http://localhost:8788and injectsweb.local-dev(orNICOBASE_WEB_API_KEYif set in the shell). - Production: Pages Functions proxy to the Worker and inject the web API key server-side. Do not set
VITE_SYNC_URLon production builds.
Production URLs:
Web (canonical): https://nicobase.sudx.io (Cloudflare Access login)
Worker (direct): https://nicobase-sync.nicolasmonterog.workers.dev
https://nicobase.pages.dev redirects to https://nicobase.sudx.io.
Template: app/.env.example.
Worker secrets are not committed. Production uses Cloudflare secrets on the Worker plus NICOBASE_WEB_API_KEY on the Pages project for the proxy. See SECRETS.md.
Run the same narrow checks used by CI:
make tokens-check
pnpm --filter nicobase-web-server test
pnpm buildOr run workspace tests only:
pnpm testThere is no ESLint config and no frontend test suite yet.
shared/tokens.json is the source of truth for colors, spacing, and radii. The codegen at scripts/sync-tokens.mjs writes:
app/src/tokens.generated.css— imported byapp/src/styles.css.artifacts/ios/Tokens.generated.swift— copy intonicobase-iosatNicoBase/Shared/DesignSystem/Tokens.generated.swift.
From the repository root:
make tokens
make tokens-checkDo not hand-edit generated files; CI rejects drift.
Current limitation: Swift codegen emits color tokens only. Spacing/radius are exported to web CSS, but iOS spacing/typography remain hand-written until the follow-up in NicoBase-web-todo.md.
Production resources:
Worker: https://nicobase-sync.nicolasmonterog.workers.dev
Pages: https://nicobase.sudx.io (nicobase.pages.dev → redirect)
D1: nicobase / 3c6969e0-aca1-488c-a05d-6060e9c5788a
CORS: https://nicobase.sudx.io
Manual Worker deploy:
cd server
npx wrangler d1 migrations apply nicobase --remote
npx wrangler secret list
npx wrangler deployManual Pages deploy:
pnpm --filter nicobase-web-app build
npx wrangler pages deploy app/dist --cwd app --project-name nicobase --branch mainEnsure the Pages project has NICOBASE_WEB_API_KEY set (wrangler pages secret put …). See SECRETS.md.
GitHub Actions:
.github/workflows/ci.ymlruns on PRs and pushes tomain..github/workflows/deploy-worker.ymlruns onmainwhenserver/**,shared/**,pnpm-lock.yaml, or the workflow changes..github/workflows/deploy-pages.ymlruns onmainwhenapp/**,shared/**,pnpm-lock.yaml, or the workflow changes.
Deploy workflows require these repo secrets:
CLOUDFLARE_ACCOUNT_ID set
CLOUDFLARE_API_TOKEN set (token: nicobase-web-deploy, account-locked to Nicolasmonterog)
deploy-worker.yml runs wrangler d1 migrations apply nicobase --remote before wrangler deploy, so adding a migration file to server/migrations/ is enough — the next push to main applies it.