Commit graph

379 commits

Author SHA1 Message Date
copilot-swe-agent[bot]
5979296f3b Merge remote-tracking branch 'refs/remotes/origin/main' into copilot/identify-spec-implementation-drift
# Conflicts:
#	apps/planner/app/components/PlannerMap.tsx
#	apps/planner/app/lib/brouter.ts
#	openspec/specs/no-go-areas/spec.md

Co-authored-by: stigi <13815+stigi@users.noreply.github.com>
2026-04-03 09:42:51 +00:00
Ullrich Schäfer
237660db5e
Merge pull request #156 from trails-cool/fix/nogo-button-click-through
Fix no-go area button click creating a waypoint
2026-04-03 10:40:05 +01:00
Ullrich Schäfer
16568ffa45
Fix no-go button click creating a waypoint
The button was rendered as a React component, not a Leaflet control,
so clicks propagated to the map. Use L.DomEvent.disableClickPropagation
on the container — same approach Leaflet's built-in controls use.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 10:37:43 +01:00
Ullrich Schäfer
596d96d8cb
Merge pull request #155 from trails-cool/fix/nogo-polygons-native
Pass no-go area polygons natively to BRouter
2026-04-03 10:34:08 +01:00
Ullrich Schäfer
3abea9d5a8
Pass no-go area polygons natively to BRouter
BRouter supports a `polygons` parameter with full vertex coordinates.
We were approximating polygons as circles (centroid + max radius),
which poorly represented elongated or concave shapes.

Now passes polygon vertices directly — no approximation, exact
no-go boundaries.

Also removes unused haversineMeters helper and updates the spec.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 10:31:43 +01:00
copilot-swe-agent[bot]
009a2f1f19
Update spec drift report after merging main: 11 of 12 drifts now resolved
Agent-Logs-Url: https://github.com/trails-cool/trails/sessions/4d97b64f-ba95-4986-8443-d731c19cc20d

Co-authored-by: stigi <13815+stigi@users.noreply.github.com>
2026-04-03 09:25:24 +00:00
copilot-swe-agent[bot]
9c3900e7b5 Merge remote-tracking branch 'origin/main' into copilot/identify-spec-implementation-drift
Co-authored-by: stigi <13815+stigi@users.noreply.github.com>
2026-04-03 09:23:17 +00:00
Ullrich Schäfer
d2ff488f37
Merge pull request #154 from trails-cool/chore/prometheus-5s-scrape
Reduce Prometheus scrape interval to 5s
2026-04-03 10:16:38 +01:00
Ullrich Schäfer
55076ef440
Reduce Prometheus scrape interval from 15s to 5s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 10:15:08 +01:00
Ullrich Schäfer
5a5fe87905
Merge pull request #149 from trails-cool/fix/planner-metrics-gauges
Fix planner metrics gauges always reporting zero
2026-04-03 10:04:40 +01:00
Ullrich Schäfer
d97992e967
Guard all metric registrations against re-evaluation
Vite's dev server can re-evaluate modules, causing prom-client
"already registered" errors for all metrics, not just default ones.
Use getOrCreate pattern to reuse existing metrics from the registry.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 10:02:21 +01:00
Ullrich Schäfer
a3ad073d96
Guard collectDefaultMetrics against duplicate registration
Importing metrics.server.ts from yjs-server.ts caused
collectDefaultMetrics() to re-register during HMR, crashing
the planner dev server. Check if metrics already exist first.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 09:16:31 +01:00
Ullrich Schäfer
e083ee134d
Wire planner metrics gauges to actual session/client counts
plannerActiveSessions and plannerConnectedClients were registered
but never updated — they always reported 0. Now incremented on
WebSocket connect and decremented on close.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 09:16:31 +01:00
Ullrich Schäfer
ca057eb9d4
Merge pull request #153 from trails-cool/chore/update-drifted-specs
Update specs to match implementation
2026-04-03 09:15:31 +01:00
Ullrich Schäfer
02939ca828
Update specs to match implementation
Addresses spec drift items #3, 5, 6, 7, 8, 9, 10, 11, 12 from #147:

- transactional-emails: Resend → Nodemailer + SMTP
- infrastructure: CX21 → cx23
- secret-management: single secrets.env → split app/infra files
- brouter-integration: 5s failover delay → instant via clientID election
- brouter-integration: 2 profiles → 5 (trekking, fastbike, safety, shortest, car)
- observability: add version field to health response
- observability: add brouter_request_duration_seconds metric
- planner-session: remove 30-day max ceiling (not enforced)
- shared-packages: clarify map package scope vs planner-specific features

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 09:13:07 +01:00
Ullrich Schäfer
9e823317b9
Merge pull request #152 from trails-cool/fix/remove-sync-parsegpx
Fix GPX distance + remove sync parseGpx
2026-04-03 09:11:36 +01:00
Ullrich Schäfer
2b0b89add8
Use epsilon 0.005 for Douglas-Peucker waypoint extraction
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 22:40:01 +01:00
Ullrich Schäfer
0e27d6eaef
Use Douglas-Peucker simplification for single-segment GPX
For GPX files with one long track segment, extracting only start/end
gives just 2 waypoints. Now uses Douglas-Peucker line simplification
(epsilon=0.05° ≈ 5km) to find significant turning points.

