Two refinements after the previous label change:
1. Outage labels rendered at position="top" were clipping against the
chart's 8px top margin. Bumped to 24px so #N · Hh Mm sits above the
band fully visible.
2. Fast Lane line was only rendered when the ride's metadata flag
has_fast_lane was true. Some rides report Fast Lane waits without
getting flagged, so we now also render the line whenever today's
samples carry any non-null fastLaneMinutes — catches rides that are
walk-on all day with a flat line at 0.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The outage marker now reads "#N · 1h 28m" instead of just "#N" so the
duration is visible at a glance without hovering. Positioned above the
band ("position: top") rather than inside it — when the label string is
wider than the band, Recharts' insideTop placement silently drops the
ReferenceArea rect; placing the label above sidesteps that.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three usability fixes after a day of using the ride detail page.
1. Six Flags is now the primary source for regular wait times. SF's
/wait-times endpoint reports regular waits alongside Fast Lane, and it
updates more promptly than Queue-Times around park-open. The sampler
and the live /rides + ride-history routes all prefer SF's regularWaittime
when its createdDateTime is non-empty; Queue-Times remains the fallback
and the authoritative isOpen source.
2. The today chart's Fast Lane line now stays visible when its value is 0
(walk-on). Y-axis bottom padding ensures the line sits clearly above the
X-axis frame instead of being clipped against it. The tooltip shows
"walk-on" instead of "0 min" for that case.
3. Outages are now explicit on the chart instead of just being gaps.
computeOutages walks today's samples to find contiguous closed runs and
numbers them chronologically. Each outage renders as a translucent pink
ReferenceArea with a "#N" label. The custom tooltip detects when the
cursor is over an outage span and shows "Outage #N — Hh Mm" (e.g.
"Outage #2 — 1h 28m") in place of the wait/Fast Lane rows.
Includes a seed-test-samples.ts dev script for eyeballing the chart with
synthetic outage data.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The home page no longer reads ?week=YYYY-MM-DD from the URL. Selected week
lives in the tcWeek cookie, set via a server action that revalidates the
home page so the next render reflects it. The URL stays at "/" regardless
of which week the user is viewing.
WeekNav prev/next/today buttons (and the arrow-key bindings) call the
server action directly — no router.refresh dance, no client-side cookie
write. BackToCalendarLink drops its localStorage-based href reconstruction
and just links to "/" since the cookie already remembers the right week
across navigations.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two related polish fixes for the ride detail page:
1. Wait-time chart x-axis now uses Intl.DateTimeFormat with no timezone
argument, so an Eastern-time user viewing a Pacific park sees ET on
the axis. Backend now sends recorded_at (UTC) alongside local_time.
2. Ride-history endpoint now applies the same operating-window gate the
/rides route uses. Queue-Times keeps reporting yesterday's last wait
with isOpen=true overnight, which made the "Right now" pill show a
live wait time when the park was actually closed.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds a cron-driven sampler that snapshots Queue-Times waits and Six Flags
Fast Lane data every 5 minutes into a new ride_wait_samples table, and a
clickable per-ride detail page at /park/[id]/ride/[slug] with Today / 7d /
30d Recharts views plus a 30d uptime pill. Rides are keyed by Queue-Times'
stable qt_ride_id so renames don't fragment history. Samples store
pre-bucketed local_date and local_time in the park's IANA timezone so
aggregations are pure SQL and DST-safe.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Join Fast Lane waits from the Six Flags /wait-times endpoint onto
Queue-Times rides by name. A new toggle on the live ride panel swaps
the shown wait to the Fast Lane number; regular waits and open status
still come from Queue-Times.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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>
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>
Removed flex:1 from left side so ride count has a real minimum width to
trigger wrapping. Added whiteSpace:nowrap so flexbox knows when to wrap it.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Padding on the parent main element doesn't work with overflow:auto — the
fix belongs on the scrollable div wrapping the table itself.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Increased pill vertical padding (3px→5px) and internal line gaps (1-2px→2-3px)
so the stacked hours/timezone text feels less cramped.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Open parks get a colored left border (green/amber/blue) instead of a dot.
Region headers lose their accent line; distinguished by "— REGION —" format
with higher-contrast text instead.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Dot turns blue (instead of green) during weather delay on homepage
- Mobile card "Open today" badge becomes blue "⛈ Weather Delay"
- Park page LiveRidePanel shows a blue "⛈ Weather Delay — all rides currently closed" badge instead of "Not open yet — check back soon"
- Added --color-weather-* CSS variables (blue palette)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Park open indicator now derives from scheduled hours, not ride counts.
Parks with queue-times coverage but 0 open rides (e.g. storm) show a
"⛈ Weather Delay" notice instead of a ride count on both desktop and mobile.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The actual overflow fix was removing whiteSpace:nowrap from the td.
With that gone, 240px is sufficient and content wraps naturally when tight.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Column uses clamp(220px, 22%, 280px) — scales on small screens, caps at
280px on large ones. Park name gets whiteSpace:nowrap so it stays one line.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause was a hardcoded 240px column width + whiteSpace:nowrap that
prevented content from ever fitting on smaller displays. Now uses 25%
width so the column scales with the viewport, removed nowrap so text
wraps naturally when squeezed, and reverted clamp() back to fixed sizes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removed flex:1/minWidth:0 from name span so dot stays snug to the right
of the park name. Removed flexShrink:0 from ride count so both sides can
compress. All text uses clamp() to scale proportionally with viewport.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Park name, city/state, and ride count all use clamp() so they shrink
proportionally on smaller displays without truncating or overflowing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Let the park name side flex-shrink (minWidth:0, flex:1) so the ride count
always fits in the row without overflowing its column.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes the space-between flex layout that pushed the count to the right
edge of a fixed-width column. Now stacks under city/state so it always
fits within the park name column regardless of screen size.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fire two refreshes per park: one at opening time to flip the open indicator,
and one 30s later to pick up queue-times ride counts once the API updates.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
In addition to the 2-minute polling interval, compute milliseconds until
each park's opening time (from hoursLabel + park timezone) and schedule
a setTimeout to fire 30s after opening. This ensures the open indicator
and ride counts appear immediately rather than waiting up to 2 minutes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Uses router.refresh() to re-run server data fetching (ride counts, open
status) without a full page reload. Only runs when viewing the current
week — no need to poll for past/future weeks.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mobile cells (~55px wide) can't fit hours text legibly, so show only a
colored dot when open and a dash when no data. Desktop restores the full
pill (hours + tz abbreviation) via Tailwind responsive classes. Row height
is controlled by a new .park-calendar-grid CSS class: 72px fixed on mobile,
minmax(96px, auto) on sm+, keeping desktop cells from looking cramped.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause: each week row was its own CSS Grid container, so rows
with open-day pills (hours + separate timezone line) grew taller than
closed rows, making the calendar column lines look staggered/slanted.
- Flatten all day cells into a single grid with gridAutoRows: 76
so every row is exactly the same fixed height
- All cells get overflow: hidden so content can never push height
- Compact the status pill to a single line (hours + tz inline,
truncated with ellipsis) — the stacked two-line pill was the
primary height expander on narrow mobile columns
- Row/column border logic moves from week-wrapper divs to individual
cell borderRight / borderBottom properties
- Nav link touch targets: padding 6×14 → 10×16, minWidth: 44px
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Header:
- Hide "X of Y parks open this week" badge on mobile (hidden sm:inline-flex)
— title + Coaster Mode button fit cleanly on a 390px screen
WeekNav:
- Arrow button padding 6px 14px → 10px 16px, minWidth: 44px for proper
touch targets (Apple HIG recommends 44px minimum)
- Date label minWidth 200px → 140px, prevents crowding on small screens
ParkCard:
- Name container: flex: 1, minWidth: 0 so long park names don't push
the status badge off-screen; name wraps naturally instead of overflowing
- Timezone abbreviation: opacity: 0.6 → color: var(--color-text-dim),
semantic dimming instead of opacity for better accessibility
- Passholder label: 0.58rem → 0.65rem (was below WCAG minimum)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Parks in the 1-hour buffer after scheduled close now show amber instead
of green: the dot on the desktop calendar turns yellow, and the mobile
card badge changes from "Open today" (green) to "Closing" (amber).
- getOperatingStatus() replaces isWithinOperatingWindow's inline logic,
returning "open" | "closing" | "closed"; isWithinOperatingWindow now
delegates to it so all callers are unchanged
- closingParkIds[] is computed server-side and threaded through
HomePageClient → WeekCalendar/MobileCardList → ParkRow/ParkCard
- New --color-closing-* CSS variables mirror the green palette in amber
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
HomePageClient writes the current weekStart to localStorage("lastWeek")
whenever it changes. BackToCalendarLink (new client component) reads
that value on mount and builds the href — falling back to "/" if nothing
is stored (e.g. direct link to a park page).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>