Add 8 proposed changes and old trails analysis

Proposed changes (all with proposal, design, specs, tasks):
- app-navigation (9 tasks) — nav bars for both apps
- planner-landing-page (7 tasks) — standalone landing page
- planner-multiplayer-awareness (13 tasks) — participants, cursors, names
- changelog (13 tasks) — public changelog with "what's new"
- transactional-emails (15 tasks) — magic link + welcome emails
- planner-features (18 tasks) — no-go areas, notes, recovery, rate limits
- komoot-import (23 tasks) — Komoot tour import
- route-features (37 tasks) — sharing, multi-day, spatial, photos

Also adds docs/old-trails-analysis.md with feature analysis from the
older trails project.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ullrich Schäfer 2026-03-25 03:57:20 +01:00
parent 57141cdfab
commit c7c1c275df
No known key found for this signature in database
GPG key ID: A32FF691A0F752D9
55 changed files with 1704 additions and 0 deletions

View file

@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-03-25

View file

@ -0,0 +1,74 @@
## Context
The Planner architecture defines noGoAreas, notes, and crash recovery in the
Yjs data model but none are implemented. The public instance also lacks rate
limiting — anyone can create unlimited sessions or hammer BRouter.
## Goals / Non-Goals
**Goals:**
- Draw no-go polygons on the map, synced via Yjs, sent to BRouter
- Shared notes (Y.Text) visible in sidebar for planning discussion
- localStorage backup of Yjs state, merged on reconnect
- Rate limits: 10 sessions/IP/hour, 60 BRouter calls/session/hour
**Non-Goals:**
- Complex polygon editing (holes, multi-polygon) — just simple polygons
- Rich text in notes (just plain text)
- Cross-tab Yjs sync via localStorage (handled by WebSocket)
- Rate limiting dashboard or admin UI
## Decisions
### D1: No-go areas as Yjs Y.Array of polygon coordinates
```typescript
// Yjs doc structure addition
noGoAreas: Y.Array<Y.Map<{
points: Array<{lat: number, lon: number}>,
name?: string
}>>
```
Each polygon is a closed ring of lat/lon points. BRouter accepts no-go areas
as a `nogo` parameter: `lon,lat,radius` for circles or polygon coordinates
depending on the BRouter version. We'll convert our polygons to BRouter's
expected format in the routing request.
### D2: Leaflet.draw for polygon drawing
Use `leaflet-draw` (or `@geoman-io/leaflet-geoman-free`) for drawing polygons
on the map. When a polygon is completed, add it to the Y.Array. Existing
polygons render as semi-transparent red overlays.
### D3: Notes as Y.Text in sidebar tab
Add a "Notes" tab to the sidebar (alongside waypoints). Uses Y.Text for
collaborative editing — simple textarea bound to Y.Text. Changes sync in
real-time. No markdown rendering — plain text only.
### D4: localStorage crash recovery
Every 10 seconds, save `Y.encodeStateAsUpdate(doc)` to
`localStorage['trails:session:${id}']`. On page load, if localStorage has
state for the current session, apply it as an update before connecting to
the server. Yjs CRDTs merge automatically — no conflicts.
Clear localStorage entry when session is closed or after successful sync.
### D5: In-memory rate limiter
Simple Map-based rate limiter in `server.ts`:
- Session creation: Track IP → count + window. Reject with 429 after 10/hour.
- BRouter proxy: Track session ID → count + window. Reject after 60/hour.
- No Redis needed — single server, in-memory is fine. Resets on restart
(acceptable).
## Risks / Trade-offs
- **leaflet-draw adds bundle size** → ~50KB gzipped. Acceptable for polygon
drawing functionality. Can lazy-load.
- **localStorage size limit** → ~5MB per origin. Yjs state is typically
10-100KB. No risk.
- **In-memory rate limiter resets on deploy** → Acceptable for current scale.
Redis-backed when needed.

View file

@ -0,0 +1,40 @@
## Why
The Planner's core editing works but is missing features from the architecture
doc: no-go areas for route avoidance, session notes for collaborative planning
communication, localStorage crash recovery for unsaved work, and rate limiting
to protect the BRouter API from abuse on the public instance.
## What Changes
- **No-go areas**: Draw polygons on the map that BRouter avoids when computing
routes. Stored as Y.Array in the Yjs doc, synced across participants.
- **Session notes**: Shared text area (Y.Text) for participants to leave notes,
discuss the route, or plan logistics. Visible in the sidebar.
- **Crash recovery**: Periodically save Yjs state to localStorage. On
reconnect, merge local state with server state to recover unsaved changes.
- **Rate limiting**: Limit session creation per IP and BRouter API calls per
session to prevent abuse on the public instance.
## Capabilities
### New Capabilities
- `no-go-areas`: Draw avoidance polygons on the Planner map, passed to BRouter as routing constraints
- `session-notes`: Shared collaborative text in Planner sessions via Yjs Y.Text
- `crash-recovery`: localStorage backup of Yjs state for reconnection after browser crash
- `rate-limiting`: IP-based limits on session creation and route computation
### Modified Capabilities
- `planner-session`: Session data model gains noGoAreas and notes fields
- `brouter-integration`: Routing requests include no-go area polygons
- `map-display`: Map gains polygon drawing tools for no-go areas
## Impact
- **Planner Yjs doc**: New fields (noGoAreas: Y.Array, notes: Y.Text)
- **BRouter API**: No-go areas passed as `nogo` parameter
- **Map**: Leaflet.draw or custom polygon tool for no-go areas
- **Dependencies**: May need `leaflet-draw` for polygon editing
- **Server**: Rate limiting middleware (in-memory store)

