docs: sync README and docs/ with current codebase
Build and Deploy / Lint, typecheck, test (push) Successful in 34s
Build and Deploy / Build & Push (push) Successful in 1m6s

Surfaces features that landed after the last big docs pass: per-ride
history pages, Fast Lane wait times, outage shading on the today chart,
Tier-5 wait-time sampler, production-hardening pieces (rate limiter,
structured logger, env validation, graceful shutdown), and the new
rides + ride_wait_samples tables. Also corrects the weather-delay rule
to match the "open" vs "closing" gate now in rides.ts.
This commit is contained in:
2026-06-02 15:31:50 -04:00
parent 2e9cec0b56
commit f87462385c
5 changed files with 397 additions and 72 deletions
+49 -17
View File
@@ -64,14 +64,15 @@ This starts the Next.js dev server on port 3000 with hot reload. Open [http://lo
### `app/` -- Next.js Pages
Two routes:
Three routes:
- `/` (`app/page.tsx`) -- Home page. Server component that fetches week data from the backend and passes everything to `HomePageClient`.
- `/park/[id]` (`app/park/[id]/page.tsx`) -- Park detail page. Fetches month calendar and live rides in parallel via `Promise.all`.
- `/park/[id]` (`app/park/[id]/page.tsx`) -- Park detail page. Fetches month calendar and live rides in parallel via `Promise.all`. Live rides use `apiFetch({ noStore: true })` to bypass the Next.js Data Cache.
- `/park/[id]/ride/[slug]` (`app/park/[id]/ride/[slug]/page.tsx`) -- Per-ride detail page with Today / 7d / 30d wait-time history. All three tabs render from a single backend response (no client-side range fetches).
Top-level boundaries: `app/error.tsx` (root error UI), `app/not-found.tsx`, `app/park/[id]/error.tsx`, and `app/loading.tsx` (streaming skeleton).
### `components/` -- React Components
10 components, split between server and client:
| Component | Type | Purpose |
|-----------|------|---------|
| `HomePageClient` | Client | Top-level state: coaster filter, auto-refresh, keyboard nav |
@@ -79,11 +80,14 @@ Two routes:
| `MobileCardList` | Server | Mobile card layout (below `lg` breakpoint) |
| `ParkCard` | Server | Individual park card for mobile |
| `ParkMonthCalendar` | Server | Month grid for park detail page |
| `LiveRidePanel` | Client | Live ride list with coaster toggle and wait times |
| `LiveRidePanel` | Client | Live ride list with coaster toggle, Fast Lane toggle, wait times |
| `WeekNav` | Client | Week navigation arrows |
| `Legend` | Server | Color legend for status indicators |
| `EmptyState` | Server | Empty database message |
| `BackToCalendarLink` | Client | Back link using localStorage for last week |
| `charts/WaitTimeTodayChart` | Client | Today's 5-min wait samples with outage shading (Recharts) |
| `charts/WeeklyStatsChart` | Client | 7d / 30d daily aggregate chart (Recharts) |
| `charts/UptimePill` | Client | Compact uptime % badge |
### `lib/` -- Shared Code
@@ -97,24 +101,36 @@ Imported by both frontend and backend:
| `coaster-data.ts` | Static RCDB coaster name sets per park, `getCoasterSet()` |
| `coaster-match.ts` | `normalizeForMatch()`, `isCoasterMatch()` -- fuzzy name matching |
| `queue-times-map.ts` | `QUEUE_TIMES_IDS` -- park ID to Queue-Times park ID mapping |
| `scrapers/sixflags.ts` | Six Flags CloudFront API client -- `scrapeMonth()`, `fetchToday()`, `scrapeRidesForDay()`, rate limiting |
| `api.ts` | `apiFetch<T>()` -- typed fetch helper with `revalidate` or `noStore` option |
| `outage.ts` | `computeOutages()` -- detects contiguous closed-during-hours runs for the today chart |
| `ride-slug.ts` | `slugifyRideName()` -- URL slug used by `/park/[id]/ride/[slug]` and the `rides` table |
| `timezone.ts` | `formatLocalDate()`, `formatLocalTime()` for bucketing samples in a park's IANA tz |
| `scrapers/sixflags.ts` | Six Flags CloudFront operating-hours client -- `scrapeMonth()`, `fetchToday()`, `scrapeRidesForDay()`, rate limiting |
| `scrapers/sixflags-waittimes.ts` | Six Flags Fast Lane wait-times client -- `fetchFastLaneWaits()`, `lookupFastLane()` |
| `scrapers/queuetimes.ts` | Queue-Times.com API client -- `fetchLiveRides()` |
| `scrapers/log.ts` | Shared scraper logger (used by both `sixflags.ts` and `sixflags-waittimes.ts`) |
| `scrapers/types.ts` | `Park`, `DayStatus`, `MonthCalendar`, `ScraperAdapter` interfaces |
### `backend/src/` -- Hono API Server
| File | Purpose |
|------|---------|
| `index.ts` | Entry point -- middleware (CORS, logger), route registration, DB init, scheduler start |
| `db/index.ts` | SQLite connection singleton, schema creation, WAL mode |
| `db/queries.ts` | All SQL queries -- `upsertDay`, `getDateRange`, `getParkMonthData`, `isMonthScraped`, etc. |
| `index.ts` | Entry point -- middleware (request log, CORS, rate limit), route registration, DB init, scheduler start, graceful shutdown |
| `config.ts` | Env-validated config object (`PORT`, `RATE_LIMIT_PER_MIN`, `PARK_HOURS_STALENESS_HOURS`, `NODE_ENV`). Fails fast on bad input. |
| `log.ts` | Structured logger -- emits `[ISO] [LEVEL] [tag] msg key=value` lines. No external dep. |
| `db/index.ts` | SQLite connection singleton, schema for `park_days` / `rides` / `ride_wait_samples`, WAL mode |
| `db/queries.ts` | All SQL queries -- `upsertDay`, `getDateRange`, `isMonthScraped`, `upsertRide`, `getRideBySlug`, `insertSample`, `getRideSamplesForDay`, `getRideDailyAggregates`, `countRideDays`, `getParkDayCount`, `transact` |
| `middleware/rate-limit.ts` | Fixed-window per-IP limiter. Honours `x-forwarded-for` / `x-real-ip`. Returns 429 with `Retry-After`. |
| `routes/calendar.ts` | `/api/calendar/*` -- week and month data with live today merging |
| `routes/parks.ts` | `/api/parks/*` -- park metadata |
| `routes/rides.ts` | `/api/parks/:id/rides` -- live ride status with schedule fallback |
| `routes/rides.ts` | `/api/parks/:id/rides` -- live ride status + Fast Lane join + schedule fallback |
| `routes/ride-history.ts` | `/api/parks/:id/rides/:slug` -- ride detail + today/7d/30d history in one payload |
| `routes/status.ts` | `/api/status` -- health check |
| `routes/scrape.ts` | `/api/scrape/trigger` -- manual scrape |
| `services/scheduler.ts` | Four-tier cron job registration |
| `services/scheduler.ts` | Five-tier cron registration with per-tier `withLatch` concurrency guards; startup-scrape-when-empty check |
| `services/scraper.ts` | Scraping orchestration -- `scrapeToday()`, `scrapeMonths()`, `scrapeFullYear()` |
| `services/wait-sampler.ts` | Tier-5 5-minute sampler -- joins Queue-Times + Fast Lane, writes `ride_wait_samples`, skips weather-delayed parks |
| `services/live-cache.ts` | Shared `TtlCache<T>` instances (`liveRidesCache`, `fastLaneCache`) so the rides route, the ride-history route, and the Tier-5 sampler share warmed upstream data |
| `services/cache.ts` | Generic `TtlCache<T>` class with configurable TTL |
---
@@ -204,19 +220,35 @@ This fetches the raw Six Flags API response for the park and date, displays the
## Testing
Frontend and backend each have their own test suite, both using the Node built-in test runner.
### Frontend tests
```bash
npm test
```
Uses the **Node.js built-in test runner** (`node --test`). Test files live in `tests/`.
Test files live in `tests/`:
**Current test coverage:**
| File | Coverage |
|------|----------|
| `tests/coaster-matching.test.ts` | `isCoasterMatch()` — exact, prefix, compact, conjunction rejection |
| `tests/fast-lane-matching.test.ts` | `lookupFastLane()` — name normalization and Fast Lane join logic |
| `tests/outage-detection.test.ts` | `computeOutages()` — contiguous-closed-run detection for the today chart |
| `tests/ride-slug.test.ts` | `slugifyRideName()` — URL slug generation and stability |
| `tests/timezone-bucketing.test.ts` | `formatLocalDate()` / `formatLocalTime()` — DST-safe park-tz bucketing |
| File | Tests | Coverage |
|------|-------|---------|
| `tests/coaster-matching.test.ts` | 13 cases | Coaster name matching: exact, prefix, compact, conjunction rejection |
### Backend tests
Tests verify the `isCoasterMatch()` function handles edge cases like trademark symbols, possessives, subtitles, space-split brand words, and conjunction-joined compound ride names.
```bash
cd backend && npm test
```
Test files live in `backend/tests/`:
| File | Coverage |
|------|----------|
| `backend/tests/wait-aggregation.test.ts` | SQL aggregation in `getRideDailyAggregates()` — averages, max, uptime, sample count |
---