Skip to content

Architecture

ThermalTrace is an Astro 6 app with output: 'server' on Cloudflare Workers, Supabase (Auth + Postgres + RLS), and Stripe for paid plans.

Sensors / relays ──push or pull──► Cloudflare Worker (Astro)

                    ┌────────────────────┼────────────────────┐
                    ▼                    ▼                    ▼
               Supabase DB          Stripe webhooks      Email / SMS / push
               (readings,           (subscriptions)      (alerts, digests)
                devices,
                households)

App shape

  • SSR by default; dashboard uses DashboardLayout (sidebar + topbar + main)
  • Prefer Astro components; hydrate Preact only where needed (client:visible / client:load)
  • Shared chrome under src/components/dashboard/
  • Overview (src/pages/dashboard.astro) loads Simple or Insights mode (dashboardOverviewMode). Status metrics and Insights cards are derived in src/lib/overviewExtras.ts, freezeHours.ts, and heatingInsights.ts. A forecast-backed time-to-freeze clock (src/lib/spaceThermalModel.ts) models this unheated space's lag vs outdoor hourly forecast and can alert on remaining hours before the probe crosses freeze. Week/history charts (HistoryChart.tsx) optionally overlay humidity and dew point from ChartPoint.humidity.

Mutations

FlowMechanism
Prefs, alert settings, snooze, invitesAstro Actions (src/actions/)
Devices, feeds, Stripe, ingest, adminsrc/pages/api/**
Backgroundsrc/worker.ts scheduled (15‑min history poll; hourly maintenance)

Data model (conceptual)

  • Households own devices and memberships (roles: owner, member, viewer, …)
  • Devices are push or pull; sensors map JSON keys → labels / kinds
  • Readings store history; raw rows roll up on a retention schedule
  • Alert settings drive freeze / humidity / leak / outage / forecast / time-to-freeze remaining-hours / channel fan-out (src/lib/alerts.ts, notify.ts, spaceThermalModel.ts). Shared form parsing: src/lib/alertSettingsForm.ts
  • Canonical storage is household devices + sensors + readings (legacy temp-feed rows auto-migrate into Devices)

Background jobs

Hourly cron (0 * * * *) plus quarter-hour history polls (15,30,45 * * * *) in wrangler.jsonc run via src/worker.ts:

  • Collect history snapshots (every 15 minutes)
  • Evaluate alerts on each poll (pull feeds when available; otherwise latest stored readings — push-only households included)
  • Freeze-map city aggregates (opt-in)
  • Trial reminders and onboarding drips
  • Monthly / quarterly reports on schedule
  • Retention / housekeeping as configured

Push ingest also evaluates threshold, leak, and rule alerts immediately (same cooldowns as cron). Weekly digests send Monday 08:00 UTC when enabled.

Failed jobs appear on the Jobs admin UI and notify ops via SMTP_MAIL_TO and optional OPS_DISCORD_WEBHOOK_URL.

Manual history collection (Bearer CRON_SECRET; per-IP rate limited):

bash
curl -X POST https://your-domain/api/cron/collect-history \
  -H "Authorization: Bearer $CRON_SECRET"

Push ingest (POST /api/ingest/<key>): ~64KB max payload, ~60 req/min/device (per Worker isolate).

Conventions

Cursor rules under .cursor/rules/ cover Astro architecture, design tokens, dashboard hydration, and performance.

An import-guard Vitest suite (src/lib/astroImportGuard.test.ts) fails CI if a page renders a component it never imports.

Repo map

src/pages/api/ingest/   Push ingest
src/lib/                Devices, alerts, mailer, Stripe, households
src/worker.ts           fetch + cron
supabase/migrations/    Schema + RLS
sketches/               Sample firmware
docs/                   This VitePress site → GitHub Pages
public/openapi.yaml     HTTP API contract

App & product guides: thermaltrace.dev · This site: developer reference