Commit Graph

13 Commits

Author SHA1 Message Date
Josh Wright
f0faff412c feat: use dateless Six Flags API endpoint for live today data
All checks were successful
Build and Deploy / Build & Push (push) Successful in 17s
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>
2026-04-05 16:54:06 -04:00
6bb35d468f security: add headers, fetch timeouts, Retry-After cap, env validation
All checks were successful
Build and Deploy / Build & Push (push) Successful in 3m50s
- next.config.ts: CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy
- sixflags.ts: cap Retry-After at 5 min; add 15s AbortSignal.timeout()
- queuetimes.ts: add 10s AbortSignal.timeout()
- rcdb.ts: add 15s AbortSignal.timeout()
- lib/env.ts: parseStalenessHours() guards against NaN from invalid env vars
- db.ts + park-meta.ts: use parseStalenessHours() for staleness window config

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 17:13:01 -04:00
766fc296a1 fix: isCoaster typo in top-level rides loop; simplify test structure
All checks were successful
Build and Deploy / Build & Push (push) Successful in 3m0s
- isCoaster → isCoasterMatch on line 109 (missed rename causing runtime crash
  which returned null from fetchLiveRides, breaking the entire ride panel)
- Rewrite test as two flat arrays: SHOULD_MATCH and SHOULD_NOT_MATCH pairs,
  each with the QT name, RCDB name, and park for context

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 15:49:47 -04:00
8324f31972 fix: correct import paths for coaster-match module
Imports must appear before other statements in ES modules.
Also drop the explicit .ts extension from import paths — Next.js
bundler resolves them without it, and the extension after const
was causing the module to silently fail in the app.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 15:46:07 -04:00
9cac86d241 test: add coaster name matching test suite
Extract matching logic into lib/coaster-match.ts (isCoasterMatch + normalizeForMatch)
so it can be imported by both the scraper and tests without duplication.

Add tests/coaster-matching.test.ts covering all known match/false-positive cases:
- Trademark symbols, leading THE, possessives, punctuation
- Subtitle variants in both directions (Apocalypse, New Revolution - Classic)
- Space-split brand words (BAT GIRL vs Batgirl)
- 4D subtitle extension (THE JOKER™ 4D Free Fly Coaster vs Joker)
- False positives: Joker y Harley Quinn, conjunction connectors

Run with: npm test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 15:43:20 -04:00
e9da6f3120 fix: robust coaster matching + dark carnival color scheme
Matching fixes:
- normalize() now strips all non-word/non-space chars via [^\w\s] instead of
  a hand-rolled list, catching !, curly apostrophe (U+2019), and any future edge cases
- Add isCoaster() helper with prefix matching (min 5 chars) to handle subtitle
  mismatches in either direction (e.g. "Apocalypse" vs "Apocalypse the Ride",
  "The New Revolution - Classic" vs "New Revolution")
- Fix top-level rides loop which still used coasterNames.has(normalize()) instead
  of isCoaster() — this was the recurring bug causing top-level rides to miss

