# Stations (Gas/Fuel) Feature — Dispatchable Change Plan
This document is written as an execution plan that can be handed to multiple AI agents to implement in parallel.
## Repo Constraints (Must Follow)
- Docker-first workflow (production builds): validate changes via `make rebuild` and container logs.
- Mobile + Desktop requirement: every UI change must be validated on both `frontend/src/features/stations/pages/StationsPage.tsx` (desktop) and `frontend/src/features/stations/mobile/StationsMobileScreen.tsx` (mobile).
- Never expose the Google Maps API key to the browser or logs.
## Scope
1. Fix broken station photo rendering on stations UI after the “hide Google API key” change.
2. Add navigation links for saved/favorite stations:
- “Navigate in Google” (Google Maps)
- “Navigate in Apple Maps” (Apple Maps)
- “Navigate in Waze” (Waze)
## Bug: Station Photos Not Displaying
### Current Implementation (What Exists Today)
- Frontend cards render an `
` via MUI `CardMedia` when `station.photoReference` is present:
- `frontend/src/features/stations/components/StationCard.tsx`
- URL generation: `frontend/src/features/stations/utils/photo-utils.ts` → `/api/stations/photo/:reference`
- Backend exposes a proxy endpoint that fetches the Google Places photo (server-side, using the secret key):
- Route: `GET /api/stations/photo/:reference`
- `backend/src/features/stations/api/stations.routes.ts`
- `backend/src/features/stations/api/stations.controller.ts`
- Google client: `backend/src/features/stations/external/google-maps/google-maps.client.ts` (`fetchPhoto`)
### Likely Root Cause (Agents Must Confirm)
The photo endpoint is protected by `fastify.authenticate`, but `
` requests do not include the Authorization header. This results in `401 Unauthorized` responses and broken images.
Second thing to confirm while debugging:
- Verify what `Station.photoReference` contains at runtime:
- expected: Google `photo_reference` token
- risk: code/docs mismatch where `photoReference` became a URL like `/api/stations/photo/{reference}`, causing double-encoding by `getStationPhotoUrl()`.
### Repro Checklist (Fast Confirmation)
- Open stations page and observe broken images in browser devtools Network:
- `GET /api/stations/photo/...` should show `401` if auth-header issue is the cause.
- Confirm backend logs show JWT auth failure for photo requests.
## Decision: Image Strategy (Selected)
Selected: **Option A1** (keep images; authenticated blob fetch in frontend; photo endpoint remains JWT-protected).
### Option A (Keep Images): Fix Auth Mismatch Without Exposing API Key
#### Option A1 (Recommended): Fetch Photo as Blob via Authenticated XHR
Why: Keeps `/api/stations/photo/:reference` protected (prevents public key abuse), avoids putting JWT in query params, and avoids exposing the Google API key.
Implementation outline:
- Frontend: replace direct `
` usage with an authenticated fetch that includes the JWT (via existing Axios `apiClient` interceptors), then render via `blob:` object URL.
- Add a small component like `StationPhoto` used by `StationCard`:
- `apiClient.get('/stations/photo/:reference', { responseType: 'blob' })`
- `URL.createObjectURL(blob)` for display
- `URL.revokeObjectURL` cleanup on unmount / reference change
- graceful fallback (hide image) on 401/500
- Backend: no route auth changes required.
Tradeoffs:
- Slightly more frontend code, but minimal security risk.
- Must ensure caching behavior is acceptable (browser cache won’t cache `blob:` URLs; rely on backend caching headers + client-side memoization).
#### Option A2 (Simplest Code, Higher Risk): Make Photo Endpoint Public
Why: Restores `
` behavior with minimal frontend work.
Implementation outline:
- Backend: remove `preHandler: [fastify.authenticate]` from `/stations/photo/:reference`.
- Add lightweight protections to reduce abuse (choose as many as feasible without adding heavy deps):
- strict input validation (length/charset) for `reference`
- low maxWidth clamp and no arbitrary URL fetching
- maintain `Cache-Control` header (already present)
- optionally add server-side rate limit (only if repo already uses a rate-limit plugin; avoid introducing new infra unless necessary)
Tradeoffs:
- Anyone can hit `/api/stations/photo/:reference` and spend your Google quota.
### Option B (Remove Images): Simplify Cards
Why: If image delivery adds too much complexity or risk, remove images from station cards.
Implementation outline:
- Frontend: remove `CardMedia` photo block from `StationCard` and any other station photo rendering.
- Leave `photoReference` in API/types untouched for now (or remove later as a cleanup task, separate PR).
- Update any tests that assert on image presence.
Tradeoffs:
- Reduced UX polish, but simplest and most robust.
## Feature: Navigation Links on Saved/Favorite Stations
### UX Requirements
- On saved station UI (desktop + mobile), provide 3 explicit navigation options:
- Google Maps
- Apple Maps
- Waze
- “Saved/favorite” is interpreted as “stations in the Saved list”; favorites are a subset.
### URL Construction (Preferred)
Use coordinates if available; fall back to address query if not.
- Google Maps:
- Preferred: `https://www.google.com/maps/dir/?api=1&destination=LAT,LNG&destination_place_id=PLACE_ID`
- Fallback: `https://www.google.com/maps/search/?api=1&query=ENCODED_QUERY`
- Apple Maps:
- Preferred: `https://maps.apple.com/?daddr=LAT,LNG`
- Fallback: `https://maps.apple.com/?q=ENCODED_QUERY`
- Waze:
- Preferred: `https://waze.com/ul?ll=LAT,LNG&navigate=yes`
- Fallback: `https://waze.com/ul?q=ENCODED_QUERY&navigate=yes`
Important: some saved stations may have `latitude/longitude = 0` if cache miss; treat `(0,0)` as “no coordinates”.
### UI Placement Recommendation
- Desktop saved list: add a “Navigate” icon button that opens a small menu with the 3 links (cleaner than inline links inside `ListItemText`).
- File: `frontend/src/features/stations/components/SavedStationsList.tsx`
- Mobile bottom sheet (station details): add a “Navigate” section with the same 3 links (buttons or list items).
- File: `frontend/src/features/stations/mobile/StationsMobileScreen.tsx`
## Work Breakdown for Multiple Agents
### Agent 1 — Confirm Root Cause + Backend Adjustments (If Needed)
Deliverables:
- Confirm whether photo requests return `401` due to missing Authorization.
- Confirm whether `photoReference` is a raw reference token vs a URL string.
- Implement backend changes only if Option A2 is chosen.
Files likely touched (Option A2 only):
- `backend/src/features/stations/api/stations.routes.ts` (remove auth preHandler on photo route)
- `backend/src/features/stations/api/stations.controller.ts` (add stricter validation; keep cache headers)
- `backend/src/features/stations/docs/API.md` (update auth expectations for photo endpoint)
### Agent 2 — Frontend Photo Fix (Option A1) OR Photo Removal (Option B)
Deliverables:
- Option A1: implement authenticated blob photo loading for station cards.
- Option B: remove station photos from cards cleanly (no layout regressions).
Files likely touched:
- `frontend/src/features/stations/components/StationCard.tsx`
- Option A1:
- Add `frontend/src/features/stations/components/StationPhoto.tsx` (or similar)
- Potentially update `frontend/src/features/stations/utils/photo-utils.ts`
- Add unit tests under `frontend/src/features/stations/__tests__/`
### Agent 3 — Navigation Links for Saved Stations (Desktop + Mobile)
Deliverables:
- Create a single URL-builder utility with tests.
- Add a “Navigate” menu/section in saved stations UI (desktop + mobile).
Files likely touched:
- `frontend/src/features/stations/utils/` (new `navigation-links.ts`)
- `frontend/src/features/stations/components/SavedStationsList.tsx`
- `frontend/src/features/stations/mobile/StationsMobileScreen.tsx`
- Optional: reuse in `frontend/src/features/stations/components/StationCard.tsx` (only if product wants it outside Saved)
### Agent 4 — Tests + QA Pass (Update What Breaks)
Deliverables:
- Update/extend tests to cover:
- navigation menu/links present for saved stations
- photo rendering behavior per chosen option
- Ensure both desktop and mobile flows still pass basic E2E checks.
Files likely touched:
- `frontend/cypress/e2e/stations.cy.ts`
- `frontend/src/features/stations/__tests__/components/StationCard.test.tsx`
- New tests for `navigation-links.ts`
## Acceptance Criteria
- Station photos render on station cards via Option A1 without exposing Google API key (no `401` responses for photo requests in Network).
- Saved stations show 3 navigation options (Google, Apple, Waze) on both desktop and mobile.
- No lint/test regressions; container build succeeds.
## Validation (Container-First)
- Rebuild and watch logs: `make rebuild` then `make logs`
- Optional focused logs: `make logs-frontend` and `make logs-backend`
- Run feature tests where available (prefer container exec):
- Backend: `docker compose exec mvp-backend npm test -- features/stations`
- Frontend: `docker compose exec mvp-frontend npm test -- stations`
- E2E: `docker compose exec mvp-frontend npm run e2e`