Result: berlin-dresden-radweg (249km, 5660 points) → 10 waypoints
that capture the route's key direction changes.

Multi-segment GPX files still use segment endpoints as before.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 22:36:13 +01:00
Ullrich Schäfer
cc90c32a0e
Extract shared extractWaypoints into @trails-cool/gpx
Both new.tsx and api.sessions.ts had duplicated track-segment
waypoint extraction logic. Moved to packages/gpx as
extractWaypoints(gpxData) — uses <wpt> elements when present,
falls back to start of each track segment + end of last.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 20:47:17 +01:00
Ullrich Schäfer
e0f7c5b806
Apply track-segment waypoint extraction to sessions API
Same fix as new.tsx — the journal→planner handoff also only used
<wpt> elements from GPX. Now both code paths extract waypoints
from track segments when no explicit waypoints exist.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 20:45:05 +01:00
Ullrich Schäfer
79942bb99e
Extract start/end from track when GPX has no waypoints
The planner's GPX import only used <wpt> elements, which many GPX
files don't have. Now falls back to the first and last track point,
giving the planner two endpoints to route between.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 20:41:29 +01:00
Ullrich Schäfer
5f3d4ae846
Remove sync parseGpx, use parseGpxAsync everywhere
parseGpx (sync) needs browser DOMParser which doesn't exist on the
server — it silently failed in every server-side caller. Removed the
export and migrated all callers to parseGpxAsync. Updated tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 20:32:48 +01:00
Ullrich Schäfer
d5bcecb345
Fix distance calculation for GPX files without elevation data
Distance was only stored in the elevation profile array, which was
empty when track points had no <ele> elements. Now computed via
haversine independently and exposed as gpxData.distance.

