docs+openspec: prior-art research (Organic Maps, Endurain, wanderer) and 15 proposals

Add docs/inspirations.md as the durable record of the 2026-07-05/06
prior-art research — per-project learnings with source paths, canonical
credit lines, and the changes each spawned — and extend the
acknowledgment lists in philosophy.md/architecture.md (Organic Maps,
Endurain, wanderer).

New OpenSpec changes (proposal/design/specs/tasks each):
- Organic Maps: elevation-profile-hardening, gpx-parser-robustness,
  hiking-time-estimate, poi-index, hiking-foot-profile
- Endurain: account-export, activity-duplicate-review,
  fit-parsing-hardening, activity-locations, self-hosting-guide,
  activity-privacy-controls
- wanderer: federation-hardening, link-share-tokens
- credits-page (user-visible acknowledgments)

Updated in-flight changes with wanderer prior-art sections:
route-federation, route-discovery.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Ullrich Schäfer 2026-07-06 07:23:00 +02:00
parent be4f7e4ae8
commit 5dd4968626
80 changed files with 2600 additions and 2 deletions

View file

@ -0,0 +1,20 @@
## ADDED Requirements
### Requirement: POI extract pipeline on the BRouter host
The BRouter host SHALL run a scheduled job (monthly by default) that downloads an OSM data file, filters it to the planner's POI category selectors with osmium, and publishes the resulting artifact with a checksum-and-timestamp manifest at a vSwitch-only URL via the existing Caddy sidecar. Transient working data SHALL live under the `trails` user's directory and be cleaned up after each run.
#### Scenario: Monthly extract published
- **WHEN** the scheduled extract job completes
- **THEN** a fresh artifact and manifest are available to the flagship over the vSwitch
- **AND** the planet working files are removed from disk
#### Scenario: Artifact not publicly reachable
- **WHEN** a host outside the vSwitch requests the artifact URL
- **THEN** the request is refused
### Requirement: POI import job on the flagship
The flagship SHALL run a scheduled import job that fetches the artifact over the vSwitch, verifies its checksum, loads it into the staging table, and performs the guarded atomic swap. Job outcome and index age SHALL be visible in the monitoring stack.
#### Scenario: Import failure is visible
- **WHEN** an import run fails (fetch, checksum, load, or guard)
- **THEN** the failure is recorded in metrics/logs and surfaces on the Grafana planner dashboard

View file

@ -0,0 +1,50 @@
## MODIFIED Requirements
### Requirement: POI categories
The Planner SHALL support the following POI categories served from the instance's own POI index (`/api/pois`): drinking water, shelter, camping, food & drink, groceries, bike infrastructure, accommodation, viewpoints, and toilets.
#### Scenario: Enable a POI category
- **WHEN** a user enables the "Drinking water" category in the POI panel
- **THEN** drinking water POIs within the current map viewport are fetched from the instance's POI index and rendered as markers
#### Scenario: Disable a POI category
- **WHEN** a user disables a previously enabled POI category
- **THEN** markers for that category are removed from the map
#### Scenario: Multiple categories enabled
- **WHEN** a user enables "Camping" and "Drinking water" simultaneously
- **THEN** both categories of markers are visible, each with distinct icons
### Requirement: Viewport-scoped POI loading
The Planner SHALL load POIs only within the current map viewport, refreshing when the viewport changes.
#### Scenario: Load POIs on viewport
- **WHEN** POI categories are enabled and the user pans or zooms the map
- **THEN** POIs are fetched for the new viewport after an 800ms debounce (`DEBOUNCE_MS` in `use-pois.ts`); a minimum request interval of 2000ms additionally throttles rapid viewport changes
#### Scenario: Zoom threshold
- **WHEN** the map zoom level is below 10 (`MIN_ZOOM` constant in `use-pois.ts`)
- **THEN** POI queries are not sent and a message indicates the user should zoom in to see POIs
#### Scenario: Cached results
- **WHEN** the user pans back to a previously viewed area within 10 minutes
- **THEN** cached POI results are displayed without a new POI request
## REMOVED Requirements
### Requirement: Overpass rate limit handling
**Reason**: The Overpass API dependency is removed; POIs are served from the instance's own index. Degradation handling is re-specified source-neutrally below.
**Migration**: The client error UI is unchanged; it now reacts to 429/5xx from `/api/pois` instead of Overpass statuses.
## ADDED Requirements
### Requirement: POI service degradation handling
The Planner SHALL handle POI endpoint failures gracefully.
#### Scenario: Rate limited response
- **WHEN** `/api/pois` returns a 429 status
- **THEN** the Planner shows a temporary "POI data unavailable — try again shortly" message and sets a backoff delay before the next request
#### Scenario: POI service unavailable
- **WHEN** `/api/pois` is unreachable or returns a server error
- **THEN** the Planner shows a message and tile overlays continue to function normally