View file

@ -0,0 +1,8 @@
## MODIFIED Requirements
### Requirement: BRouter routing with constraints
Route computation SHALL include no-go area polygons as avoidance constraints.
#### Scenario: Route with no-go areas
- **WHEN** the routing host computes a route and no-go areas exist
- **THEN** the BRouter request includes nogo parameters for each polygon

View file

@ -0,0 +1,12 @@
## ADDED Requirements
### Requirement: localStorage crash recovery
The Planner SHALL periodically save Yjs state to localStorage and recover it on reconnect.
#### Scenario: Browser crash recovery
- **WHEN** a user's browser crashes and they reopen the session
- **THEN** unsaved changes from localStorage are merged with the server state
#### Scenario: Clean exit
- **WHEN** a session syncs successfully
- **THEN** the localStorage backup is cleared

View file

@ -0,0 +1,12 @@
## MODIFIED Requirements
### Requirement: Map polygon drawing
The Planner map SHALL support drawing and displaying no-go area polygons.
#### Scenario: Polygon tool
- **WHEN** a user activates the no-go area tool
- **THEN** they can draw a polygon by clicking points on the map
#### Scenario: Polygon display
- **WHEN** no-go areas exist in the session
- **THEN** they are rendered as semi-transparent red polygons on the map

View file

@ -0,0 +1,16 @@
## ADDED Requirements
### Requirement: Draw no-go areas
Users SHALL be able to draw polygons on the map that BRouter avoids when computing routes.
#### Scenario: Draw polygon
- **WHEN** a user activates the no-go area tool and draws a polygon on the map
- **THEN** the polygon is added to the Yjs doc and visible to all participants
#### Scenario: Route avoids no-go area
- **WHEN** a route is computed and a no-go area intersects the direct path
- **THEN** BRouter routes around the no-go area
#### Scenario: Delete no-go area
- **WHEN** a user deletes a no-go area polygon
- **THEN** it is removed from the Yjs doc and the route is recomputed

View file

@ -0,0 +1,8 @@
## MODIFIED Requirements
### Requirement: Planner session data model
The Yjs document SHALL include noGoAreas and notes fields alongside waypoints and routeData.
#### Scenario: Session with all fields
- **WHEN** a Planner session is active
- **THEN** the Yjs doc contains: waypoints (Y.Array), routeData (Y.Map), noGoAreas (Y.Array), notes (Y.Text)

View file

@ -0,0 +1,15 @@
## ADDED Requirements
### Requirement: Session creation rate limit
The Planner SHALL limit session creation to 10 per IP per hour.
#### Scenario: Rate limit exceeded
- **WHEN** an IP creates more than 10 sessions in one hour
- **THEN** the server responds with 429 Too Many Requests
### Requirement: BRouter call rate limit
The Planner SHALL limit route computations to 60 per session per hour.
#### Scenario: Routing rate limit exceeded
- **WHEN** a session exceeds 60 BRouter calls in one hour
- **THEN** the server responds with 429 and the client shows a "slow down" message

View file

@ -0,0 +1,12 @@
## ADDED Requirements
### Requirement: Collaborative session notes
Planner sessions SHALL have a shared text area for participants to write notes.
#### Scenario: Write notes
- **WHEN** a user types in the notes area
- **THEN** the text syncs in real-time to all other participants via Yjs
#### Scenario: Notes persist
- **WHEN** a user leaves and rejoins a session
- **THEN** the notes are still there (stored in Yjs doc)

View file

@ -0,0 +1,36 @@
## 1. No-Go Areas
- [ ] 1.1 Add `noGoAreas` Y.Array to Yjs doc in use-yjs.ts
- [ ] 1.2 Add leaflet-draw (or geoman) dependency for polygon drawing
- [ ] 1.3 Create NoGoAreaLayer component — renders polygons as red overlays, handles draw/delete
- [ ] 1.4 Add no-go area toolbar button to PlannerMap
- [ ] 1.5 Pass no-go areas to BRouter API as `nogo` parameters in brouter.ts
- [ ] 1.6 Trigger route recomputation when no-go areas change
## 2. Session Notes
- [ ] 2.1 Add `notes` Y.Text to Yjs doc in use-yjs.ts
- [ ] 2.2 Create NotesPanel component — textarea bound to Y.Text with real-time sync
- [ ] 2.3 Add "Notes" tab to sidebar (alongside waypoints)
- [ ] 2.4 Add i18n keys for notes UI (en + de)
## 3. Crash Recovery
- [ ] 3.1 Add periodic localStorage save (every 10s) of Yjs state in use-yjs.ts
- [ ] 3.2 On session reconnect, check localStorage for saved state and apply as Yjs update
- [ ] 3.3 Clear localStorage entry after successful sync or session close
- [ ] 3.4 Write unit test for save/restore logic
## 4. Rate Limiting
- [ ] 4.1 Create rate limiter utility in apps/planner/app/lib/rate-limit.ts (already exists — extend for IP tracking)
- [ ] 4.2 Add session creation rate limit (10/IP/hour) in the /new route or API
- [ ] 4.3 Add BRouter proxy rate limit (60/session/hour) in the route computation handler
- [ ] 4.4 Return 429 with Retry-After header and show user-friendly message on client
## 5. Verify
- [ ] 5.1 Test no-go areas: draw polygon, verify route avoids it, delete polygon
- [ ] 5.2 Test notes: type in two windows, verify real-time sync
- [ ] 5.3 Test crash recovery: make changes, kill browser, reopen — verify recovery
- [ ] 5.4 Test rate limiting: exceed limits, verify 429 response