Commit Graph
64 Commits
Author SHA1 Message Date
joshandClaude Opus 4.6 3c91d9a453 fix: mount volume at correct path so database survives updates
Build and Deploy / Build & Push (push) Successful in 1m7s
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>
2026-04-24 09:13:59 -04:00
josh deb8e4169b Revert "feat: run full scrape on backend container start"
Build and Deploy / Build & Push (push) Successful in 1m18s
This reverts commit db668c0787.
2026-04-23 23:14:22 -04:00
joshandClaude Opus 4.6 06b911917d fix: use explicit Eastern timezone for day boundary instead of system TZ
Build and Deploy / Build & Push (push) Successful in 2m50s
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>
2026-04-23 23:11:33 -04:00
joshandClaude Opus 4.6 db668c0787 feat: run full scrape on backend container start
Build and Deploy / Build & Push (push) Successful in 1m44s
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>
2026-04-23 22:28:07 -04:00
joshandClaude Opus 4.6 a53e3ffa9f docs: add comprehensive project documentation
Build and Deploy / Build & Push (push) Successful in 1m31s
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>
2026-04-23 22:15:02 -04:00
joshandClaude Opus 4.6 4922dce8ac docs: update README for web + backend architecture
Build and Deploy / Build & Push (push) Successful in 34s
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>
2026-04-23 21:54:45 -04:00
joshandClaude Opus 4.6 c5c9f750a3 chore: update Docker and CI for web + backend architecture
Build and Deploy / Build & Push (push) Successful in 2m10s
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>
2026-04-23 21:49:17 -04:00
joshandClaude Opus 4.6 3815da2d3f refactor: make frontend a pure presentation layer fetching from backend API
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>
2026-04-23 21:43:59 -04:00
joshandClaude Opus 4.6 ccd35c4648 chore: remove old scraper scripts, replaced by backend scheduler
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>
2026-04-23 21:33:34 -04:00
joshandClaude Opus 4.6 70b56158d4 feat: add Hono backend API server with tiered scheduler
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>
2026-04-23 21:32:38 -04:00
joshandClaude Opus 4.6 4652a92c29 refactor: hardcode API IDs and coaster lists, remove Playwright discovery
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>
2026-04-23 21:25:53 -04:00
joshandClaude Opus 4.6 757c2a8d4f chore: gitignore parks.db
Build and Deploy / Build & Push (push) Successful in 1m39s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 21:02:37 -04:00
josh c5dc01b6ff adds unami
Build and Deploy / Build & Push (push) Successful in 8m48s
2026-04-23 21:00:34 -04:00
josh 0009af751f Update README.md
Build and Deploy / Build & Push (push) Successful in 19s
2026-04-05 17:39:24 -04:00
josh 4063ded9ec Update README.md
Build and Deploy / Build & Push (push) Successful in 1m9s
2026-04-05 17:34:18 -04:00
joshandClaude Sonnet 4.6 7456ead430 feat: coaster filter toggle on homepage
Build and Deploy / Build & Push (push) Successful in 1m14s
- 🎢 Coasters button in nav bar (URL-driven: ?coasters=1)
- When active, swaps ride counts for coaster counts per park
- Label switches between "X rides operating" / "X coasters operating"
- Arrow key navigation preserves coaster filter state
- Only shown when coaster data exists in park-meta

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 21:03:00 -04:00
joshandClaude Sonnet 4.6 f1fec2355c polish: ride count copy, open indicator, and badge sizing
Build and Deploy / Build & Push (push) Successful in 1m5s
- "X rides open" → "X rides operating" (desktop + mobile)
- Green glowing dot next to park name when actively operating
- Hours text in calendar cells: larger (0.78rem) and bolder (600)
- Parks open badge: green tint when parks are open, larger text

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 20:54:14 -04:00
joshandClaude Sonnet 4.6 fbf4337a83 feat: park page operating window check; always show ride total
Build and Deploy / Build & Push (push) Successful in 5m54s
- Extract isWithinOperatingWindow() to lib/env.ts (shared)
- Park detail page: always fetch Queue-Times, but force all rides
  closed when outside the ±1h operating window
