# Development > See also: [Architecture](ARCHITECTURE.md) | [Operations](OPERATIONS.md) | [API Reference](API.md) ## Prerequisites - **Node.js 22+** and **npm** - No database tools needed (SQLite is auto-created by the backend) - No Docker needed for local development --- ## Setup ```bash # Clone the repository git clone 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 ```bash 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: ```bash curl -X POST http://localhost:3001/api/scrape/trigger?scope=full ``` ### Terminal 2: Frontend ```bash npm run dev ``` This starts the Next.js dev server on port 3000 with hot reload. Open [http://localhost:3000](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` 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: ```typescript { 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: ```typescript export const QUEUE_TIMES_IDS: Record = { // ... existing mappings newpark: 456, // Queue-Times park ID }; ``` **Finding the Queue-Times ID:** Browse [queue-times.com](https://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: ```typescript export function getCoasterSet(parkId: string): Set | 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)](https://rcdb.com). 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: ```bash npm run debug -- --park --date ``` **Example:** ```bash 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 ```bash 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 ```bash # 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.