Tested: berlin-dresden-radweg-2021.gpx (5660 points, no elevation)
now correctly reports 248.8 km.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 20:32:47 +01:00
Ullrich Schäfer
ed2d04c248
Merge pull request #151 from trails-cool/fix/postgis-geom-async-parse
Fix PostGIS geom: use parseGpxAsync + raw SQL
2026-04-02 17:34:43 +01:00
Ullrich Schäfer
6ed828ef9c
Fix PostGIS geom population: use parseGpxAsync + raw SQL
The initial approach (#150) didn't work because:
1. routes.server.ts used parseGpx (sync, needs browser DOMParser)
   instead of parseGpxAsync (uses linkedom on Node.js) — GPX parsing
   silently failed, so stats AND geom were never populated
2. Drizzle's customType doesn't pass SQL expressions through toDriver

Fixes:
- Switch to parseGpxAsync for all server-side GPX parsing
- Use db.execute() with raw ST_GeomFromGeoJSON SQL after insert
- Remove unused lineStringFromCoords helper from schema
- Share setGeomFromGpx between routes and activities
- Add unit tests for GPX→GeoJSON coordinate extraction

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 17:32:07 +01:00
Ullrich Schäfer
feeca58064
Merge pull request #150 from trails-cool/fix/populate-postgis-geom
Populate PostGIS geom column from GPX track data
2026-04-02 17:07:26 +01:00
Ullrich Schäfer
b4c3f97296
Populate PostGIS geom column from GPX track data
Extract coordinates from parsed GPX tracks and store as PostGIS
LineString geometry via ST_GeomFromGeoJSON. Applied to:
- createRoute / updateRoute (routes.server.ts)
- createActivity / createRouteFromActivity (activities.server.ts)

Adds lineStringFromCoords() helper to the journal schema that
builds the SQL expression from [lon, lat] coordinate pairs.

Unblocks spatial queries (e.g. route discovery via ST_Intersects).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 17:05:02 +01:00
Ullrich Schäfer
47baa11e69
Merge pull request #148 from trails-cool/fix/grafana-oauth-user-lookup
Fix Grafana OAuth creating duplicate users
2026-04-02 16:41:54 +01:00
Ullrich Schäfer
acccfcc53b
Enable Grafana OAuth email lookup to reuse existing users
Without this, GitHub OAuth creates a new Grafana user on every login
instead of linking to the existing account by email.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 16:39:35 +01:00
copilot-swe-agent[bot]
4e9e7d7cb6
Add spec drift report documenting 12 drifts between specs and implementation
Agent-Logs-Url: https://github.com/trails-cool/trails/sessions/ad77fee7-e03f-4695-86e2-1a6cb3c28977

Co-authored-by: stigi <13815+stigi@users.noreply.github.com>
2026-04-01 13:34:10 +00:00
copilot-swe-agent[bot]
89788a6ab7
Initial plan 2026-04-01 13:06:24 +00:00
Ullrich Schäfer
d1faf21a83
Merge pull request #146 from trails-cool/feat/europe-brouter-segments
Expand BRouter routing to all of Europe
2026-03-29 22:49:16 +02:00
Ullrich Schäfer
836e0a2b6a
Expand BRouter routing coverage to all of Europe
Add ~67 segment tiles covering W10-E40, N35-N70 (was 4 Germany-only
tiles). Total disk usage ~2.9 GB. Tiles that don't exist on the
BRouter CDN are silently skipped.

Already downloaded to the server and BRouter restarted.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 22:48:11 +02:00
Ullrich Schäfer
56cd6b1ccc
Merge pull request #145 from trails-cool/chore/archive-account-settings
Archive account-settings change
2026-03-29 22:44:20 +02:00
Ullrich Schäfer
7e253caf64
Archive account-settings change, sync specs
All 37 tasks complete. Synced delta specs to main:
- New: openspec/specs/account-settings/
- Updated: openspec/specs/journal-auth/

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 22:41:51 +02:00
Ullrich Schäfer
0150c40631
Merge pull request #144 from trails-cool/chore/harden-sshd
Harden SSH config
2026-03-29 22:33:57 +02:00
Ullrich Schäfer
8eb3481831
Harden SSH: disable passwords, forwarding, add keepalive
Add SSH hardening to user_data for future server rebuilds:
- PasswordAuthentication no (key-only access)
- X11Forwarding no (headless server)
- AllowTcpForwarding no (no SSH tunnels needed)
- AllowAgentForwarding no (no agent forwarding needed)
- MaxAuthTries 3 (reduced from 6)
- ClientAliveInterval 300 + ClientAliveCountMax 2 (clean up dead sessions)

Already applied manually to the existing server via
/etc/ssh/sshd_config.d/99-hardening.conf.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 22:32:30 +02:00
Ullrich Schäfer
7d63e2b754
Merge pull request #143 from trails-cool/fix/unwrap-dashboard-json
Fix dashboard JSON: unwrap for Grafana provisioning
2026-03-29 22:21:26 +02:00
Ullrich Schäfer
3b6aede461
Fix error-rate-high alert firing on no data
The alert was firing with DatasourceNoData because zero 5xx responses
means the numerator returns empty (not 0). Fixed by:
- Adding `or vector(0)` so zero 5xx returns 0% instead of empty
- Setting `noDataState: OK` as a safety net

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 22:20:14 +02:00
Ullrich Schäfer
d45d6e3eaf
Unwrap dashboard JSON for Grafana provisioning
Grafana file-based provisioning expects flat dashboard JSON, not the
API format with a "dashboard" wrapper. The 4 wrapped dashboards were
silently failing to load — only business.json (already flat) worked.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 22:18:06 +02:00
Ullrich Schäfer
1103deb7e8
Merge pull request #142 from trails-cool/fix/grafana-annotation-filter-type
Fix Grafana annotation filter: remove type:dashboard
2026-03-29 22:00:07 +02:00
Ullrich Schäfer
9223eacb91
Remove type:dashboard from annotation config
The type:dashboard field was causing Grafana to filter annotations
by dashboard scope. Our deploy annotations are global (no dashboardUID),
so the target.type:tags is the only filter needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 21:57:39 +02:00
Ullrich Schäfer
16fd07bd97
Merge pull request #141 from trails-cool/fix/grafana-annotation-target
Fix Grafana deploy annotation query
2026-03-29 21:53:57 +02:00
Ullrich Schäfer
cafedfe6fc
Fix Grafana annotation query: add target with type tags
The annotation config was missing the target block that tells
Grafana to filter by tags. Without it, the annotation query
was silently ignored.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 21:53:33 +02:00
Ullrich Schäfer
af12837772
Merge pull request #140 from trails-cool/fix/deploy-annotations-curl-dashboards
Fix deploy annotations: wget→curl + dashboard queries
2026-03-29 20:51:06 +02:00
Ullrich Schäfer
fcfea209b8
Add deploy annotation query to all Grafana dashboards
Provisioned dashboards now display deploy markers (cyan vertical
lines) by querying built-in Grafana annotations tagged "deploy".

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 20:49:40 +02:00
Ullrich Schäfer
87be96db67
Switch deploy annotations from BusyBox wget to curl
BusyBox wget in the Grafana Alpine container doesn't support
--post-data/--header. Grafana's image includes curl.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 20:49:40 +02:00
Ullrich Schäfer
7d0ad7fe31
Merge pull request #139 from trails-cool/fix/deploy-annotation-env-file
Fix deploy annotation reading token from wrong env file
2026-03-29 20:00:07 +02:00
Ullrich Schäfer
c2b9032686
Fix deploy annotation reading token from wrong env file
GRAFANA_SERVICE_TOKEN is in secrets.infra.env, which the infra
workflow merges into .env on the server. The apps workflow was
reading from app.env (which only has app secrets).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 19:59:17 +02:00
Ullrich Schäfer
8440c2de8f
Merge pull request #138 from trails-cool/chore/grafana-service-token
Add GRAFANA_SERVICE_TOKEN to encrypted infra secrets
2026-03-29 19:51:45 +02:00