---
name: weathr-app-plan
overview: Build a Next.js 16 weather app (`weathr/`) with city search and a shareable per-city weather view (current + 24h + 5-day) powered by OpenWeatherMap, styled with shadcn/ui, deployable to Vercel.
todos:
  - id: shadcn-init
    content: Initialize shadcn/ui and add base components (button, input, card, command, popover, skeleton, separator, sonner)
    status: pending
  - id: cities-fetch-script
    content: Add scripts/fetch-cities.ts that downloads OWM bulk city.list.json.gz and writes a trimmed data/cities.json; wire to npm prebuild
    status: pending
  - id: lib-cities
    content: Create lib/cities.ts (load cities.json once, build lowercased name index, expose searchCities and getCityById)
    status: pending
  - id: lib-openweather
    content: "Create lib/openweather.ts (typed client: getCurrent(lat, lon), getForecast(lat, lon)) and lib/format.ts formatters"
    status: pending
  - id: search-api-route
    content: Implement app/api/search/route.ts using lib/cities.ts (local fuzzy search, no OWM call)
    status: pending
  - id: search-component
    content: Build components/search-command.tsx with debounced fetch to /api/search and router.push(`/city/${id}`)
    status: pending
  - id: home-page
    content: Rewrite app/page.tsx into a clean hero with SearchCommand
    status: pending
  - id: city-page
    content: Implement app/city/[id]/page.tsx (resolve city via lib/cities, parallel weather+forecast fetches), plus loading.tsx and error.tsx
    status: pending
  - id: weather-components
    content: Build WeatherCurrent, WeatherHourly (24h strip from 3h forecast), WeatherForecast (5-day grouped) and WeatherIcon
    status: pending
  - id: layout-metadata
    content: Update app/layout.tsx metadata + dynamic generateMetadata on city page; tidy globals.css for shadcn tokens
    status: pending
  - id: next-config-tracing
    content: Update next.config.ts with outputFileTracingIncludes so data/cities.json is bundled into the search route on Vercel
    status: pending
  - id: readme-deploy
    content: Update README with setup (npm run cities:fetch), env var, and Vercel deploy instructions
    status: pending
isProject: false
---

## Stack & Assumptions

- Next.js 16.2.6 (App Router), React 19, TypeScript, Tailwind v4 — already bootstrapped in [weathr/](weathr/).
- `OPENWEATHERMAP_API_KEY` already in [weathr/.env](weathr/.env).
- shadcn/ui — to install (`npx shadcn@latest init`, components: `button`, `input`, `card`, `command`, `popover`, `skeleton`, `separator`, `sonner`).
- Deployed to Vercel; env var set in project settings.
- Per [weathr/AGENTS.md](weathr/AGENTS.md): consult `node_modules/next/dist/docs/` before writing code (Next.js 16 has breaking changes vs training data).

## Data Source: OpenWeatherMap

Search is powered by the **OWM bulk city list** (not the live Geocoding API), because the user wants `/city/[id]` URLs and Geocoding does not return city IDs.

- Bulk list: `https://bulk.openweathermap.org/sample/city.list.json.gz`
  - Roughly 209k cities, each entry: `{ id, name, state, country, coord: { lat, lon } }`.
  - Static, updated infrequently by OWM.
- Weather endpoints we call (free tier, by lat/lon — resolved locally from the bulk list):
  - Current: `GET /data/2.5/weather?lat=&lon=&units=metric&appid=...`
  - 5-day / 3-hour forecast: `GET /data/2.5/forecast?lat=&lon=&units=metric&appid=...`
    - Source for the "Next 24h (3h steps)" strip (first 8 entries) AND the 5-day daily summary (grouped by date, min/max).

Note: true 1-hour granularity needs the paid One Call 3.0 API — out of scope.

## City List Pipeline

```mermaid
flowchart LR
  Script["scripts/fetch-cities.ts"] -->|downloads gz| OWMBulk[OWM bulk city.list.json.gz]
  Script -->|gunzip + trim fields| Json["data/cities.json"]
  Json -->|loaded once| LibCities["lib/cities.ts (in-memory index)"]
  LibCities --> SearchApi["/api/search"]
  LibCities --> CityPage["/city/[id]"]
```