- LiveRidePanel: always show closed ride count badge (not just when
  some rides are also open); label reads "X rides total" when none
  are open vs "X closed / down" when some are

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 20:43:33 -04:00
joshandClaude Sonnet 4.6 8e969165b4 feat: show live open ride count in park name cell
- Fetch Queue-Times ride counts for parks open today (5min cache)
- Only shown within 1h before open to 1h after scheduled close
- Count displayed on the right of the park name/location cell (desktop)
  and below the open badge (mobile)
- Whole park cell is now a clickable link
- Hover warms the park cell background; no row-wide highlight

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 20:38:12 -04:00
joshandClaude Sonnet 4.6 43feb4cef0 fix: restrict today highlight to date header only
Remove today background/border from data row cells so the yellow
highlight only appears on the day label, not the entire column.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 20:18:19 -04:00
joshandClaude Sonnet 4.6 a87f97ef53 fix: use local time with 3am cutover for today's date
new Date().toISOString() returns UTC, causing the calendar to advance
to the next day at 8pm EDT / 7pm EST. getTodayLocal() reads local
wall-clock time and rolls back one day before 3am so the calendar
stays on the current day through the night.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 20:15:52 -04:00
joshandClaude Sonnet 4.6 fdea8443fb fix: restore arrow key week navigation; improve empty state copy
Build and Deploy / Build & Push (push) Successful in 1m6s
- WeekNav: add keydown listener for ArrowLeft/ArrowRight week nav
- EmptyState: replace dev-facing "No data scraped yet" + npm commands
  with customer-friendly "Schedule not available yet" message

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 20:07:43 -04:00
joshandClaude Sonnet 4.6 6bb35d468f security: add headers, fetch timeouts, Retry-After cap, env validation
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
joshandClaude Sonnet 4.6 e1b0e5e44d chore: remove redundant 'done' line from discover output
Build and Deploy / Build & Push (push) Successful in 3m27s
The → ID line already confirms success.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 16:59:11 -04:00
joshandClaude Sonnet 4.6 edd044a1f8 docs: fix volume name to root_park_data
Build and Deploy / Build & Push (push) Successful in 56s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 16:54:36 -04:00
joshandClaude Sonnet 4.6 eeed646203 docs: rewrite deployment section for two-image setup
Build and Deploy / Build & Push (push) Successful in 52s
- Document web/:web and scraper/:scraper image split
- Full first-time setup flow: pull → discover → RCDB IDs → scrape → up
- Document all scraper env vars (TZ, PARK_HOURS_STALENESS_HOURS, COASTER_STALENESS_HOURS)
- Add manual scrape commands, update workflow, add npm test to dev section

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 16:48:22 -04:00
joshandClaude Sonnet 4.6 eeb4a649c1 feat: split web and scraper into separate Docker images
Build and Deploy / Build & Push (push) Successful in 3m4s
- Dockerfile: replace single runner stage with web + scraper named targets
  - web: Next.js standalone only — no playwright, tsx, or scripts
  - scraper: scripts/lib/node_modules/playwright only — no Next.js output
- docker-compose.yml: each service pulls its dedicated image tag
- .gitea/workflows/deploy.yml: build both targets on push to main
- lib/db.ts: STALE_AFTER_MS reads PARK_HOURS_STALENESS_HOURS env var (default 72h)
- lib/park-meta.ts: COASTER_STALE_MS reads COASTER_STALENESS_HOURS env var (default 720h)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 16:40:31 -04:00
joshandClaude Sonnet 4.6 766fc296a1 fix: isCoaster typo in top-level rides loop; simplify test structure
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
joshandClaude Sonnet 4.6 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
joshandClaude Sonnet 4.6 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
joshandClaude Sonnet 4.6 dc4fbeb7ec chore: ignore .claude/ directory
Build and Deploy / Build & Push (push) Successful in 3m36s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 15:26:00 -04:00
joshandClaude Sonnet 4.6 c1e42d6aa1 fix: truncate long ride names with tooltip instead of wrapping
Restores fixed-height ride rows with ellipsis truncation.
Adds title attribute so hovering shows the full name in a native tooltip.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 15:25:01 -04:00
joshandClaude Sonnet 4.6 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
joshandClaude Sonnet 4.6 bad366d5ea revert: remove park-meta.json copy from Dockerfile
Build and Deploy / Build & Push (push) Successful in 3m34s
Volume mount hides image-layer files on existing deployments anyway.
Deploy manually: curl -o park-meta.json <gitea-raw-url>

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 14:02:39 -04:00
joshandClaude Sonnet 4.6 9700d0bd9a feat: RCDB-backed roller coaster filter with fuzzy name matching
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
joshandClaude Sonnet 4.6 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
joshandClaude Sonnet 4.6 da083c125c feat: automated nightly scraper + housekeeping
Build and Deploy / Build & Push (push) Successful in 3m11s
Scraper automation (docker-compose):
- Add scraper service to docker-compose.yml using the same image and
  shared park_data volume; overrides CMD to run scrape-schedule.sh
