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.
Richmond family guide to the James River: conditions, access points, and activity recommendations.
LifestyleTypeScript · PLpgSQLGitHub @MikeNGarrettOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
James River conditions for Richmond, VA families focused on safety.
Translation pending
Taken from the repository README
Loading the full README.
Read the full 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.).