- `data/cities.json` is **generated, gitignored**. Wired via `prebuild` npm script so Vercel regenerates it on every deploy. Locally: `npm run cities:fetch`.
- Trimming: drop `state` if empty, keep `{ id, name, country, coord }`. Expected size: ~15–20 MB raw, gzipped further by Vercel's edge. Acceptable for a Node serverless function.
- Index: build a `Map<lowercasedName, City[]>` and a prefix array for fuzzy/prefix search. Initialized once per cold start (module-level lazy init).

## Routing & URL Design

- `/` — home: hero + search.
- `/city/[id]` — shareable city view (e.g. `/city/2643743` = London,GB).
- `/api/search?q=...` — server route doing local search over `data/cities.json` via `lib/cities.ts`. No OWM call. Returns top 8 matches `{ id, name, country, lat, lon }`.

## File Structure

```
weathr/
  app/
    layout.tsx              # update metadata, keep Geist fonts
    page.tsx                # rewrite: home with <SearchCommand />
    globals.css             # extend with shadcn theme tokens
    city/[id]/
      page.tsx              # server component, resolves city, parallel weather fetches
      loading.tsx           # skeletons
      error.tsx             # graceful error UI
      not-found.tsx         # unknown id
    api/search/route.ts     # GET handler -> lib/cities.search
  components/
    search-command.tsx      # client: shadcn Command, debounced fetch, router.push
    weather-current.tsx
    weather-hourly.tsx
    weather-forecast.tsx
    weather-icon.tsx
    ui/...                  # shadcn primitives
  lib/
    cities.ts               # load cities.json, build index, search/getById
    openweather.ts          # typed client: getCurrent, getForecast
    format.ts               # temp/wind/time formatters
    utils.ts                # shadcn cn()
  scripts/
    fetch-cities.ts         # downloads + trims OWM bulk -> data/cities.json
  data/
    cities.json             # generated, gitignored
  components.json           # shadcn config
  next.config.ts            # outputFileTracingIncludes for data/cities.json
```

## Data Flow

```mermaid
flowchart LR
  User --> Home["/ (SearchCommand)"]
  Home -->|"debounced q"| SearchApi["/api/search"]
  SearchApi -->|local index| Cities[lib/cities.ts]
  Home -->|"select -> push"| CityPage["/city/[id]"]
  CityPage --> Cities
  Cities -->|"lat, lon"| OWM[OpenWeatherMap]
  CityPage --> Current[WeatherCurrent]
  CityPage --> Hourly[WeatherHourly]
  CityPage --> Forecast[WeatherForecast]
```

## Key Implementation Details

- `scripts/fetch-cities.ts`: pure Node + `tsx`, no extra deps. Uses `fetch` + `zlib.gunzip` to write `data/cities.json`. Idempotent; skips download if file fresh (<7 days).
- `lib/cities.ts`: module-level lazy load (`let index: Index | null = null`); reads `data/cities.json` from disk via `fs.readFileSync` resolved with `path.join(process.cwd(), "data", "cities.json")`. Exposes `searchCities(q, limit=8)` and `getCityById(id)`.
- `lib/openweather.ts`: typed wrapper, `fetch(url, { next: { revalidate: 600 } })` for 10-min cache; reads key from `process.env.OPENWEATHERMAP_API_KEY`.
- City page is a server component: `const city = getCityById(id); if (!city) notFound();` then `Promise.all([getCurrent, getForecast])` with `city.coord.lat/lon`. Renders `WeatherCurrent`, `WeatherHourly` (first 8 entries), `WeatherForecast` (group `forecast.list` by date, min/max).
- `SearchCommand` uses shadcn `Command` + `Popover`; debounce ~200ms with `AbortController`; on select: `router.push(`/city/${id}`)`.
- Units: metric by default (no toggle in v1).
- Error handling: unknown id -> `not-found.tsx`; OWM errors -> `error.tsx` with retry.
- Metadata: dynamic `generateMetadata({ params })` builds title from city name + country.
- `next.config.ts`: add `outputFileTracingIncludes: { "/api/search": ["./data/cities.json"], "/city/[id]": ["./data/cities.json"] }` so Vercel bundles the JSON into both serverless functions.

## Deployment

- Push to GitHub repo `weathr`.
- Import to Vercel; set `OPENWEATHERMAP_API_KEY` in project env (Production + Preview).
- `npm run prebuild` runs `cities:fetch` automatically before `next build`, producing `data/cities.json` inside the build container.

## Out of Scope (v1)

- Favorites / recent searches persistence
- Geolocation auto-detect
- Unit toggle (C/F)
- Air quality, alerts, maps