- scripts/scrape-schedule.sh: runs an initial scrape on container start,
  then sleeps until 3:00 AM (respects TZ env var) and repeats nightly;
  logs timestamps and next-run countdown; non-fatal on scrape errors

Staleness window: 7 days → 72 hours in lib/db.ts so data refreshes
more frequently with the automated schedule in place

Remove favicon: delete app/icon.tsx and public/logo.svg

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 12:47:14 -04:00
joshandClaude Sonnet 4.6 20f1058e9e fix: protect today's record from scrape overwrites
Change upsertDay WHERE guard from >= to > date('now') so today is
treated identically to past dates. Once a park's operating day starts
the API drops that date, making it appear closed. The record written
when the date was still future is the correct one and must be preserved.

Only strictly future dates (> today) are now eligible for upserts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 12:42:03 -04:00
joshandClaude Sonnet 4.6 5ea2dafc0e fix: preserve historical day records, skip scraping past months
upsertDay: add WHERE park_days.date >= date('now') to the ON CONFLICT
DO UPDATE clause. Past dates now behave as INSERT OR IGNORE — new rows
are written freely but existing historical records are never overwritten.
The API stops returning elapsed dates, so the DB row is the permanent
source of truth for any date that has already occurred.

isMonthScraped: months whose last day is before today are permanently
skipped regardless of staleness age. The API has no data for past months
so re-scraping them wastes API calls and cannot improve the records.
Current and future months continue to use the 7-day staleness window.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 12:37:27 -04:00
joshandClaude Sonnet 4.6 d4c8046515 improve: redesign mobile card layout for usability
Build and Deploy / Build & Push (push) Successful in 3m24s
Replace the cramped 7-column day grid with a clean open-days list.
Each card now shows:
- Park name + "Open today" / "Closed today" badge in the header
- One row per open day (Today, Monday, Friday...) with full hours
- Today row highlighted in amber; passholder days labeled inline
- Whole card is a tap target linking to the park detail page

Also:
- Hide the legend below sm breakpoint (not needed on phones)
- Reduce horizontal padding to 16px on mobile (was 24px)
- Tighten MobileCardList vertical spacing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 12:33:02 -04:00
joshandClaude Sonnet 4.6 b4183507a8 chore: remove logo from header and README
Build and Deploy / Build & Push (push) Successful in 3m3s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 12:28:39 -04:00
joshandClaude Sonnet 4.6 81ff6ea659 feat: roller coaster logo and working favicon
- public/logo.svg: amber coaster silhouette (lift hill + vertical loop +
  camelback) on transparent background; used in header and README
- app/icon.tsx: PNG favicon via Next.js ImageResponse (works in all
  browsers including Safari); renders simplified hill + loop on dark
  rounded-square background
