Files
SixFlagsSuperCalendar/docs/DEVELOPMENT.md
T
josh 6f893b909f
Build and Deploy / Build & Push (push) Successful in 1m10s
docs: fix stale Docker paths and park region counts
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>
2026-04-24 09:37:02 -04:00

8.8 KiB

Development

See also: Architecture | Operations | API Reference

Prerequisites

  • Node.js 22+ and npm
  • No database tools needed (SQLite is auto-created by the backend)
  • No Docker needed for local development

Setup

# Clone the repository
git clone <repo-url>
cd SixFlagsSuperCalendar

# Install frontend dependencies
npm install

# Install backend dependencies
cd backend && npm install && cd ..

Running Locally

The project requires two terminals -- one for the backend, one for the frontend.

Terminal 1: Backend

cd backend
npm run dev

This starts the Hono API server on port 3001 using tsx (TypeScript runtime with watch mode). On first run:

  • Creates an empty SQLite database at backend/data/parks.db
  • Registers the four-tier cron scheduler
  • The schedulers will populate data automatically over time, or you can trigger a manual scrape immediately:
curl -X POST http://localhost:3001/api/scrape/trigger?scope=full

Terminal 2: Frontend

npm run dev

This starts the Next.js dev server on port 3000 with hot reload. Open http://localhost:3000.

Navigation:

  • Use the / buttons to navigate weeks, or pass ?week=YYYY-MM-DD in the URL
  • Click any park name to open its detail page with month calendar and ride status

Project Structure Walkthrough

app/ -- Next.js Pages

Two 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.

components/ -- React Components

10 components, split between server and client:

Component Type Purpose
HomePageClient Client Top-level state: coaster filter, auto-refresh, keyboard nav
WeekCalendar Server Desktop 7-column table with region groupings
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
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

lib/ -- Shared Code

Imported by both frontend and backend:

File Purpose
types.ts Core DayData interface
env.ts getTodayLocal() (3 AM switchover), isWithinOperatingWindow(), getOperatingStatus(), parseStalenessHours()
parks.ts All 24 park definitions, PARK_MAP, groupByRegion()
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
scrapers/queuetimes.ts Queue-Times.com API client -- fetchLiveRides()
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.
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/status.ts /api/status -- health check
routes/scrape.ts /api/scrape/trigger -- manual scrape
services/scheduler.ts Four-tier cron job registration
services/scraper.ts Scraping orchestration -- scrapeToday(), scrapeMonths(), scrapeFullYear()
services/cache.ts Generic TtlCache<T> class with configurable TTL

Adding a New Park

Adding a park requires changes to three files. The park will be automatically picked up by the scheduler, the API, and the frontend.

1. lib/parks.ts

Add an entry to the PARKS array:

{
    id: "newpark",           // URL-safe unique identifier
    apiId: 123,              // Six Flags CloudFront API park ID
    name: "Six Flags New Park",
    shortName: "New Park",
    chain: "sixflags",
    slug: "newpark",         // Should match sixflags.com URL path
    region: "Midwest",      // One of: Northeast, Southeast, Midwest, Texas & South, West & International
    location: {
        lat: 40.0,
        lng: -80.0,
        city: "Anytown",
        state: "OH",
    },
    timezone: "America/New_York",  // IANA timezone
    website: "https://www.sixflags.com",
},

Finding the API ID: Use the debug script or inspect network requests on the Six Flags website. The apiId is the numeric park identifier in the CloudFront API URL.

2. lib/queue-times-map.ts

Add the Queue-Times.com park ID mapping:

export const QUEUE_TIMES_IDS: Record<string, number> = {
    // ... existing mappings
    newpark: 456,  // Queue-Times park ID
};

Finding the Queue-Times ID: Browse queue-times.com, navigate to the park, and note the numeric ID in the URL.

3. lib/coaster-data.ts

Add the coaster name set:

export function getCoasterSet(parkId: string): Set<string> | null {
    // ... existing cases
    case "newpark":
        return new Set([
            normalizeForMatch("Coaster Name One"),
            normalizeForMatch("Coaster Name Two"),
        ]);
}

Finding coaster names: Look up the park on RCDB (Roller Coaster Database). List all operating roller coasters. Names should be the official RCDB names before normalization.


Debug Script

Inspect raw API data and parsed output for any park and date:

npm run debug -- --park <parkId> --date <YYYY-MM-DD>

Example:

npm run debug -- --park kingsisland --date 2026-06-15

This fetches the raw Six Flags API response for the park and date, displays the parsed result, and saves the raw JSON to the debug/ directory for inspection. Useful for:

  • Investigating API response format changes
  • Debugging parsing issues for specific parks/dates
  • Verifying that a park's apiId is correct

Testing

npm test

Uses the Node.js built-in test runner (node --test). Test files live in tests/.

Current test coverage:

File Tests Coverage
tests/coaster-matching.test.ts 13 cases Coaster name matching: exact, prefix, compact, conjunction rejection

Tests verify the isCoasterMatch() function handles edge cases like trademark symbols, possessives, subtitles, space-split brand words, and conjunction-joined compound ride names.


Code Conventions

TypeScript

  • Strict mode enabled in both tsconfig.json files
  • Frontend uses bundler module resolution with @/* path alias
  • Backend uses CommonJS modules with @lib/* alias resolving to ../lib/*

Styling

  • Inline styles via style={{}} props for most component styling
  • Tailwind CSS v4 for responsive utilities (hidden lg:block, sm:flex, px-4 sm:px-6)
  • Theme defined via @theme {} block and CSS custom properties in app/globals.css
  • No CSS modules, no styled-components, no component library

Code Organization

  • Shared types and utilities live in lib/ and are imported by both frontend and backend
  • No component library -- all UI is built from scratch
  • Backend uses tsx for runtime TypeScript execution (no build step in development)

Building Docker Images Locally

# Build the web image
docker build --target web -t sixflagssupercalendar:web .

# Build the backend image
docker build --target backend -t sixflagssupercalendar:backend .

# Run locally with Docker Compose
docker compose up -d

# Or run individual containers
docker run -d -p 3001:3001 -v park_data:/app/backend/data -e TZ=America/New_York sixflagssupercalendar:backend
docker run -d -p 3000:3000 -e BACKEND_URL=http://host.docker.internal:3001 sixflagssupercalendar:web

When running individual containers outside of Docker Compose, use host.docker.internal instead of backend for the BACKEND_URL, since Docker's internal DNS won't resolve service names without Compose.