View file

@ -0,0 +1,45 @@
## ADDED Requirements
### Requirement: POI index table
The instance SHALL maintain a `planner.pois` table of OSM points of interest for the planner's POI categories, storing OSM type and id, category, optional name, centroid point geometry, the element's tags, and import timestamp, indexed for bbox-and-category lookup. Category membership SHALL be derived from the structured tag selectors defined in `@trails-cool/map-core`, which are the single source of truth for the pipeline filter and import-time classification.
#### Scenario: Bbox query by category
- **WHEN** the serving layer queries the index for "drinking water" within a viewport bbox
- **THEN** all indexed elements whose tags match that category's selectors and whose centroid lies in the bbox are returned
#### Scenario: Way POIs appear as points
- **WHEN** an OSM way (e.g. a campground area) matches a category selector
- **THEN** it is indexed with its centroid as the point geometry
### Requirement: Periodic import with atomic swap
The instance SHALL refresh the POI index on a schedule (monthly by default) by filtering an OSM data file to the category selectors, loading the result into a staging table, and swapping it in atomically so serving is never interrupted. The import SHALL abort without swapping when the staging row count falls below 70% of the live table's row count, unless bootstrap mode is explicitly enabled.
#### Scenario: Refresh replaces data without downtime
- **WHEN** a scheduled import completes successfully
- **THEN** the live table reflects the new dataset and no serving request observed an empty or partial table
#### Scenario: Shrunken dataset refused
- **WHEN** an import produces fewer than 70% of the live table's rows (e.g. truncated download)
- **THEN** no swap occurs, the live data stays untouched, and the failure is logged and visible in metrics
#### Scenario: Regional extract for self-hosters
- **WHEN** an operator configures a regional extract URL instead of the planet file
- **THEN** the same pipeline imports that region only
### Requirement: POI serving endpoint
The Planner SHALL serve POIs from the index via `/api/pois`, accepting a bbox and category list, enforcing the same-origin and session checks and the per-IP rate limit that the Overpass proxy enforced, and capping results at 100 per request. The response SHALL provide the fields the client's `Poi` shape requires (id, coordinates, name, category, tags). POI requests SHALL NOT contact any third-party service.
#### Scenario: Happy-path query
- **WHEN** the browser requests two enabled categories for the current viewport
- **THEN** the endpoint returns matching POIs from the local index with their tags
#### Scenario: Missing index degrades gracefully
- **WHEN** the POI table is empty or absent (fresh self-hosted instance)
- **THEN** the endpoint returns an empty result or a service-unavailable status without errors that break the map
### Requirement: Index freshness observability
The instance SHALL expose metrics for index size per category, index age, import job outcome, and serving request counts, and the monitoring stack SHALL alert when the index age indicates a missed refresh.
#### Scenario: Stale index alerts
- **WHEN** the index age exceeds the alert threshold (~6 weeks)
- **THEN** an alert fires identifying the POI import as unhealthy

View file

@ -0,0 +1,15 @@
## REMOVED Requirements
### Requirement: Overpass API rate limit
**Reason**: The `/api/overpass` proxy and its third-party upstream are removed; POIs are served from the instance's own index.
**Migration**: The equivalent limit continues on the replacement endpoint — see "POI API rate limit" below. No client changes; the same error UI handles 429s.
## ADDED Requirements
### Requirement: POI API rate limit
The Planner SHALL limit `/api/pois` requests to 120 per IP per minute to protect the instance's database from abusive clients.
#### Scenario: POI rate limit exceeded
- **WHEN** a single IP exceeds 120 POI requests in one minute
- **THEN** `/api/pois` responds with 429 Too Many Requests
- **AND** no database query is executed for rejected requests