Platform Overview

The web platform (envirobot.yerielph.workers.dev) is a Next.js 15 site deployed as a single Cloudflare Worker. It documents the project, replays run data for judges, browses the firmware source, previews the CAD model, and gives the team a way to edit all of that content without a redeploy.


Stack

Layer Technology Role
Framework Next.js 15 (App Router) + TypeScript + Tailwind 4 Pages, API routes, styling
Runtime Cloudflare Workers, via @opennextjs/cloudflare (OpenNext) Hosting — not Cloudflare Pages, not Vercel
3D rendering @react-three/fiber + @react-three/drei (three.js) Arena replay (/viz), CAD viewer (/design)
STEP parsing occt-import-js (WASM, client-side) Converts STEP/STP geometry to a renderable mesh in-browser
Charts Chart.js + react-chartjs-2 NTU / moisture bar charts in the visualiser
Database Supabase (Postgres) Content metadata: docs_pages, code_files, design_files
Object storage Cloudflare R2, via aws4fetch (SigV4) File bytes: firmware source, CAD models
Animation GSAP + @gsap/react Landing page scroll effects

Two datastores, two jobs: Supabase holds metadata (filenames, ordering, doc markdown), R2 holds bytes (file content, addressed by r2_key). Every content type follows the same shape: a DB row pointing at an R2 object.


Route Map

Route Purpose Guide
/ Landing page — project pitch, scroll narrative
/docs This documentation site
/viz Run data visualiser — 2D/3D arena replay + charts Visualiser
/code Public Arduino firmware source browser Code Browser
/design CAD model viewer CAD Viewer
/admin Content management console (auth required) Content Admin
/admin/login Admin sign-in Content Admin

Content Pipeline

Firmware source and this documentation both have a canonical source in the git repo — the CMS is a mirror, not the source of truth, so the site can never show something the codebase doesn't actually do:

arduino/envirobot/*.cpp, *.h, *.ino     ──sync-code.mjs──▶  app/api/admin/seed-code/route.ts
                                                              (embeds redacted file contents)
lib/docs-content.ts                     ──sync-docs.mjs──▶  content/docs/*.mdx
                                                              (mirror only — the site reads
                                                               docs-content.ts directly)
Script Run after What it does
node scripts/sync-code.mjs Any firmware change Regenerates app/api/admin/seed-code/route.ts from arduino/envirobot/. Nothing is redacted — WiFi is AP-only, so the firmware holds no private credentials
node scripts/sync-docs.mjs Any lib/docs-content.ts edit Mirrors each entry to content/docs/{slug}.mdx on disk, so doc sources are readable outside the CMS too

After sync-code.mjs, hit POST /api/admin/seed-code from the admin panel (or curl) once to push the embedded files into R2 + the code_files table — that's what /code actually serves. Docs don't need a seed step: /api/docs falls back to docs-content.ts directly whenever no docs_pages row exists for a slug.

Precedence trap: if a doc page is ever edited and saved from /admin, that write creates a docs_pages row which then overrides docs-content.ts permanently for that slug — editing the source file after that point silently does nothing until the DB row is deleted or re-saved. Check the docs_pages table before assuming a docs-content.ts edit will show up live.


Deploy

cd webapp
npm run deploy    # opennextjs-cloudflare build && opennextjs-cloudflare deploy (Wrangler under the hood)
  • Never vercel or the Vercel CLI — Wrangler only.
  • Config: wrangler.jsonc (worker name envirobot, nodejs_compat flag) + open-next.config.ts.
  • Do not add export const runtime = 'edge' to any route — OpenNext runs Node runtime semantics inside workerd; edge exports break the build.
  • npm run preview builds and runs the Worker locally before pushing.

Secrets

Set once via wrangler secret put <NAME>; mirrored locally in .dev.vars (gitignored):

Secret Used by
NEXT_PUBLIC_SUPABASE_URL Both Supabase clients
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY Public client — browser-side login, RLS-scoped
SUPABASE_SERVICE_ROLE_KEY Admin client — server-side only, bypasses RLS. Never send to the browser
R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY R2 SigV4 signing (lib/r2.ts)
R2_ACCOUNT_ID, R2_BUCKET R2 object URL construction

Where Each Feature's Data Lives

Feature Supabase table R2 prefix Public API
Docs docs_pages (slug, title, content, updated_at) — (markdown stored in Postgres, not R2) GET /api/docs?slug=
Code Browser code_files (filename, lang, r2_key, display_order) code/ GET /api/code, /api/code/content, /api/code/download-all
CAD Viewer design_files (filename, r2_key, display_order) (admin-chosen key) GET /api/design, /api/design/content

All public GET routes are edge-cached (s-maxage, varying by content volatility — code content is cached immutable since r2_key is content-addressed by filename, not hash, so a changed file gets a new upload rather than mutating in place).