Commit graph

10 commits

Author SHA1 Message Date
Ullrich Schäfer
c2abb64ee0
Add per-waypoint notes and nearby POI snap to Planner
- `note` field on Waypoint type, stored in Yjs Y.Map and exported as
  `<desc>` inside `<wpt>` in GPX
- Inline textarea note editing in WaypointSidebar with auto-resize,
  character counter (500 max), save-on-blur, Escape cancel
- Note indicator dot on map markers; note tooltip on hover
- `useNearbyPois` hook: fetches POIs within 500m of selected waypoint
  via Overpass proxy, 500ms debounce, AbortController, 60s rate-limit
  suppression
- NearbyPoiMarkers component: renders POI markers on map for selected
  waypoint with snap-to-POI on click
- Nearby section in WaypointSidebar: list with snap buttons, spinner,
  empty/rate-limited states, "Show more" toggle (5 → all)
- Unit tests for fetchNearbyPois bbox geometry and snap-to-POI Yjs
  transaction behaviour

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 23:58:21 +02:00
Ullrich Schäfer
ed7f6ce153
Session-bind /api/route and /api/overpass
Today both proxies are effectively open to anyone who can set an
Origin header for trails.cool — a third party can use us as a free
BRouter/Overpass relay. Require a live planner session on every call
so abuse traffic costs the scraper a session row (observable,
revocable) before they can issue a single query.

Server:
- New `requireSession(id)` helper — returns the session row or a 401
  Response. Reused by both route handlers.
- `/api/route`: `sessionId` in body is now required and verified;
  rate-limit key always falls back to the session id.
- `/api/overpass`: new `X-Trails-Session` header, verified. Header
  keeps the session out of the request body so the body-keyed cache
  is unaffected.

Client plumbing:
- `useRouting(yjs, sessionId)` — sessionId goes into the /api/route
  body.
- `usePois(sessionId)` → `queryPois(..., sessionId)` → `X-Trails-Session`
  on the proxy call.
- `PlannerMap` + `YjsDebugPanel` gain a `sessionId` prop from
  `SessionView`.

Journal server-to-server:
- Demo-bot and `/api/v1/routes/compute` now POST `/api/sessions` to
  mint a throwaway planner session, then cite it on the forwarded
  `/api/route` call. Planner's `expire-sessions` cron cleans these up
  (7d window) so nothing needs explicit teardown.

Tests:
- 5 unit tests for `requireSession` covering missing / empty /
  non-string / unknown-session / valid-session cases.
- Two integration E2E tests document the 401 for missing session on
  each proxy.
- Pre-existing `/api/route` integration tests updated to mint a
  session first.

