Darshan Sanjel
  1. Portfolio
  2. Projects
  3. Trail Window
Case study

A weather planner that shows its math, with a locked-down AI briefing.

Trail Window ranks every daylight window in the next five days for a hike, ride or run. The AI trail briefing sits behind a Cloudflare Worker built like an API you would actually put on the internet.

Status
Live
Front end
JavaScript · Leaflet
Data
Open-Meteo
Back end
Cloudflare Workers

01The problem

Weather apps tell you the forecast, not when to go. I wanted a tool that answers "when is the best time this week for this route?" and explains why, and I wanted its AI feature to be safe to leave running on the public internet.

Try the live app · Read the source

02Setup

  • Ranks every daylight window in the next five days using rain, wind and feels-like penalties, and shows the scoring math instead of hiding it.
  • Parses GPX files in the browser and forecasts conditions at each checkpoint for the hour you'd reach it.
  • Maps with Leaflet; forecasts from Open-Meteo.
  • The AI trail briefing goes through a Cloudflare Worker with a server-side API key, a CORS allowlist, strict input validation, rate limiting, and a template fallback.
  • Unit tests plus Playwright responsive checks at 390, 768 and 1280 px.

03Architecture

Trail Window architecture The static web app in the browser parses GPX files locally and fetches forecasts from Open-Meteo. For the AI briefing it calls a Cloudflare Worker, which checks CORS, validates input and rate-limits before calling the AI model with a server-side key, and falls back to a template if needed. Your browser Trail Window app static site · JavaScript Leaflet map · scoring GPX file parsed in the browser Internet Open-Meteo forecast API Cloudflare Worker CORS allowlist input validation rate limiting API key stays here template fallback AI model briefing checkpoints forecast request briefing
The only secret, the AI API key, lives in the Worker. The browser never sees it, and the Worker rejects anything that is not from the app or not shaped like a real request.

04What broke and how I fixed it

Threats the Worker is built against

  • Leaked API key: the key is a server-side secret in the Worker, never shipped to the browser.
  • Other sites using my endpoint: a CORS allowlist only accepts requests from the app's origin.
  • Malformed or oversized input: strict validation rejects anything that isn't a well-formed briefing request.
  • Abuse and runaway cost: rate limiting caps how often the endpoint can be called.
  • Model outage: a template fallback still returns a useful briefing.

05What I'd do next

  • Let people tune the penalty weights (some hikers don't mind wind) and save their preferences.
  • Add Worker request logging and alerting so abuse shows up before the rate limit does.