2-week sprint to deployable walking-isochrone MVP. Each task is a self-contained prompt for an agentic loop: goal, files, definition of done.
Conventions:
- Each task lives on its own branch and ships as a PR with reviewer-subagent comments.
- Tasks are ordered by dependency. Strict left-to-right; parallelism is noted where it's safe.
- A task is "done" only when CI is green on the PR and the listed acceptance bullets pass.
- Goal. Empty Turborepo with
apps/web(Next.js 14 App Router, TS strict, Tailwind, shadcn/ui) andpackages/providers(TS package, no runtime deps yet). - Files. Root
package.json,pnpm-workspace.yaml,turbo.json,tsconfig.base.json,apps/web/*,packages/providers/*. - DoD.
pnpm devboots the app atlocalhost:3000and renders a placeholder.pnpm typecheckandpnpm lintpass. README has setup steps.
- Goal. GitHub Actions workflow runs lint, typecheck, vitest. Vercel project linked; PR deploys preview URLs.
- Files.
.github/workflows/ci.yml,vercel.jsonif needed,.env.example. - DoD. A throwaway PR shows a green CI run and a Vercel preview comment.
- Goal. Define
IsochroneProvider,PoiProvider,TileProvider,TransitDataProviderinterfaces with Zod schemas for inputs/outputs. - Files.
packages/providers/src/{isochrone,poi,tile,transit}/types.ts. - DoD. All four interfaces compile, are exported from the package root, and have a fixture-fed in-memory
Fake*Providerused in tests.
- Goal. Implement
OrsIsochroneProvideragainst the ORS isochrones endpoint. Server-side only; never imported into client components. - Files.
packages/providers/src/isochrone/ors.ts, fixture in__fixtures__/ors-walking-15min.json, vitest spec. - DoD. Unit tests pass against the fixture without making real network calls. A small contract test asserts the polygon is valid GeoJSON. README shows how to set
ORS_API_KEY.
- Goal.
StadiaTileProviderreturning a MapLibre style URL;<IlsochroneMap>component usingreact-map-gl/maplibrerendering Tel Aviv. - Files.
packages/providers/src/tile/stadia.ts,apps/web/src/components/map/*,apps/web/src/app/page.tsx. - DoD.
/renders a working map of Tel Aviv with attribution. Tile theme switches with system color scheme. Lighthouse perf > 80 on desktop.
- Goal. Draggable marker for origin. On load, request geolocation; fallback to Tel Aviv center (32.0853, 34.7818).
- Files.
apps/web/src/components/map/OriginPin.tsx,apps/web/src/lib/geolocation.ts. - DoD. Marker drag end emits a typed event consumed by a parent. Permission denial is graceful with a toast.
- Goal. Time band selector (5/10/15/20/30). On change or pin drag, hit
/api/isochroneand render the polygon. - Files.
apps/web/src/app/api/isochrone/route.ts,apps/web/src/components/controls/TimeSelector.tsx,apps/web/src/components/map/IsochroneLayer.tsx, SWR hook inapps/web/src/lib/hooks/useIsochrone.ts. - DoD. Pin drag and time change update the polygon within 2 s P50 in the deployed preview. Route handler caches identical requests for 60 s on the server.
- Goal.
GeoapifyPoiProvidersearching by bbox + categories with internalPoiCategoryenum normalization. - Files.
packages/providers/src/poi/geoapify.ts, fixture, vitest spec. - DoD. Fixture-driven tests pass. Categories map correctly to internal enum values. Adapter never returns provider-specific tags.
- Goal. After isochrone renders, fetch POIs in its bbox, filter inside the polygon client-side (turf.js
booleanPointInPolygon), render markers, toggleable per category in a sidebar. - Files.
apps/web/src/app/api/pois/route.ts,apps/web/src/components/map/PoiLayer.tsx,apps/web/src/components/controls/CategoryToggles.tsx. - DoD. ≤ 200 markers render without jank. Toggling categories updates the layer instantly. POIs outside the polygon are filtered out.
- Goal. App state (lng, lat, mode, t, cats, z) lives in the URL. Loading a URL reproduces the view. Updates are debounced.
- Files.
apps/web/src/lib/url-state.ts, integrated with map and controls. - DoD. Round-trip test (Playwright): set state via UI → copy URL → open in new tab → state matches.
- Goal. Button that picks a random POI inside the current isochrone, animates camera, and shows a card.
- Files.
apps/web/src/components/controls/SurpriseMe.tsx,apps/web/src/components/map/PoiCard.tsx. - DoD. Empty-isochrone case shows a friendly empty state. Animation respects
prefers-reduced-motion.
- Goal. Stadia, MapLibre, OSM, Geoapify, ORS attributions visible. Keyboard nav for all controls. 360 px viewport works.
- Files.
apps/web/src/components/map/Attribution.tsx, audits noted in PR description. - DoD. axe-core on
/reports zero serious violations. All four attribution links resolve. iPhone SE viewport screenshot looks correct in the PR.
- Goal. One end-to-end smoke test: load
/, drop a pin, change time, assert polygon and ≥ 1 POI marker exist. - Files.
apps/web/e2e/smoke.spec.ts, CI step added to T-02 workflow. - DoD. Smoke passes locally and in CI on a built preview.
- Status. Local run green (polygon from local engine, POI markers, time-band + pin-drag refetch). CI wiring deferred: the POI assertion needs a
GEOAPIFY_API_KEYrepo secret — add the job when secrets are provisioned.
- Goal. Static OG image for
/(Next ImageResponse), production README with the project pitch, screenshots/GIF, and "how to run." - Files.
apps/web/src/app/opengraph-image.tsx,README.md,docs/screenshots/. - DoD. OG card validates on opengraph.xyz. README links to the live URL.
- Goal. Land a
FoursquarePoiProviderinpackages/providers/src/poi/foursquare.ts. IntroduceCompositePoiProviderthat routes commercial categories (café, restaurant) to Foursquare and geographic categories (park, beach, viewpoint, museum) to Geoapify, per ADR-0006. - Files.
packages/providers/src/poi/{foursquare,composite}.ts,packages/providers/src/poi/foursquare.test.ts(fixture-driven),apps/web/src/app/api/pois/route.ts(swap to composite when key present),apps/web/.env.example(addFOURSQUARE_API_KEY). - DoD. Unit tests cover the routing logic. With
FOURSQUARE_API_KEYset, café/restaurant markers come from Foursquare; without it, the existing Geoapify-only path still works. - Blocked by. User provisioning a Foursquare developer account.
- Goal. Side panel listing the visible POIs with sortable columns: rating (Foursquare/Google), distance from origin (computed client-side), category. Selecting a row opens the destination card and flies the camera (reusing the SurpriseMe camera path).
- Files.
apps/web/src/components/panels/SuggestedPlaces.tsx, small distance helper inapps/web/src/lib/geo.ts. - DoD. Toggling sort columns reorders the list instantly. Selecting a row updates the map. Works on mobile (panel collapses to a bottom sheet).
- Blocked by. T-16 (needs rating data).
- Goal. Promote latest preview to production. Tag
v0.1.0. Post-mortem note: what worked, what didn't, what changed in the PRD. - Files.
CHANGELOG.md,docs/postmortem-v0.1.md. - DoD. Live URL reachable; CHANGELOG has entries; postmortem references at least 3 ADR/PRD diffs that came out of building.
- Goal. Replace the hosted ORS dependency with a repo-owned engine per the 2026-07-18 design doc: Python graph pipeline (
tools/graph-pipeline, uv-managed) building a committed binary walk-graph asset; pure-TS engine (packages/engine) doing snap → cutoff Dijkstra → marching-squares polygonization;LocalIsochroneProviderbehind the unchangedIsochroneProviderseam. - Files.
tools/graph-pipeline/**,packages/engine/**,packages/providers/src/{server.ts,isochrone/{local,bundled-source}.ts},apps/web/src/lib/server/isochrone-providers.ts,apps/web/src/app/api/isochrone/route.ts,apps/web/assets/graphs/walk-tlv.v1.bin,docs/reference/graph-asset-format.md, ADR-0007. - DoD. All met: pipeline pytest + engine/provider/web vitest green; cross-language fixture contract test; perf guard (warm 30-min isochrone ~42 ms < 200 ms); ORS IoU validation recorded in
docs/research/02-local-vs-ors-iou.md;ISOCHRONE_PROVIDERselection with 422 out-of-coverage and optional ORS fallback.
- Goal. Raise the prototype UI to product quality per
docs/superpowers/specs/2026-07-19-ui-elevation-design.md: warm & playful tokens (DM Sans, coral/orange), nested time-band isochrones from one multi-band API response, unified desktop control bar + logo/info chip, vaul bottom sheet on mobile, band legend, playful 422/empty/onboarding states, POI glyphs + name chips. - Files.
packages/engine/src/isochrone.ts(computeIsochroneBands),packages/providers/src/isochrone/{types,local}.ts(optional getIsochroneBands),apps/web/src/app/api/isochrone/route.ts(bands=1),apps/web/src/lib/hooks/{useIsochroneBands,useIsMobile}.ts,apps/web/src/components/{brand,controls,legend,map,onboarding,sheet,destination}/**,apps/web/src/app/{globals.css,layout.tsx,page.tsx},apps/web/e2e/smoke.spec.ts. - DoD. All met: 62 unit tests + 2 e2e green; time-band changes are instant (no refetch, e2e-asserted); bottom sheet at 375px; build green (client-bundle purity); screenshots recorded in session.