Caveat: existing browser tabs lose their /api/route ability until
reload (the old JS doesn't know to send sessionId). Acceptable for
an anonymous planner.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 22:18:45 +02:00
Ullrich Schäfer
6c1148d5b7
Quantize Overpass query bbox to align cache keys across clients
Near-identical viewports from different collaborators (slightly
different pan/zoom because Yjs awareness doesn't enforce pixel-exact
alignment) previously produced byte-different Overpass queries and
therefore different server-side cache keys. Each client missed cache
and triggered its own upstream fetch.

buildQuery now quantizes the bbox to a 0.01° grid (~1 km), expanding
outward (south/west floor, north/east ceil) so the viewport is always
covered, and formats coords with 3 decimals for a stable string.

Trade-offs:
- Slightly larger query bbox: ≤ one grid cell (≈ 1 km) of padding on
  each side. For a zoom-12 viewport (~11 km wide) that's <10% extra
  data; bounded by the existing `[maxsize:1048576]` + `out 100` limits.
- Coordinates in the query are now 3-decimal strings regardless of the
  caller's precision — existing buildQuery test updated to match.

Two new tests cover the cache-alignment property and the outward-
expansion invariant. Hit ratio impact will be visible on the Grafana
"Overpass Cache Hit Ratio" stat added in the prior PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 02:18:46 +02:00
Ullrich Schäfer
a4df5a43f6
Route Overpass through server-side proxy with cache + coalescing
Adds apps/planner/app/routes/api.overpass.ts — a same-origin, rate-limited
server-side proxy that forwards Overpass QL to the upstream endpoint
configured via OVERPASS_URL (default: overpass.private.coffee).

Motivation:
- private.coffee's best-practices require a meaningful User-Agent
  identifying the project. Browsers cannot set User-Agent on fetch()
  (forbidden header), so the request has to originate server-side.
- Collaborative sessions commonly have N clients pan/zoom the same map,
  so the same bbox query arrives multiple times within seconds.
- Setting up the proxy now lets us swap OVERPASS_URL to a self-hosted
  Overpass later without client changes.

What the proxy does:
- Sets User-Agent "trails.cool Planner (https://trails.cool; legal@trails.cool)"
- Enforces same-origin via the Origin header
- Rate-limits per client IP (120 req/min)
- In-memory LRU cache of upstream responses keyed on the form-encoded body
  (TTL 10 min, max 200 entries)
- Coalesces concurrent misses for the same key so N simultaneous clients
  in one session incur exactly one upstream call

Client change: apps/planner/app/lib/overpass.ts POSTs to /api/overpass
instead of iterating over public Overpass endpoints. Fallback list
removed; resilience is now the proxy's responsibility.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 02:04:50 +02:00
Ullrich Schäfer
f7c2604121
Use overpass.private.coffee as primary Overpass endpoint
Switch the Planner's primary Overpass endpoint from
overpass.kumi.systems to overpass.private.coffee. private.coffee is a
privacy-focused public Overpass instance that doesn't log queries,
which fits the Planner's privacy-first principle better than the
previous default. Keep overpass-api.de as the silent fallback for
resilience while the self-host-overpass change is in flight.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 01:48:09 +02:00
Ullrich Schäfer
60b94b5789
Extract @trails-cool/map-core package from Planner
New renderer-agnostic package with zero dependencies:
- Tile configs (base layers, overlay layers)
- Color palettes (surface, highway, smoothness, tracktype, cycleway,
  bikeroute, elevation, maxspeed) — 8 color maps + 3 color functions
- POI category definitions (9 categories with Overpass queries, icons,
  colors, profile mappings)
- Z-index layering constants
- Snap distance constant

Updated 15 consuming files to import from @trails-cool/map-core.
Deleted poi-categories.ts and z-index.ts from the Planner (fully moved).
packages/map re-exports tile configs from map-core for backwards compat.

All 117 tests pass, no user-facing changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 22:28:14 +02:00
Ullrich Schäfer
8c3fc36d23
Add Overpass fallback endpoint and reduce query size
- Try overpass.kumi.systems first, fall back to overpass-api.de
- Reduced max results from 200 to 100, added maxsize:1MB limit
- Shortened timeout from 15s to 10s, added qt (quiet) output mode

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 01:54:03 +02:00
Ullrich Schäfer
65330cf9e2
Switch Overpass endpoint to overpass.kumi.systems
overpass.kumi.systems has higher rate limits than overpass-api.de.
This is the same endpoint brouter-web uses as its default.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 01:28:03 +02:00
Ullrich Schäfer
d1b8674575
Handle Overpass rate limit returned as 200 with error body
Overpass API sometimes returns HTTP 200 with a plain-text error body
containing "rate_limited" instead of a proper 429 status. Now checks
response body for this pattern before attempting JSON parse.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 01:25:59 +02:00
Ullrich Schäfer
375ff2fa13
Add Overpass client, POI categories, cache, and usePois hook
- poi-categories.ts: 9 POI categories with Overpass QL queries, icons,
  colors, and profile-aware defaults
- overpass.ts: Query builder, JSON parser, deduplication, rate limit error
- poi-cache.ts: Tile-based cache (0.1° grid cells, 10min TTL)
- use-pois.ts: React hook with 500ms debounce, AbortController, zoom
  threshold (>=12), exponential backoff on 429

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 01:15:34 +02:00