- Add eas.json with development, preview, and production profiles
- Link project to EAS (projectId: 93c75cae-fecf-4ce5-8cd8-c823760b12e2)
- Add expo-dev-client, expo-constants, expo-linking dependencies
- Remove eas-cli from project deps (use globally)
- Add ITSAppUsesNonExemptEncryption to iOS infoPlist
- Use catalog react with expo.install.exclude to skip version check
- Fix slug to match EAS project registration
- Run pnpm dedupe to resolve duplicate react across workspaces
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Routes tab (Phase 3.1):
- Paginated FlatList fetching from Journal REST API
- Route cards with name, distance (km), elevation gain, day count
- Pull-to-refresh, loading spinner, error state with retry, empty state
- Safe area insets for notch and tab bar
API client:
- Import types from @trails-cool/api instead of hand-written interfaces
- Parse list/detail responses through Zod schemas at runtime
- Add zod as mobile app dependency
i18n:
- Add mobile namespace (en + de) for routes, activities, profile, login
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Journal server (Phase 1.4 + 1.5):
- Add oauth_clients, oauth_codes, oauth_tokens tables to journal schema
- Implement GET /oauth/authorize with PKCE flow and login redirect
- Implement POST /oauth/token (authorization_code + refresh_token grants)
- Add validateBearerToken() + getAuthenticatedUser() middleware
- Seed trails-cool-mobile as trusted OAuth client on server startup
- Add GET /.well-known/trails-cool discovery endpoint
- Add returnTo support to login page and magic link verify
- Add @trails-cool/api workspace dependency to journal
Mobile app (Phase 1.5 + 1.6):
- Login screen with server URL input and discovery validation
- OAuth2 PKCE login via expo-web-browser with expo-crypto for Hermes
- Token storage in expo-secure-store with auto-refresh on 401
- API client with bearer token injection and typed errors
- Server URL persistence with localhost default in dev mode
- API version compatibility check on app foreground
- Log out + switch server on Profile tab
- iOS ATS exception for local networking
Tests:
- PKCE crypto verification, OAuthError, token generation
- Discovery endpoint response shape
- API version semver compatibility
- API client error types
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
TS 5.9 enables noUncheckedSideEffectImports under strict mode, which
errors on CSS side-effect imports (e.g. leaflet/dist/leaflet.css).
These imports are resolved by bundlers (Vite/Metro), not tsc. Set
noUncheckedSideEffectImports: false in the shared tsconfig base.
Reverts the unnecessary leaflet devDependencies on the map package
since this base config fix handles it properly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
leaflet, react-leaflet, and @types/leaflet are peer dependencies of
the map package. In CI, pnpm's strict isolation means they aren't
accessible from the package directory during standalone tsc. Adding
them as devDependencies ensures they're installed for typechecking
while consumers still provide them at runtime via peer deps.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously only 2-3 workspaces participated in each turbo task. This
expands test, lint, and typecheck to cover all 11 workspaces with
parallel execution and caching.
Test pipeline:
- Move from single root vitest to per-workspace test scripts via turbo
- Shared vitest config (vitest.shared.ts) with passWithNoTests
- Per-workspace configs re-export the shared base
- Mobile uses Jest (jest-expo + React Native Testing Library)
- Add node-environment GPX tests verifying linkedom fallback
- Add i18n mobile init tests
- Exclude apps/mobile from root vitest (uses Jest separately)
Lint pipeline:
- Add eslint lint script to all 9 previously unlinted workspaces
- Standardize all scripts to "eslint ." with shared root config
- Add .expo/ to global ESLint ignores
- Fix lint errors: unused imports in api/types, export parseGpx
Typecheck pipeline:
- Add "typecheck": "tsc" to all 8 packages
- Add @types/node (catalog) to gpx and db packages
- Fix mobile app.config.ts: remove deprecated experiments.monorepo
and newArchEnabled (both default in Expo SDK 55)
- Add allowImportingTsExtensions to mobile tsconfig
Shared package compatibility (mobile-app Phase 1.3):
- Add linkedom as explicit dependency to @trails-cool/gpx
- Add initI18nMobile() export to @trails-cool/i18n
- Confirm @trails-cool/types is pure interfaces (no DOM deps)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
- leaflet.markercluster: Dynamic import with fallback to plain layer
group. Clusters POI markers at low zoom, ungroups at zoom 15+.
- Base layer sync: baselayerchange event writes to Yjs, new
participants load the selected base layer on connect.
- Both tile overlays and base layer now persist across participants
and crash recovery.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root package.json had hardcoded versions for react, react-dom, @types/react,
@types/react-dom, tailwindcss, @tailwindcss/vite, drizzle-orm, drizzle-kit,
and drizzle-postgis that drifted from catalog entries. Switched them all to
catalog: and bumped catalog versions to match.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Vite 8 ships Rolldown as its Rust-based bundler, replacing esbuild+Rollup.
Removed the esbuild override from pnpm-workspace.yaml since Vite 8 no longer
uses esbuild for bundling. All plugins (@react-router/dev, @tailwindcss/vite,
@sentry/vite-plugin, @vitejs/plugin-basic-ssl) support Vite 8.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Vite 6.4.1 → 6.4.2: fixes path traversal in optimized deps .map
handling (medium) and arbitrary file read via dev server WebSocket
(high)
- lodash 4.17.23 → 4.18.1 via pnpm override: fixes code injection
via _.template (CVE-2026-4800, high) and prototype pollution via
_.unset/_.omit (CVE-2026-2950, medium). Transitive dep from
@react-router/dev — override needed until they bump it upstream.
Skipped 4.18.0 which was marked as a bad release.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previous attempt left duplicate i18next versions in the lockfile
(26.0.1 for @trails-cool/i18n peer dep, 26.0.3 for root). This caused
two different i18next instances at runtime, breaking i18n initialization
and leaving pages blank in E2E tests.
Fix: update specifiers and run pnpm dedupe to ensure a single version.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bugs fixed:
- FIT parser returns timestamps as Date objects, not strings — convert
to ISO 8601 before passing to generateGpx (caused str.replace crash)
- FIT parser already converts semicircles to degrees — remove redundant
conversion that produced near-zero coordinates
- Wahoo CDN URLs are pre-signed S3 URLs — remove Bearer auth header
from download requests (caused 400 "Unsupported Authorization Type")
- Filter out third-party workouts (fitness_app_id >= 1000) since Wahoo
does not share their data via the API
UX improvements:
- Individual imports use fetcher (no page refresh), button shows
"Importing..." inline
- "Import all" button imports all unimported workouts on the page
sequentially with progress indicator
- Add @vitejs/plugin-basic-ssl for local HTTPS dev (opt-in via HTTPS=1)
Also adds unit test for FIT-to-GPX conversion with real fixture file,
and updates wahoo-import spec to reflect all behaviors.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Split initI18n() into separate server/client initialization paths
inspired by remix-i18next's architecture:
- initI18nServer(lng): no LanguageDetector, per-request language from
Accept-Language header
- initI18nClient(): LanguageDetector reads <html lang> first to match
server, then localStorage and navigator
- detectLanguage(request): parses Accept-Language for best match
Fixes rehydration errors caused by v26 removing initImmediate option
(replaced with initAsync) and LanguageDetector running on the server
where browser APIs don't exist.
Also makes <html lang> dynamic instead of hardcoded "en".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Health endpoints:
- /api/health (Journal) and /health (Planner) with DB connectivity check
- Docker healthchecks updated to use app health endpoints
Structured logging:
- Pino with JSON output in production, pretty-print in dev
- Request logging middleware in Planner (method, path, status, duration)
- Replaced console.log/error with structured logger in email and auth flows
Prometheus metrics:
- prom-client with default Node.js metrics + custom histograms/gauges
- /metrics endpoints on both apps
- http_request_duration, planner_active_sessions, brouter_request_duration
Monitoring stack:
- Prometheus, Loki, Grafana containers in docker-compose
- Grafana provisioned with datasources, dashboards, and alert rules
- Caddy access logging (JSON to stdout for Loki)
- grafana.trails.cool with basic auth via Caddy
Dashboards and alerting:
- Overview: request rate, error rate, latency p50/p95/p99
- Planner: active sessions, connected clients, BRouter latency
- Infrastructure: memory, CPU, event loop lag
- Alerts: disk >80%, app down 2min, error rate >5%
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Transactional emails:
- Add nodemailer SMTP email module with dev-mode console logging
- Magic link template and welcome template with HTML + plain text
- Wire sendMagicLink into login flow, sendWelcome into registration
- Update privacy page and deploy docs for SMTP configuration
Planner features:
- No-go areas: draw polygons on map (leaflet-geoman), synced via Yjs,
passed to BRouter as nogos parameter, route recomputes on change
- Session notes: collaborative Y.Text textarea in sidebar tab
- Crash recovery: periodic localStorage save of Yjs state, restore on reconnect
- Rate limit session creation (10/IP/hour) in /new route
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move initI18n() from entry.client.tsx to root.tsx in both Planner and
Journal so i18n is initialized before any component renders. Downgrade
react-i18next to 16.6.1 and i18next to 25.10.4 to avoid hydration
mismatch issues introduced in newer versions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- @sentry/vite-plugin uploads source maps during Docker build
- Release tagged with git SHA (SENTRY_RELEASE) on both client and server
- Environment set to production/development for filtering
- CD passes SENTRY_AUTH_TOKEN + SENTRY_RELEASE as Docker build args
- docker-compose passes SENTRY_RELEASE to runtime containers
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Planner (client + custom server) and Journal (client + entry.server):
- Client: @sentry/react with browser tracing + replay on error
- Server: @sentry/node for unhandled exceptions
- ErrorBoundary captures React errors via Sentry.captureException
- Disabled in dev, 10% trace sample rate in production
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three issues fixed:
1. GPX parser used browser DOMParser which doesn't exist in Node/Vite SSR.
Added async parseGpxAsync() using linkedom for server-side parsing.
2. Server-side session initialization stored waypoints in a Yjs doc
instance separate from the Vite WebSocket plugin's doc store.
Moved waypoint initialization to client-side: API returns parsed
waypoints, client adds them to Yjs after sync.
3. GPX was encoded in URL params causing HTTP 431. Now the Journal
creates a Planner session via API (POST body), and only passes
compact waypoint coordinates in URL params.
Verified: Journal route → Edit in Planner → 4 waypoints loaded,
route computed (79.3km), elevation profile, Save to Journal ready.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
JWT-based handoff between Journal and Planner:
Journal side:
- JWT token generation scoped to route_id with 7-day expiry (jose)
- "Edit in Planner" button on route detail page — generates JWT,
redirects to Planner /new with callback URL, token, and GPX
- Callback endpoint (POST /api/routes/:id/callback) validates JWT
and creates new route version from received GPX
Planner side:
- /new route accepts callback, token, returnUrl, gpx params
- Creates session with callback metadata, initializes with GPX
- "Save to Journal" button POSTs GPX with Bearer token to callback
- "Return to Journal" link shown after successful save
All 6 Group 9 tasks complete.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add Vite plugin implementing y-protocols sync server for dev mode
- Fix useYjs hook: move provider creation into useEffect to survive
React StrictMode remounts
- Fix WebSocket URL: use /sync as base (y-websocket appends room name)
- Verified in browser: Connected status, Host badge, awareness working
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Override esbuild >=0.25.0 to fix CVE in dev server that allows
cross-origin requests. The vulnerable 0.18.20 was pulled in
transitively via drizzle-kit → @esbuild-kit.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Yjs WebSocket server at /sync/:sessionId for real-time collaboration
- Session creation API (POST /api/sessions) with optional GPX initialization
- Session join page with Planner layout (header, map area, sidebar)
- In-memory session store (PostgreSQL persistence deferred to infra setup)
- Custom production server entry with WebSocket upgrade handling
- Vite path aliases (~/) for clean imports
- Fix tsconfig for apps: noEmit, exclude build dir
Tasks completed: 4.1, 4.3, 4.4, 4.5
Deferred: 4.2 (PostgreSQL), 4.6-4.8 (need client-side Yjs from Group 6)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>