UI:
- Dark neutral base (#111) replacing cold navy and muddy purple
- Neon accent palette: hot pink, electric green, vivid yellow, cyan
- Park page max-width 960→1280px, calendar cells 72→96px tall
- Scrollbar accent matches theme

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 15:22:59 -04:00
9700d0bd9a feat: RCDB-backed roller coaster filter with fuzzy name matching
All checks were successful
Build and Deploy / Build & Push (push) Successful in 2m54s
- Add lib/park-meta.ts to manage data/park-meta.json (rcdb_id + coaster lists)
- Add lib/scrapers/rcdb.ts to scrape operating coaster names from RCDB park pages
- discover.ts now seeds park-meta.json with skeleton entries for all parks
- scrape.ts now refreshes RCDB coaster lists (30-day staleness) for parks with rcdb_id set
- fetchLiveRides() accepts a coasterNames Set; isCoaster uses normalize() on both sides
  to handle trademark symbols, 'THE ' prefixes, and punctuation differences between
  Queue-Times and RCDB names — applies correctly to both land rides and top-level rides
- Commit park-meta.json so it ships in the Docker image (fresh volumes get it automatically)
- Update .gitignore / .dockerignore to exclude only *.db files, not all of data/
- Dockerfile copies park-meta.json into image before VOLUME declaration
- README: document coaster filter setup and correct staleness window (72h not 7d)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 13:49:49 -04:00
819e716197 feat: coaster-only toggle on live ride status panel
Queue-Times groups rides into lands (e.g. "Coasters", "Family", "Kids").
Capture that categorisation in LiveRide.isCoaster and surface it as a
toggle in the new LiveRidePanel client component.

- lib/scrapers/queuetimes.ts: add isCoaster: boolean to LiveRide,
  derived from land.name.toLowerCase().includes("coaster")
- components/LiveRidePanel.tsx: client component replacing the old
  inline LiveRideList; adds a "🎢 Coasters only" pill toggle that
  filters the grid; toggle only appears when the park has coaster-
  categorised rides; amber when active, muted when inactive
- app/park/[id]/page.tsx: swap LiveRideList for LiveRidePanel,
  remove now-dead LiveRideList/LiveRideRow functions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 12:56:20 -04:00
e7b72ff95b feat: add live ride status via Queue-Times.com API
All checks were successful
Build and Deploy / Build & Push (push) Successful in 2m51s
Park detail pages now show real-time ride open/closed status and wait
times sourced from Queue-Times.com (updates every 5 min) when a park
is operating. Falls back to the Six Flags schedule API for off-hours
or parks without a Queue-Times mapping.

- lib/queue-times-map.ts: maps all 24 park IDs to Queue-Times park IDs
- lib/scrapers/queuetimes.ts: fetches and parses queue_times.json with
  5-minute ISR cache; returns LiveRidesResult with isOpen + waitMinutes
- app/park/[id]/page.tsx: tries Queue-Times first; renders LiveRideList
  with Live badge and per-ride wait times; falls back to RideList for
  schedule data when live data is unavailable
- README: documents two-tier ride status approach

Attribution: Queue-Times.com (displayed in UI per their API terms)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 12:15:36 -04:00
e48038c399 feat: UI redesign with park detail pages and ride status
Some checks failed
Build and Deploy / Build & Push (push) Failing after 22s
Visual overhaul:
- Warmer color system with amber accent for Today, better text hierarchy
- Row hover highlighting, sticky column shadow on horizontal scroll
- Closed cells replaced with dot (·) instead of "Closed" text
- Regional grouping (Northeast/Southeast/Midwest/Texas & South/West)
- Two-row header with park count badge and WeekNav on separate lines
- Amber "Today" button in WeekNav when off current week
- Mobile card layout (< 1024px) with 7-day grid per park; table on desktop
- Skeleton loading state via app/loading.tsx

Park detail pages (/park/[id]):
- Month calendar view with ← → navigation via ?month= param
- Live ride status fetched from Six Flags API (cached 1h)
- Ride hours only shown when they differ from park operating hours
- Fallback to nearest upcoming open day when today is dropped by API,
  including cross-month fallback for end-of-month edge case

Data layer:
- Park type gains region field; parks.ts exports groupByRegion()
- db.ts gains getParkMonthData() for single-park month queries
- sixflags.ts gains scrapeRidesForDay() returning RidesFetchResult
  with rides, dataDate, isExact, and parkHoursLabel

Removed: CalendarGrid.tsx, MonthNav.tsx (dead code)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 11:53:06 -04:00
91e09b0548 feat: detect passholder preview days and filter plain buyouts
All checks were successful
Build and Deploy / Build & Push (push) Successful in 3m9s
- Buyout days are now treated as closed unless they carry a Passholder
  Preview event, in which case they surface as a distinct purple cell
  in the UI showing "Passholder" + hours
- DB gains a special_type column (auto-migrated on next startup)
- scrape.ts threads specialType through to upsertDay
- debug.ts now shows events, isBuyout, isPassholderPreview, and
  specialType in the parsed result section

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 10:53:05 -04:00
8a68251beb feat: add debug script for inspecting raw API data per park+date
All checks were successful
Build and Deploy / Build & Push (push) Successful in 4m23s
npm run debug -- --park greatadventure --date 2026-07-04

Prints the raw API response for that day alongside the parsed result
so mismatched or missing hours can be traced to their source.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 10:39:37 -04:00
548c7ae09e feat: initial project scaffold with CI/CD and Docker deployment
Next.js 15 + Tailwind CSS v4 week calendar showing Six Flags park hours.
Scrapes the internal CloudFront API, stores results in SQLite.
Includes Dockerfile (Debian/Playwright-compatible), docker-compose, and
Gitea Actions pipeline that builds and pushes to the container registry.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 00:48:09 -04:00