Restores the startup scrape removed in deb8e41, gated on
getParkDayCount() < 50 so warm restarts don't hammer the API.
Cold containers (e.g. after the volume mount fix) populate
immediately instead of waiting up to 24h for tier-4 cron.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Update package names, Docker image tags, CI/CD workflow, and
documentation to reflect the public brand name. References to
the actual Six Flags theme park chain/API are intentionally kept.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The script was already in layout.tsx but CSP blocked both loading
and sending beacons to tracking.thewrightserver.net.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update /app/data → /app/backend/data across all docs to match the
volume mount fix from 3c91d9a. Add missing TZ env var to the web
container snippet in OPERATIONS.md. Correct Midwest (6→7) and
West & International (6→5) park counts in ARCHITECTURE.md.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
WORKDIR is /app/backend so the DB lands at /app/backend/data/parks.db,
but the volume was mounted at /app/data — a different directory. The DB
lived in the container's ephemeral layer and was wiped on every pull.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
getTodayLocal() relied on system clock hours, which broke in the web
container (TZ defaulting to UTC) — the day flipped at 11 PM EDT (3 AM
UTC) instead of 3 AM Eastern. Now uses Intl.DateTimeFormat with an
explicit America/New_York timezone. Also replaced all toISOString()
date formatting with local-component helpers to avoid UTC conversion.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fires scrapeToday() then scrapeFullYear() as a background task on
startup so fresh deploys have data immediately instead of waiting
for the first cron tick. Staleness check makes warm restarts a no-op.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add docs/ folder with architecture, operations, API reference, and
development guides covering system design, deployment, troubleshooting,
all backend endpoints, and contributor workflows.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove references to Playwright discovery, RCDB scraping, scraper
container, and npm run scripts. Document the new two-container setup,
tiered scheduling, backend API endpoints, and local dev workflow.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace scraper container with backend API container. Web image no
longer mounts a data volume or ships SQLite. Backend image runs Hono
server with node-cron scheduler, owns the database exclusively.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Server components now fetch composed data from the backend instead of
directly querying SQLite and external APIs. Removes better-sqlite3
dependency from the frontend entirely.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Delete scripts/scrape.ts and scripts/scrape-schedule.sh — their
functionality now lives in the backend's node-cron tiered scheduler
(backend/src/services/scheduler.ts + scraper.ts).
Remove scrape and scrape:force npm scripts from package.json.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Standalone Node.js backend that owns the SQLite database and serves
composed data via REST endpoints. Replaces the shell-scheduled scraper
with in-process node-cron tiered scheduling.
Backend structure:
- Hono HTTP server on port 3001 with CORS and request logging
- Singleton SQLite connection with WAL mode
- In-memory TTL cache for Queue-Times and fetchToday responses
- Comparison check on fetchToday (read-before-write, only upserts on change)
API endpoints:
- GET /api/calendar/week — week schedule + live ride counts for all parks
- GET /api/calendar/:parkId/month — month calendar for one park
- GET /api/parks — park list with metadata
- GET /api/parks/:id — single park detail
- GET /api/parks/:id/rides — live rides with Queue-Times/schedule fallback
- GET /api/status — health check, scrape stats
- POST /api/scrape/trigger — manual scrape (scope: today/month/upcoming/full)
Scheduler tiers:
- Tier 1: today — hourly (Mar-Dec)
- Tier 2: current month — every 6 hours
- Tier 3: upcoming — twice daily (3 AM + 3 PM)
- Tier 4: full year — daily at 3 AM
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Embed Six Flags API IDs directly in the park registry and snapshot
coaster lists from park-meta.json into a TypeScript module. This
eliminates the Playwright-based discovery script, RCDB scraper, and
runtime dependency on park-meta.json — preparing for the backend
API transition.
- Add apiId field to Park type and all 24 park entries
- Create lib/coaster-data.ts with hardcoded coaster lists
- Update page components to use park.apiId and new getCoasterSet()
- Remove scripts/discover.ts, lib/scrapers/rcdb.ts, lib/park-meta.ts
- Remove data/park-meta.json from shared volume
- Remove playwright devDependency and discover npm script
- Simplify scripts/scrape.ts (no RCDB, no discovery checks)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The API without a date param returns today's operating data directly,
invalidating the previous assumption that today's date was always missing.
- Add fetchToday(apiId, revalidate?) to sixflags.ts — calls the dateless
endpoint with optional ISR cache
- Extract parseApiDay() helper shared by scrapeMonth and fetchToday
- Update upsertDay WHERE clause: >= date('now') so today can be updated
(was > date('now'), which froze today after first write)
- scrape.ts: add a today-scrape pass after the monthly loop so each run
always writes fresh today data to the DB
- app/page.tsx: fetch live today data for all parks (5-min ISR) and merge
into the data map before computing open/closing/weatherDelay status
- app/park/[id]/page.tsx: prefer live today data from API for todayData
so weather delays and hour changes surface within 5 minutes
- scrapeRidesForDay: update comment only — role unchanged (QT fallback)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removed flex:1 from left side so ride count has a real minimum width to
trigger wrapping. Added whiteSpace:nowrap so flexbox knows when to wrap it.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Padding on the parent main element doesn't work with overflow:auto — the
fix belongs on the scrollable div wrapping the table itself.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Increased pill vertical padding (3px→5px) and internal line gaps (1-2px→2-3px)
so the stacked hours/timezone text feels less cramped.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Open parks get a colored left border (green/amber/blue) instead of a dot.
Region headers lose their accent line; distinguished by "— REGION —" format
with higher-contrast text instead.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Dot turns blue (instead of green) during weather delay on homepage
- Mobile card "Open today" badge becomes blue "⛈ Weather Delay"
- Park page LiveRidePanel shows a blue "⛈ Weather Delay — all rides currently closed" badge instead of "Not open yet — check back soon"
- Added --color-weather-* CSS variables (blue palette)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Park open indicator now derives from scheduled hours, not ride counts.
Parks with queue-times coverage but 0 open rides (e.g. storm) show a
"⛈ Weather Delay" notice instead of a ride count on both desktop and mobile.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The actual overflow fix was removing whiteSpace:nowrap from the td.
With that gone, 240px is sufficient and content wraps naturally when tight.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Column uses clamp(220px, 22%, 280px) — scales on small screens, caps at
280px on large ones. Park name gets whiteSpace:nowrap so it stays one line.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause was a hardcoded 240px column width + whiteSpace:nowrap that
prevented content from ever fitting on smaller displays. Now uses 25%
width so the column scales with the viewport, removed nowrap so text
wraps naturally when squeezed, and reverted clamp() back to fixed sizes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removed flex:1/minWidth:0 from name span so dot stays snug to the right
of the park name. Removed flexShrink:0 from ride count so both sides can
compress. All text uses clamp() to scale proportionally with viewport.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Park name, city/state, and ride count all use clamp() so they shrink
proportionally on smaller displays without truncating or overflowing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Let the park name side flex-shrink (minWidth:0, flex:1) so the ride count
always fits in the row without overflowing its column.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes the space-between flex layout that pushed the count to the right
edge of a fixed-width column. Now stacks under city/state so it always
fits within the park name column regardless of screen size.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>