Bygga
Type the product name from the box. Bygga shows every assembly step in 3D, with the parts, screws and tools for each one, and reads it out loud.
翻譯準備中
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證James River conditions for Richmond, VA families focused on safety.
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗A family-friendly James River conditions dashboard for Richmond, Virginia.
Live at rvajames.org.
There are a half-dozen places to read raw USGS gage data, NWS forecasts, and James River Watch bacterial samples. None of them tell a parent in Richmond, in plain language, whether it's safe to take a four-year-old to skip rocks at Belle Isle today. This app does — by pulling the data on a schedule, running it through a deterministic safety-rules engine, and layering a small amount of AI on top to translate conditions into experience.
Richmond families with kids who want to spend a day at the river without spending an hour piecing together gauge readings, advisories, weather, and trail closures from five different websites first.
The app shows nine specific access points — Belle Isle, Pony Pasture, Texas Beach, Browns Island, Mayo Island, Shiplock Trail, North Bank Trail, Buttermilk Trail, Pump House — with status, recommendations tailored to the youngest family member's age band, and a per-location detail page with resources and activity guidance.
lib/safety/rules.ts + lib/safety/thresholds.json) to compute a status (safe / caution / danger / closed) per access point. The same thresholds drive the at-a-glance UI and the AI prompt — single source of truth.| Layer | Choice | Why |
|---|---|---|
| Framework | Next.js 15, App Router, React 19, TypeScript | Server Components + streaming + edge-runtime support |
| Hosting | Cloudflare Workers via @opennextjs/cloudflare | Cheap, fast, global. Replaced @cloudflare/next-on-pages (deprecated) early. |
| Database | Supabase (Postgres) | Generated TypeScript types, RLS for anon read-only access, service-role for ingest writes |
| AI | Anthropic Claude API — Haiku default, Sonnet on escalation | Prompt caching keeps cost negligible; lazy on-demand generation cached in Supabase |
| Styling | Tailwind CSS v4 (CSS-first via @theme) | No JS config; container queries native |
| Validation | Zod | Runtime validation at every external boundary (API ingest, AI output) |
| Scraping | cheerio | Works on Workers with nodejs_compat |
| Auth (admin only) | Cloudflare Access | Zero-trust gate; no auth code to maintain |
| Tests | Vitest | Pure-function rules-engine coverage |
┌──────────────────┐ ┌──────────────────┐
│ USGS (15 min) │ │ NWS+NOAA (hourly)│ Cloudflare Cron
│ JRA (daily) │ │ RVA closures (1d)│ Triggers
│ RVA CSO (12h) │ │ USGS pct (daily) │
└────────┬─────────┘ └────────┬─────────┘
│ │
└───────────┬───────────┘
▼
┌───────────────┐
│ Supabase │ conditions_snapshots,
│ (Postgres) │ advisories, location_status,
│ │ usgs_percentiles, ...
└───────┬───────┘
│
▼
┌───────────────────────┐
│ Next.js Server │
│ Components on │
│ Cloudflare Workers │
└───────────┬───────────┘
│
Rules engine (deterministic)
│
▼
┌───────────────────────┐
│ Page render │
│ • Deterministic UI │
│ • <Suspense> │
│ └─► Lazy AI ─────┼──► Anthropic (Haiku/Sonnet)
│ cached │ + cached system prompt
│ in DB │
└───────────────────────┘
Highlights:
lib/ai/get-or-generate.ts cached in Supabase by prompt hash. UNIQUE constraint handles concurrent-write races. Net cost at current traffic: pennies per month.lib/safety/rules.ts. AI narration only appears in the metro summary at top and on per-location detail pages, where its voice and nuance earn their keep. Avoids 9 AI calls per homepage visit.<dialog> with closedby="any". Container queries via Tailwind v4 @container. text-wrap: balance and pretty. Speculation Rules for prefetch. The GoogleChrome/modern-web-guidance skill (installed via skills-lock.json) informs component-level decisions.location_status table with kind enum (open / restricted / closed / closed_indefinite). A closed location overrides weather-based status entirely./admin/closures (Cloudflare Access-gated).npm i -g pnpm)brew install supabase/tap/supabase)git clone <this repo>
cd rva-james
pnpm install
# Start the local Supabase stack
supabase start
# Note the printed URL, anon key, and service_role key
# Copy env templates
cp .env.development.local.example .env.development.local
# Wrangler requires the filename .dev.vars — symlink it to the single source of truth
ln -s .env.development.local .dev.vars
Populate .env.development.local with:
| Variable | Value |
|---|---|
NEXT_PUBLIC_SUPABASE_URL | Supabase API URL (http://127.0.0.1:54321 locally) |
NEXT_PUBLIC_SUPABASE_ANON_KEY | anon key from supabase start |
SUPABASE_URL | same as NEXT_PUBLIC_SUPABASE_URL |
SUPABASE_ANON_KEY | same as anon key |
SUPABASE_SERVICE_ROLE_KEY | service_role key from supabase start |
ANTHROPIC_API_KEY | your Anthropic API key (optional locally) |
CRON_SECRET | any random string for local cron testing |
pnpm dev # Next.js dev at http://localhost:3000
For a faithful preview of the Cloudflare Worker build:
pnpm build:cf # OpenNext build
pnpm preview # Local Worker at http://localhost:8787
Hit each cron route directly with the CRON_SECRET header:
curl -H "x-cron-secret: $CRON_SECRET" http://localhost:3000/api/cron/usgs
curl -H "x-cron-secret: $CRON_SECRET" http://localhost:3000/api/cron/nws
curl -H "x-cron-secret: $CRON_SECRET" http://localhost:3000/api/cron/jra
curl -H "x-cron-secret: $CRON_SECRET" http://localhost:3000/api/cron/cso
curl -H "x-cron-secret: $CRON_SECRET" http://localhost:3000/api/cron/usgs-percentiles
curl -H "x-cron-secret: $CRON_SECRET" http://localhost:3000/api/cron/noaa-ahps
curl -H "x-cron-secret: $CRON_SECRET" http://localhost:3000/api/cron/rva-closures
Each returns { ok, rowsWritten } on success.
pnpm test # Vitest — pure-function rules engine + ingest
| Command | Description |
|---|---|
pnpm dev | Next.js dev server |
pnpm build:cf | OpenNext Cloudflare bundle |
pnpm preview | Local Worker preview |
pnpm deploy:cf | Deploy to Cloudflare Workers |
pnpm test | Vitest |
pnpm lint | ESLint |
supabase db push | Apply migrations to linked project |
supabase gen types typescript --local > lib/supabase/types.ts | Regenerate DB types |
app/
page.tsx Homepage (deterministic + lazy AI)
layout.tsx Brand font, metadata
globals.css Tailwind v4 @theme + tokens
locations/[slug]/ Per-location detail
safety/ Safety + sources page
status/ Ingestion + cost dashboard
admin/closures/ Cloudflare Access-gated closure admin
api/cron/<source>/route.ts Each ingest job
_dev/ Dev-only routes (NODE_ENV gated)
brand/ Brand token showcase (dev-gated)
components/
metro/ RiverSegmentPanel, MetroSummaryPanel,
RiverConditionsDetailDialog,
RiverWideActivityGrid
tiles/ RiverLevelTile, AdvisoriesBanner, ...
ui/ PageContainer, HorizontalGauge,
Sparkline, TrendArrow
filters/ ConditionsForm
location/ ActivityMatrix, ResourceList
legal/ DisclaimerFooter, FirstVisitModal
banners/ FloodBanner
lib/
ai/ client, get-or-generate, system-prompt,
prompts/{interpret-location, summarize-metro}
ingest/ One file per data source + run.ts wrapper
queries/ Server-side data fetchers per surface
safety/ rules.ts + thresholds.json (single source
of truth shared with the AI prompt)
supabase/ Server + browser clients, generated types
supabase/migrations/ 0001..0009 schema evolution
The architecture is general-purpose; the data is local. To stand up a version for another waterway:
In supabase/migrations/0001_init.sql (and 0003 for the gauge entries), replace:
00065 (gage height), 00060 (discharge), 00010 (water temp), 62620 (tidal/regulated elevation).lib/ingest/usgs.ts — gage IDs and parameter codeslib/ingest/usgs-percentiles.ts — historical percentile fetch (same station list)lib/ingest/nws.ts — NWS grid point for your city (find via https://api.weather.gov/points/{lat},{lon})lib/ingest/noaa-ahps.ts — AHPS forecast gauge ID (find at water.noaa.gov)lib/ingest/jra.ts — replace with your region's bacterial water-quality program (Riverkeeper, Waterkeeper Alliance member, etc.); may require rewriting the scrape entirelylib/ingest/cso.ts — your city's combined-sewer-overflow advisory page (may not exist for separate-sewer cities; remove if so)lib/ingest/rva-closures.ts — your city/parks closure announcement pageEdit lib/safety/thresholds.json for your river's characteristics:
normal_max_ft, flood_stage, etc.) — pull from NWS AHPS for your gaugegage_safe_max_ft per activity)The rules engine in lib/safety/rules.ts reads from this JSON — no code changes needed if your river fits the same value shape.
lib/ai/system-prompt.ts is the heart of the personality. Update:
Note: the AI is told to derive nothing it shouldn't — every safety claim is grounded in thresholds.json. Maintain this discipline.
app/globals.css @theme block defines colors. Replace Richmond's rva-blue etc. with your city's brand or an independent palette. Verify AA contrast programmatically (e.g. via wcag-contrast) on every foreground/background pair.
supabase/migrations/0006_location_resources.sql seeds the resource links per access point. Rewrite for your locations — your local parks department, riverkeeper, NPS unit, etc.
app/safety/page.tsx — your local emergency services + relevant authoritiescomponents/legal/DisclaimerFooter.tsx — adjust if local liability framing differscomponents/legal/FirstVisitModal.tsx — first-visit copyapp/layout.tsxwrangler secret put the four secrets (SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, ANTHROPIC_API_KEY, CRON_SECRET)wrangler.jsoncsupabase link --project-ref <yours> and supabase db pushpnpm deploy:cf/admin/* with allowed admin email(s)Trigger each cron route against the deployed Worker, confirm ingestion_runs rows show ok=true, visit the homepage. The deterministic side renders instantly; the AI side warms on first visit per (date, age bucket) combo.
Is: A serviceable starting point for any city-river dashboard targeting families. The patterns — deterministic rules + lazy AI cached in Supabase, cron-driven ingest, modal-and-disclosure UX — are reusable.
Isn't: A turnkey SaaS, a generic data dashboard framework, or production code for high-traffic use. It's tuned for one specific river, one specific audience, and traffic that fits in Cloudflare Workers' free tier. If your river is dramatically different — significant ice formation, tidal-only with no free-flowing reach, no public gauges — expect to rewrite more than you reuse.
See LICENSE. The data sources have their own licenses and terms; respect upstream attribution requirements (USGS public domain, NWS public domain, James River Association attribution requested, OSM if you add map tiles, etc.).