- app/page.tsx: logo img added next to "Thoosie Calendar" title in header
- README.md: logo displayed at top of document
- Remove app/icon.svg (replaced by icon.tsx → PNG)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 12:26:45 -04:00
joshandClaude Sonnet 4.6 8b1c8dcb29 chore: rebrand to Thoosie Calendar, add favicon
- Rename app title and header from "Six Flags Calendar" to "Thoosie Calendar"
- Update layout metadata title and description
- Update README title
- Add app/icon.svg favicon: amber T on dark background, picked up
  automatically by Next.js App Router

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 12:18:22 -04:00
joshandClaude Sonnet 4.6 e7b72ff95b feat: add live ride status via Queue-Times.com API
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
joshandClaude Sonnet 4.6 ba8cd46e75 docs: update README with current feature set, remove CI/CD section
- Add park detail pages and ride status to description
- Replace flat park list with regional table
- Add debug command documentation
- Remove CI/CD section (Gitea Actions config docs)
- Clean up deployment section

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 12:03:32 -04:00
joshandClaude Sonnet 4.6 b0bbb4d465 fix: resolve ESLint errors blocking Docker build
Build and Deploy / Build & Push (push) Successful in 2m45s
- Remove unused formatDate() from park page (replaced by formatShortDate)
- Remove unused date prop from DayCell component
- Change let to const for cellBorderRadius in ParkCard
- Change let to const for bg in ParkMonthCalendar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 11:55:17 -04:00
joshandClaude Sonnet 4.6 e48038c399 feat: UI redesign with park detail pages and ride status
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
joshandClaude Sonnet 4.6 5f82407fea feat: show full park names in calendar (e.g. Six Flags Great Adventure)
Build and Deploy / Build & Push (push) Successful in 3m14s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 11:02:28 -04:00
joshandClaude Sonnet 4.6 e2d90b3c5b feat: hide parks with no open days in the current week
Build and Deploy / Build & Push (push) Has been cancelled
Header subtitle now shows "X of 24 parks open" for context.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 11:00:53 -04:00
joshandClaude Sonnet 4.6 91e09b0548 feat: detect passholder preview days and filter plain buyouts
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
joshandClaude Sonnet 4.6 7c28d8f89f feat: save debug output to debug/{parkId}_{date}.txt
Build and Deploy / Build & Push (push) Successful in 5m13s
Creates debug/ folder (txt files gitignored). Output is printed to
the terminal and written to the file simultaneously.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 10:45:55 -04:00
joshandClaude Sonnet 4.6 8a68251beb feat: add debug script for inspecting raw API data per park+date
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
joshandClaude Sonnet 4.6 5bef17aa41 refactor: one-line-per-park output with inline month progress
Build and Deploy / Build & Push (push) Successful in 3m11s
Each park prints a row of █ (fetched) and · (skipped) as months
complete, then ends with open day count, "up to date", or error count.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 10:31:11 -04:00
joshandClaude Sonnet 4.6 bc5777c9e2 refactor: scrape all 12 months per park before moving to next park
Build and Deploy / Build & Push (push) Successful in 2m54s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 10:25:20 -04:00
joshandClaude Sonnet 4.6 09dcfde16e ci: use docker/metadata-action and build-push-action
Build and Deploy / Build & Push (push) Successful in 14m31s
Cleaner pipeline using official Docker actions. Supports semver tags
alongside latest. Registry driven by vars.REGISTRY variable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 01:37:09 -04:00
joshandClaude Sonnet 4.6 2bc10c98d9 chore: remove build from compose, pull from registry only
Build and Deploy / build-and-push (push) Has been cancelled
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 01:26:45 -04:00
joshandClaude Sonnet 4.6 cd3c3fab7e fix: add public/ directory so Docker COPY doesn't fail
Build and Deploy / build-and-push (push) Has been cancelled
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 01:15:54 -04:00
joshandClaude Sonnet 4.6 12af38c5f6 chore: hardcode registry URL and clean up readme
Build and Deploy / build-and-push (push) Failing after 38s
docker-compose no longer needs REGISTRY_URL env var.
README now uses the actual registry host throughout.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 01:13:16 -04:00
joshandClaude Sonnet 4.6 a2b59b2f8d fix: derive registry host from gitea.server_url instead of unset variable
Build and Deploy / build-and-push (push) Failing after 1m7s
REGISTRY_URL var was empty so docker login fell through to Docker Hub.
Now strips protocol from gitea.server_url to get the registry hostname —
no manual variable needed. docker-compose defaults to the known host.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 01:10:20 -04:00
josh 4118d31df8 Update README.md
Build and Deploy / build-and-push (push) Failing after 6s
2026-04-04 01:00:07 -04:00
josh c32967ebf1 Update README.md 2026-04-04 00:56:39 -04:00
joshandClaude Sonnet 4.6 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
josh af6aa29474 chore: add project config 2026-04-03 22:06:54 -04:00
josh 6a139597ad docs: initialize project 2026-04-03 22:05:47 -04:00