Complete mobile app specs: fill all gaps
Design fixes: - D6: Cleaned up, points to separate activity-recording change - D13: Locked in TanStack Query + Zustand + React Context - D14: Tile hosting (OpenFreeMap default, configurable tileUrl) - D15: Photo/media (presigned upload URLs, thumbnails) - D16: Journal REST API implementation (api.v1.*.ts route modules) New specs (4): - web-push-relay: Web Push → APNs/FCM relay (Mastodon pattern) - api-contract-package: @trails-cool/api with Zod schemas - device-management: Connected devices list + revoke - photo-media: Presigned uploads, thumbnails, photo display Task updates: - Added Phase 2: Journal REST API (16 tasks) - Added Phase 7: Notifications (8 tasks) - Renumbered all phases (1-7) - 112 total tasks Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f6e46d871b
commit
cd939ccf07
6 changed files with 352 additions and 104 deletions
|
|
@ -96,14 +96,9 @@ No real-time collaboration on mobile. If the user needs full collaborative editi
|
|||
- Activities: Stored locally first, synced to Journal when online
|
||||
- Edit queue: Changes made offline queued and synced on reconnect (conflict resolution: last-write-wins, with a warning if the server version changed)
|
||||
|
||||
### D6: Activity recording with expo-location
|
||||
### D6: Activity recording
|
||||
|
||||
Use `expo-location` for GPS tracking in the foreground/background:
|
||||
- Start/stop recording from the active route view
|
||||
- Live stats: distance, duration, current speed, elevation gain
|
||||
- Track points stored locally, converted to GPX on stop
|
||||
- Save as Journal activity linked to the route
|
||||
- Export to HealthKit (iOS) / Health Connect (Android) via `expo-health`
|
||||
Activity recording (GPS tracking, HealthKit) is a separate change — see `mobile-activity-recording`.
|
||||
|
||||
### D7: Navigation structure
|
||||
|
||||
|
|
@ -145,7 +140,40 @@ This aligns with the ActivityPub federation direction and matches how Mastodon c
|
|||
|
||||
### D13: State management
|
||||
|
||||
TBD — to be discussed.
|
||||
Three layers, each with a clear responsibility:
|
||||
|
||||
- **TanStack Query** for server state — routes, activities, user profile. Provides caching, background refetch, stale-while-revalidate, cursor pagination helpers, and optimistic updates. All Journal API data flows through TanStack Query hooks.
|
||||
- **Zustand** for local UI state — edit mode flags, recording state, offline queue status, selected map layer, download progress. Small, synchronous stores with no persistence (or optional persistence to AsyncStorage where needed).
|
||||
- **React Context** only for auth token and server URL — these are set once at login and consumed everywhere. No complex state, no frequent updates. Avoids prop drilling for the two values every API call needs.
|
||||
|
||||
### D14: Tile hosting
|
||||
|
||||
MapLibre needs a vector tile source. Default to **OpenFreeMap** — free, OSM-based, no API key required. This matches the project's principle of using open standards and avoiding vendor lock-in.
|
||||
|
||||
Self-hosted instances can configure a custom tile URL in the discovery endpoint. The `/.well-known/trails-cool` response gains a `tileUrl` field (e.g., `"tileUrl": "https://tiles.example.org/{z}/{x}/{y}.pbf"`). When present, the mobile app and any future MapLibre web client use it instead of the default.
|
||||
|
||||
Fallback: if vector tiles fail to load (offline, misconfigured URL), fall back to raster tiles from OpenStreetMap tile servers — the same approach the web Planner already uses with Leaflet.
|
||||
|
||||
### D15: Photo/media handling
|
||||
|
||||
Routes and activities can have photos. The upload flow avoids proxying large files through the Journal server:
|
||||
|
||||
1. Client requests a presigned upload URL from `POST /api/v1/uploads` with filename and content type
|
||||
2. Client uploads the file directly to S3/Garage using the presigned URL
|
||||
3. Client confirms the upload via the API with the storage key (e.g., `POST /api/v1/routes/:id` or `PUT /api/v1/routes/:id` with the storage key in the photos array)
|
||||
|
||||
Photos are stored as an array of URLs on the route or activity record. Thumbnails are generated server-side on upload confirmation — the Journal creates a resized version and stores it alongside the original. The API returns both `url` and `thumbnailUrl` for each photo.
|
||||
|
||||
### D16: Journal REST API implementation
|
||||
|
||||
The REST API endpoints (`/api/v1/*`) are implemented as React Router route modules under `apps/journal/app/routes/api.v1.*.ts`. This follows the existing routing pattern — both apps use explicit `routes.ts` for registration.
|
||||
|
||||
Shared middleware handles:
|
||||
- **Bearer token validation**: Extracts and validates the OAuth2 access token from the `Authorization` header, attaches the authenticated user to the request context
|
||||
- **Zod body parsing**: Validates request bodies against schemas from `@trails-cool/api`, returns structured 400 errors on failure
|
||||
- **Error formatting**: Catches exceptions and returns the standard `ApiErrorResponse` shape
|
||||
|
||||
Existing web routes (cookie-based auth, form actions, loaders) remain unchanged. The `/api/v1/*` routes use bearer tokens exclusively — no cookie sessions.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
## ADDED Requirements
|
||||
|
||||
### Requirement: Zod schemas as source of truth
|
||||
The `@trails-cool/api` package SHALL define all API request and response shapes as Zod schemas, with TypeScript types inferred via `z.infer<>`.
|
||||
|
||||
#### Scenario: Server validates request body
|
||||
- **WHEN** the Journal server receives a request body for a known endpoint
|
||||
- **THEN** it validates the body using the corresponding Zod schema from `@trails-cool/api` and returns a structured 400 error on failure
|
||||
|
||||
#### Scenario: Client validates response
|
||||
- **WHEN** the mobile client receives a response from a Journal instance (especially self-hosted on an older version)
|
||||
- **THEN** it can optionally validate the response against the Zod schema to detect incompatibilities
|
||||
|
||||
### Requirement: API version constant
|
||||
The package SHALL export an `API_VERSION` semver constant as the single source of truth for the current API version.
|
||||
|
||||
#### Scenario: Version bump in one place
|
||||
- **WHEN** the API version needs to be bumped (new endpoint, new field, breaking change)
|
||||
- **THEN** the version is changed in `@trails-cool/api` and both the Journal server and mobile client see it at compile time
|
||||
|
||||
### Requirement: Endpoint path constants
|
||||
The package SHALL export typed constants for all API endpoint paths.
|
||||
|
||||
#### Scenario: Endpoint paths used by server and client
|
||||
- **WHEN** the server registers a route or the client constructs a URL
|
||||
- **THEN** both import the path from `@trails-cool/api` (e.g., `ENDPOINTS.routes.list` resolves to `"/api/v1/routes"`)
|
||||
|
||||
### Requirement: Request and response schemas
|
||||
The package SHALL define Zod schemas for all API endpoints.
|
||||
|
||||
#### Scenario: Routes schemas
|
||||
- **WHEN** a route-related endpoint is called
|
||||
- **THEN** schemas exist for `RouteListResponse`, `RouteDetailResponse`, `CreateRouteRequest`, `UpdateRouteRequest`
|
||||
|
||||
#### Scenario: Activities schemas
|
||||
- **WHEN** an activity-related endpoint is called
|
||||
- **THEN** schemas exist for `ActivityListResponse`, `ActivityDetailResponse`, `CreateActivityRequest`
|
||||
|
||||
#### Scenario: Auth schemas
|
||||
- **WHEN** an auth-related endpoint is called
|
||||
- **THEN** schemas exist for `TokenExchangeRequest`, `TokenResponse`, `DiscoveryResponse`
|
||||
|
||||
#### Scenario: Upload schemas
|
||||
- **WHEN** an upload-related endpoint is called
|
||||
- **THEN** schemas exist for `PresignedUploadRequest`, `PresignedUploadResponse`
|
||||
|
||||
### Requirement: Error response schema
|
||||
The package SHALL define a standard error response schema used by all endpoints.
|
||||
|
||||
#### Scenario: Error shape
|
||||
- **WHEN** any API endpoint returns an error
|
||||
- **THEN** the response matches the `ApiErrorResponse` schema: `{ error: string, code: string, fields?: Array<{ field: string, message: string }> }`
|
||||
34
openspec/changes/mobile-app/specs/device-management/spec.md
Normal file
34
openspec/changes/mobile-app/specs/device-management/spec.md
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
## ADDED Requirements
|
||||
|
||||
### Requirement: Connected devices list
|
||||
The Journal web settings page SHALL display a list of connected devices (active OAuth2 sessions).
|
||||
|
||||
#### Scenario: View connected devices
|
||||
- **WHEN** the user opens the account settings page on the Journal web UI
|
||||
- **THEN** a "Connected Devices" section lists all active OAuth2 tokens with device name, last active timestamp, and approximate location (derived from IP)
|
||||
|
||||
#### Scenario: Device shows last active time
|
||||
- **WHEN** a device makes an API request using its OAuth2 token
|
||||
- **THEN** the Journal updates the token's last active timestamp and IP address
|
||||
|
||||
### Requirement: Device metadata on token
|
||||
Each OAuth2 token SHALL store device name, last active timestamp, and IP address.
|
||||
|
||||
#### Scenario: Device name stored on token exchange
|
||||
- **WHEN** the mobile app exchanges an authorization code for tokens via `POST /oauth/token`
|
||||
- **THEN** the device name (sent in the request body, e.g., "iPhone 15, iOS 18") is stored alongside the token record
|
||||
|
||||
#### Scenario: Last active updated on API use
|
||||
- **WHEN** a bearer token is used to authenticate an API request
|
||||
- **THEN** the token's `lastActiveAt` and `lastActiveIp` fields are updated
|
||||
|
||||
### Requirement: Revoke individual device tokens
|
||||
Users SHALL be able to revoke individual device sessions from the Journal web settings page.
|
||||
|
||||
#### Scenario: Revoke device from web
|
||||
- **WHEN** the user clicks "Revoke" on a connected device in the Journal settings
|
||||
- **THEN** the OAuth2 token for that device is deleted and all subsequent API requests from that device fail with 401
|
||||
|
||||
#### Scenario: Revoke device from API
|
||||
- **WHEN** a client sends `DELETE /api/v1/auth/devices/:id` with a valid bearer token
|
||||
- **THEN** the targeted device token is revoked (the requesting device can revoke other devices, not itself via this endpoint)
|
||||
41
openspec/changes/mobile-app/specs/photo-media/spec.md
Normal file
41
openspec/changes/mobile-app/specs/photo-media/spec.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
## ADDED Requirements
|
||||
|
||||
### Requirement: Presigned upload URL
|
||||
The Journal API SHALL provide presigned upload URLs for direct-to-storage file uploads.
|
||||
|
||||
#### Scenario: Request upload URL
|
||||
- **WHEN** a client sends `POST /api/v1/uploads` with filename and content type
|
||||
- **THEN** the server returns a presigned upload URL for S3/Garage and a storage key to reference the uploaded file
|
||||
|
||||
#### Scenario: Upload photo directly to storage
|
||||
- **WHEN** the client receives a presigned URL
|
||||
- **THEN** the client uploads the file directly to S3/Garage using an HTTP PUT to the presigned URL, bypassing the Journal server
|
||||
|
||||
### Requirement: Photo attachment on routes and activities
|
||||
Routes and activities SHALL support an array of attached photos.
|
||||
|
||||
#### Scenario: Attach photo to route
|
||||
- **WHEN** a client creates or updates a route with storage keys in the photos array
|
||||
- **THEN** the route record stores the photo references and they are returned in subsequent GET requests with `url` and `thumbnailUrl`
|
||||
|
||||
#### Scenario: Attach photo to activity
|
||||
- **WHEN** a client creates an activity with storage keys in the photos array
|
||||
- **THEN** the activity record stores the photo references and they are returned in subsequent GET requests
|
||||
|
||||
### Requirement: Server-side thumbnail generation
|
||||
The Journal SHALL generate thumbnails when a photo upload is confirmed.
|
||||
|
||||
#### Scenario: Thumbnail created on upload confirmation
|
||||
- **WHEN** a route or activity is saved with a new photo storage key
|
||||
- **THEN** the server reads the original image from storage, generates a resized thumbnail, stores it alongside the original, and records both URLs
|
||||
|
||||
### Requirement: Photo display
|
||||
Clients SHALL display photos in route and activity detail views.
|
||||
|
||||
#### Scenario: View photos in route detail
|
||||
- **WHEN** a client fetches a route with attached photos
|
||||
- **THEN** the response includes an array of photo objects with `url` (full size) and `thumbnailUrl` (resized)
|
||||
|
||||
#### Scenario: Delete photo
|
||||
- **WHEN** a client updates a route or activity and removes a storage key from the photos array
|
||||
- **THEN** the photo and its thumbnail are deleted from storage on save
|
||||
42
openspec/changes/mobile-app/specs/web-push-relay/spec.md
Normal file
42
openspec/changes/mobile-app/specs/web-push-relay/spec.md
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
## ADDED Requirements
|
||||
|
||||
### Requirement: Web Push API on Journal
|
||||
The Journal SHALL implement the standard Web Push API (RFC 8030) for sending push notifications to subscribed clients.
|
||||
|
||||
#### Scenario: Subscribe to push notifications
|
||||
- **WHEN** a client sends `POST /api/v1/push/subscribe` with a push subscription object (endpoint URL, p256dh key, auth secret)
|
||||
- **THEN** the Journal stores the subscription and uses it to deliver future notifications for that user
|
||||
|
||||
#### Scenario: Receive route update notification
|
||||
- **WHEN** a shared route is updated by another user
|
||||
- **THEN** the Journal sends an encrypted Web Push message to all subscribers of that route's owner
|
||||
|
||||
#### Scenario: Unsubscribe from push notifications
|
||||
- **WHEN** a client sends `DELETE /api/v1/push/unsubscribe` with the subscription endpoint URL
|
||||
- **THEN** the Journal removes the subscription and stops sending notifications to it
|
||||
|
||||
### Requirement: Push relay service
|
||||
A relay service SHALL translate Web Push messages into platform-native push notifications (APNs for iOS, FCM for Android).
|
||||
|
||||
#### Scenario: Relay forwards encrypted payload to APNs
|
||||
- **WHEN** the relay receives a Web Push message destined for an iOS device
|
||||
- **THEN** the relay forwards the encrypted payload to APNs without decrypting or reading the content
|
||||
|
||||
#### Scenario: Relay forwards encrypted payload to FCM
|
||||
- **WHEN** the relay receives a Web Push message destined for an Android device
|
||||
- **THEN** the relay forwards the encrypted payload to FCM without decrypting or reading the content
|
||||
|
||||
#### Scenario: End-to-end encryption
|
||||
- **WHEN** a notification is sent from the Journal through the relay to the device
|
||||
- **THEN** the relay cannot read the notification content — only the mobile app can decrypt it using the subscription keys
|
||||
|
||||
### Requirement: Self-hosted relay compatibility
|
||||
Self-hosted Journal instances SHALL send push notifications through the hosted relay by default, with an option to run a private relay.
|
||||
|
||||
#### Scenario: Default relay for self-hosted instances
|
||||
- **WHEN** a self-hosted Journal instance sends a Web Push notification
|
||||
- **THEN** it sends the standard Web Push request to the trails.cool hosted relay (no Apple/Google credentials needed on the self-hosted instance)
|
||||
|
||||
#### Scenario: Custom relay for self-hosters
|
||||
- **WHEN** a self-hosted administrator configures a custom relay URL
|
||||
- **THEN** the Journal sends Web Push requests to the custom relay instead of the hosted one
|
||||
|
|
@ -19,131 +19,182 @@
|
|||
|
||||
### 1.3 Shared Package Compatibility
|
||||
|
||||
- [ ] 1.2.1 Audit `@trails-cool/types` for DOM/Node.js dependencies — confirm it's pure TypeScript interfaces
|
||||
- [ ] 1.2.2 Refactor `@trails-cool/gpx` to use a platform-agnostic XML parser: use `linkedom` on Node.js and `DOMParser` on React Native/browser
|
||||
- [ ] 1.2.3 Write unit tests for `parseGpx()` and `generateGpx()` running in a jsdom-free environment to verify no DOM dependency
|
||||
- [ ] 1.2.4 Verify `@trails-cool/i18n` initializes in React Native — add an `initMobile()` export if the current init assumes a browser environment
|
||||
- [ ] 1.2.5 Add `apps/mobile` to Turborepo pipeline (`turbo.json`) for build and typecheck
|
||||
- [ ] 1.3.1 Audit `@trails-cool/types` for DOM/Node.js dependencies — confirm it's pure TypeScript interfaces
|
||||
- [ ] 1.3.2 Refactor `@trails-cool/gpx` to use a platform-agnostic XML parser: use `linkedom` on Node.js and `DOMParser` on React Native/browser
|
||||
- [ ] 1.3.3 Write unit tests for `parseGpx()` and `generateGpx()` running in a jsdom-free environment to verify no DOM dependency
|
||||
- [ ] 1.3.4 Verify `@trails-cool/i18n` initializes in React Native — add an `initMobile()` export if the current init assumes a browser environment
|
||||
- [ ] 1.3.5 Add `apps/mobile` to Turborepo pipeline (`turbo.json`) for build and typecheck
|
||||
|
||||
### 1.3 Journal Auth (OAuth2 PKCE)
|
||||
### 1.4 Journal Auth (OAuth2 PKCE)
|
||||
|
||||
- [ ] 1.3.1 Add `journal.oauth_clients` table with client_id, redirect_uri, and trusted flag
|
||||
- [ ] 1.3.2 Add `journal.oauth_codes` table (code, userId, clientId, codeChallenge, codeChallengeMethod, expiresAt)
|
||||
- [ ] 1.3.3 Add `journal.oauth_tokens` table (accessToken, refreshToken, userId, clientId, expiresAt)
|
||||
- [ ] 1.3.4 Implement `GET /oauth/authorize` endpoint — show login UI, generate auth code on success, redirect to client
|
||||
- [ ] 1.3.5 Implement `POST /oauth/token` endpoint — validate PKCE code_verifier, issue access + refresh tokens
|
||||
- [ ] 1.3.6 Implement refresh token grant in `POST /oauth/token`
|
||||
- [ ] 1.3.7 Add middleware to validate OAuth2 bearer tokens on existing API routes
|
||||
- [ ] 1.3.8 Seed `trails-cool-mobile` as a trusted first-party OAuth2 client with `trailscool://` redirect URI
|
||||
- [ ] 1.3.9 Write unit tests for PKCE challenge validation, token issuance, and token refresh
|
||||
- [ ] 1.4.1 Add `journal.oauth_clients` table with client_id, redirect_uri, and trusted flag
|
||||
- [ ] 1.4.2 Add `journal.oauth_codes` table (code, userId, clientId, codeChallenge, codeChallengeMethod, expiresAt)
|
||||
- [ ] 1.4.3 Add `journal.oauth_tokens` table (accessToken, refreshToken, userId, clientId, expiresAt)
|
||||
- [ ] 1.4.4 Implement `GET /oauth/authorize` endpoint — show login UI, generate auth code on success, redirect to client
|
||||
- [ ] 1.4.5 Implement `POST /oauth/token` endpoint — validate PKCE code_verifier, issue access + refresh tokens
|
||||
- [ ] 1.4.6 Implement refresh token grant in `POST /oauth/token`
|
||||
- [ ] 1.4.7 Add middleware to validate OAuth2 bearer tokens on existing API routes
|
||||
- [ ] 1.4.8 Seed `trails-cool-mobile` as a trusted first-party OAuth2 client with `trailscool://` redirect URI
|
||||
- [ ] 1.4.9 Write unit tests for PKCE challenge validation, token issuance, and token refresh
|
||||
|
||||
### 1.4 Server Configuration
|
||||
### 1.5 Server Configuration
|
||||
|
||||
- [ ] 1.4.1 Add server URL input on login screen with "Connect to a different server" toggle (defaults to `https://trails.cool`)
|
||||
- [ ] 1.4.2 Add `GET /.well-known/trails-cool` discovery endpoint on the Journal — returns instance name, version, API base URL
|
||||
- [ ] 1.4.3 Validate entered server URL by fetching discovery endpoint before proceeding to login
|
||||
- [ ] 1.4.4 Check `apiVersion` semver from discovery against app's required minimum — block with upgrade prompt if server is too old
|
||||
- [ ] 1.4.5 Persist server URL in Expo SecureStore, use as base for all API calls and OAuth2 flow
|
||||
- [ ] 1.4.6 Re-check API version on app foreground (after background) — show banner if version mismatch detected
|
||||
- [ ] 1.4.5 Add "Switch server" option on Profile tab — logs out, clears local data, returns to login
|
||||
- [ ] 1.5.1 Add server URL input on login screen with "Connect to a different server" toggle (defaults to `https://trails.cool`)
|
||||
- [ ] 1.5.2 Add `GET /.well-known/trails-cool` discovery endpoint on the Journal — returns instance name, version, API base URL
|
||||
- [ ] 1.5.3 Validate entered server URL by fetching discovery endpoint before proceeding to login
|
||||
- [ ] 1.5.4 Check `apiVersion` semver from discovery against app's required minimum — block with upgrade prompt if server is too old
|
||||
- [ ] 1.5.5 Persist server URL in Expo SecureStore, use as base for all API calls and OAuth2 flow
|
||||
- [ ] 1.5.6 Re-check API version on app foreground (after background) — show banner if version mismatch detected
|
||||
- [ ] 1.5.7 Add "Switch server" option on Profile tab — logs out, clears local data, returns to login
|
||||
|
||||
### 1.5 Journal API Client (Mobile)
|
||||
### 1.6 Journal API Client (Mobile)
|
||||
|
||||
- [ ] 1.4.1 Create `apps/mobile/src/lib/api-client.ts` with base HTTP client, auth header injection, and 401 auto-refresh logic
|
||||
- [ ] 1.4.2 Create `apps/mobile/src/lib/auth.ts` with OAuth2 PKCE login flow using `expo-auth-session` and token storage via `expo-secure-store`
|
||||
- [ ] 1.4.3 Implement routes API methods: listRoutes, getRoute, updateRoute, createRoute
|
||||
- [ ] 1.4.4 Implement activities API methods: listActivities, getActivity, createActivity
|
||||
- [ ] 1.4.5 Add typed error handling for network failures, 401, and server errors
|
||||
- [ ] 1.4.6 Write unit tests for API client with mocked fetch responses
|
||||
- [ ] 1.6.1 Create `apps/mobile/src/lib/api-client.ts` with base HTTP client, auth header injection, and 401 auto-refresh logic
|
||||
- [ ] 1.6.2 Create `apps/mobile/src/lib/auth.ts` with OAuth2 PKCE login flow using `expo-auth-session` and token storage via `expo-secure-store`
|
||||
- [ ] 1.6.3 Implement routes API methods: listRoutes, getRoute, updateRoute, createRoute
|
||||
- [ ] 1.6.4 Implement activities API methods: listActivities, getActivity, createActivity
|
||||
- [ ] 1.6.5 Add typed error handling for network failures, 401, and server errors
|
||||
- [ ] 1.6.6 Write unit tests for API client with mocked fetch responses
|
||||
|
||||
## Phase 2: Routes
|
||||
## Phase 2: Journal REST API
|
||||
|
||||
### 2.1 Route List
|
||||
### 2.1 Auth & Discovery Endpoints
|
||||
|
||||
- [ ] 2.1.1 Build Routes tab screen with paginated route list fetched from Journal API
|
||||
- [ ] 2.1.2 Display route cards with name, distance, elevation, and thumbnail map preview
|
||||
- [ ] 2.1.3 Add pull-to-refresh and loading/error states
|
||||
- [ ] 2.1.4 Add i18n keys for route list strings (en + de)
|
||||
- [ ] 2.1.1 Implement bearer token auth middleware for `/api/v1/*` routes
|
||||
- [ ] 2.1.2 Implement `GET /.well-known/trails-cool` discovery endpoint (apiVersion, instanceName, tileUrl, apiBaseUrl)
|
||||
- [ ] 2.1.3 Implement `POST /api/v1/auth/token` (OAuth2 code-to-token exchange, refresh grant)
|
||||
|
||||
### 2.2 Route Detail
|
||||
### 2.2 Routes Endpoints
|
||||
|
||||
- [ ] 2.2.1 Build route detail screen with `react-native-maps` showing the route polyline and waypoint markers
|
||||
- [ ] 2.2.2 Display route metadata: name, distance, elevation gain, number of days, waypoint list
|
||||
- [ ] 2.2.3 Add "Edit", "Download Offline", and "Edit in Planner" action buttons
|
||||
- [ ] 2.2.4 Style map markers and polyline colors to match the web Planner
|
||||
- [ ] 2.2.1 Implement `GET /api/v1/routes` with cursor-based pagination
|
||||
- [ ] 2.2.2 Implement `GET /api/v1/routes/:id` with full route detail (GPX, waypoints, versions)
|
||||
- [ ] 2.2.3 Implement `POST /api/v1/routes` to create a new route
|
||||
- [ ] 2.2.4 Implement `PUT /api/v1/routes/:id` to update a route (new version)
|
||||
- [ ] 2.2.5 Implement `DELETE /api/v1/routes/:id`
|
||||
|
||||
### 2.3 Route Editing
|
||||
### 2.3 Activities Endpoints
|
||||
|
||||
- [ ] 2.3.1 Implement add-waypoint via long-press on map, inserting at the nearest route segment
|
||||
- [ ] 2.3.2 Implement drag-to-move for waypoint markers
|
||||
- [ ] 2.3.3 Implement waypoint deletion with confirmation
|
||||
- [ ] 2.3.4 Add overnight stop toggle in waypoint detail sheet
|
||||
- [ ] 2.3.5 Add POI snap suggestions when adding waypoints near known POIs
|
||||
- [ ] 2.3.6 Integrate BRouter routing via Journal API proxy — recompute route segments on waypoint changes
|
||||
- [ ] 2.3.7 Implement save: generate GPX from current waypoints + geometry, PUT to Journal API
|
||||
- [ ] 2.3.8 Add unsaved-changes guard when navigating away from the editor
|
||||
- [ ] 2.3.1 Implement `GET /api/v1/activities` with cursor-based pagination
|
||||
- [ ] 2.3.2 Implement `GET /api/v1/activities/:id` with full activity detail
|
||||
- [ ] 2.3.3 Implement `POST /api/v1/activities` to create a new activity
|
||||
- [ ] 2.3.4 Implement `DELETE /api/v1/activities/:id`
|
||||
|
||||
## Phase 3: Testing
|
||||
### 2.4 Supporting Endpoints
|
||||
|
||||
### 3.1 Unit & Component Tests
|
||||
- [ ] 2.4.1 Implement `POST /api/v1/routes/compute` BRouter proxy endpoint
|
||||
- [ ] 2.4.2 Implement `POST /api/v1/uploads` presigned URL endpoint
|
||||
|
||||
- [ ] 3.1.1 Set up Jest + jest-expo + React Native Testing Library in `apps/mobile/`
|
||||
- [ ] 3.1.2 Write unit tests for API client (mocked fetch, auth refresh, error handling)
|
||||
- [ ] 3.1.3 Write component tests for route list, route detail, and route editor screens
|
||||
- [ ] 3.1.4 Write unit tests for offline SQLite storage layer
|
||||
- [ ] 3.1.5 Add test script to Turborepo pipeline
|
||||
### 2.5 Device Management
|
||||
|
||||
### 3.2 E2E Tests (Maestro)
|
||||
- [ ] 2.5.1 Store device name and last active timestamp on OAuth2 token use
|
||||
- [ ] 2.5.2 Implement `GET /api/v1/auth/devices` — list connected devices for the authenticated user
|
||||
- [ ] 2.5.3 Implement `DELETE /api/v1/auth/devices/:id` — revoke a device token
|
||||
|
||||
- [ ] 3.2.1 Install Maestro CLI and create `apps/mobile/.maestro/` test directory
|
||||
- [ ] 3.2.2 Write Maestro flow: login → see route list → open route detail
|
||||
- [ ] 3.2.3 Write Maestro flow: edit route → add waypoint → save
|
||||
- [ ] 3.2.4 Write Maestro flow: download route for offline → toggle airplane mode → view cached route
|
||||
- [ ] 3.2.5 Configure Maestro CI integration with EAS Build (run E2E on preview builds)
|
||||
### 2.6 Validation & Testing
|
||||
|
||||
## Phase 4: Offline
|
||||
- [ ] 2.6.1 Add Zod validation middleware using schemas from `@trails-cool/api`
|
||||
- [ ] 2.6.2 Unit tests for all API endpoints
|
||||
|
||||
### 4.1 Route Download
|
||||
## Phase 3: Routes
|
||||
|
||||
- [ ] 4.1.1 Set up Expo SQLite database with tables for offline routes, waypoints, and edit queue
|
||||
- [ ] 4.1.2 Implement route download: fetch GPX + metadata from API, store in SQLite
|
||||
- [ ] 4.1.3 Build download progress UI with cancel support
|
||||
- [ ] 4.1.4 Implement offline route loading — detect network state and load from SQLite when offline
|
||||
### 3.1 Route List
|
||||
|
||||
### 4.2 Tile Cache
|
||||
- [ ] 3.1.1 Build Routes tab screen with paginated route list fetched from Journal API
|
||||
- [ ] 3.1.2 Display route cards with name, distance, elevation, and thumbnail map preview
|
||||
- [ ] 3.1.3 Add pull-to-refresh and loading/error states
|
||||
- [ ] 3.1.4 Add i18n keys for route list strings (en + de)
|
||||
|
||||
- [ ] 4.2.1 Implement tile download manager: given a route bounding box, download tiles at zoom levels 10-15
|
||||
- [ ] 4.2.2 Store tiles in the file system via `expo-file-system` with a lookup index in SQLite
|
||||
- [ ] 4.2.3 Configure `react-native-maps` to use cached tiles when offline
|
||||
- [ ] 4.2.4 Add storage budget display and management on Profile tab (total cached size, delete individual routes)
|
||||
### 3.2 Route Detail
|
||||
|
||||
### 4.3 Offline Edit Queue
|
||||
- [ ] 3.2.1 Build route detail screen with `react-native-maps` showing the route polyline and waypoint markers
|
||||
- [ ] 3.2.2 Display route metadata: name, distance, elevation gain, number of days, waypoint list
|
||||
- [ ] 3.2.3 Add "Edit", "Download Offline", and "Edit in Planner" action buttons
|
||||
- [ ] 3.2.4 Style map markers and polyline colors to match the web Planner
|
||||
|
||||
- [ ] 4.3.1 Implement edit queue in SQLite: store pending route edits with timestamps
|
||||
- [ ] 4.3.2 Add sync-pending indicator on routes with queued edits
|
||||
- [ ] 4.3.3 Implement sync-on-reconnect: process queued edits in order via Journal API
|
||||
- [ ] 4.3.4 Handle sync conflicts: warn user if server version changed, apply last-write-wins
|
||||
### 3.3 Route Editing
|
||||
|
||||
## Phase 5: Polish
|
||||
- [ ] 3.3.1 Implement add-waypoint via long-press on map, inserting at the nearest route segment
|
||||
- [ ] 3.3.2 Implement drag-to-move for waypoint markers
|
||||
- [ ] 3.3.3 Implement waypoint deletion with confirmation
|
||||
- [ ] 3.3.4 Add overnight stop toggle in waypoint detail sheet
|
||||
- [ ] 3.3.5 Add POI snap suggestions when adding waypoints near known POIs
|
||||
- [ ] 3.3.6 Integrate BRouter routing via Journal API proxy — recompute route segments on waypoint changes
|
||||
- [ ] 3.3.7 Implement save: generate GPX from current waypoints + geometry, PUT to Journal API
|
||||
- [ ] 3.3.8 Add unsaved-changes guard when navigating away from the editor
|
||||
|
||||
### 5.1 Deep Links
|
||||
## Phase 4: Testing
|
||||
|
||||
- [ ] 5.1.1 Configure `trailscool://` URL scheme in `app.config.ts` for iOS and Android
|
||||
- [ ] 5.1.2 Implement deep link handler: parse `trailscool://routes/:id` and `trailscool://activities/:id`, navigate to detail screens
|
||||
- [ ] 5.1.3 Implement "Edit in Planner" flow: open Planner URL in browser with JWT callback, handle return deep link
|
||||
- [ ] 5.1.4 Add universal links / App Links for `https://trails.cool/routes/:id` (optional, requires associated domains)
|
||||
### 4.1 Unit & Component Tests
|
||||
|
||||
### 5.2 Push Notifications
|
||||
- [ ] 4.1.1 Set up Jest + jest-expo + React Native Testing Library in `apps/mobile/`
|
||||
- [ ] 4.1.2 Write unit tests for API client (mocked fetch, auth refresh, error handling)
|
||||
- [ ] 4.1.3 Write component tests for route list, route detail, and route editor screens
|
||||
- [ ] 4.1.4 Write unit tests for offline SQLite storage layer
|
||||
- [ ] 4.1.5 Add test script to Turborepo pipeline
|
||||
|
||||
- [ ] 5.2.1 Set up `expo-notifications` with push token registration
|
||||
- [ ] 5.2.2 Add Journal API endpoint to register device push tokens
|
||||
- [ ] 5.2.3 Send push notifications for route updates on shared routes (Journal server-side)
|
||||
- [ ] 5.2.4 Handle notification taps — deep link to the relevant route or activity
|
||||
### 4.2 E2E Tests (Maestro)
|
||||
|
||||
### 5.3 App Store Preparation
|
||||
- [ ] 4.2.1 Install Maestro CLI and create `apps/mobile/.maestro/` test directory
|
||||
- [ ] 4.2.2 Write Maestro flow: login → see route list → open route detail
|
||||
- [ ] 4.2.3 Write Maestro flow: edit route → add waypoint → save
|
||||
- [ ] 4.2.4 Write Maestro flow: download route for offline → toggle airplane mode → view cached route
|
||||
- [ ] 4.2.5 Configure Maestro CI integration with EAS Build (run E2E on preview builds)
|
||||
|
||||
- [ ] 5.3.1 Write App Store and Play Store descriptions (en + de) with screenshots
|
||||
- [ ] 5.3.2 Add privacy nutrition labels for iOS (location, health, network) and Android data safety section
|
||||
- [ ] 5.3.3 Configure EAS Submit for iOS App Store and Google Play
|
||||
- [ ] 5.3.4 Set up over-the-air updates via EAS Update for non-native JS changes
|
||||
- [ ] 5.3.5 Test full app flow end-to-end on physical iOS and Android devices
|
||||
## Phase 5: Offline
|
||||
|
||||
### 5.1 Route Download
|
||||
|
||||
- [ ] 5.1.1 Set up Expo SQLite database with tables for offline routes, waypoints, and edit queue
|
||||
- [ ] 5.1.2 Implement route download: fetch GPX + metadata from API, store in SQLite
|
||||
- [ ] 5.1.3 Build download progress UI with cancel support
|
||||
- [ ] 5.1.4 Implement offline route loading — detect network state and load from SQLite when offline
|
||||
|
||||
### 5.2 Tile Cache
|
||||
|
||||
- [ ] 5.2.1 Implement tile download manager: given a route bounding box, download tiles at zoom levels 10-15
|
||||
- [ ] 5.2.2 Store tiles in the file system via `expo-file-system` with a lookup index in SQLite
|
||||
- [ ] 5.2.3 Configure `react-native-maps` to use cached tiles when offline
|
||||
- [ ] 5.2.4 Add storage budget display and management on Profile tab (total cached size, delete individual routes)
|
||||
|
||||
### 5.3 Offline Edit Queue
|
||||
|
||||
- [ ] 5.3.1 Implement edit queue in SQLite: store pending route edits with timestamps
|
||||
- [ ] 5.3.2 Add sync-pending indicator on routes with queued edits
|
||||
- [ ] 5.3.3 Implement sync-on-reconnect: process queued edits in order via Journal API
|
||||
- [ ] 5.3.4 Handle sync conflicts: warn user if server version changed, apply last-write-wins
|
||||
|
||||
## Phase 6: Polish
|
||||
|
||||
### 6.1 Deep Links
|
||||
|
||||
- [ ] 6.1.1 Configure `trailscool://` URL scheme in `app.config.ts` for iOS and Android
|
||||
- [ ] 6.1.2 Implement deep link handler: parse `trailscool://routes/:id` and `trailscool://activities/:id`, navigate to detail screens
|
||||
- [ ] 6.1.3 Implement "Edit in Planner" flow: open Planner URL in browser with JWT callback, handle return deep link
|
||||
- [ ] 6.1.4 Add universal links / App Links for `https://trails.cool/routes/:id` (optional, requires associated domains)
|
||||
|
||||
### 6.2 App Store Preparation
|
||||
|
||||
- [ ] 6.2.1 Write App Store and Play Store descriptions (en + de) with screenshots
|
||||
- [ ] 6.2.2 Add privacy nutrition labels for iOS (location, health, network) and Android data safety section
|
||||
- [ ] 6.2.3 Configure EAS Submit for iOS App Store and Google Play
|
||||
- [ ] 6.2.4 Set up over-the-air updates via EAS Update for non-native JS changes
|
||||
- [ ] 6.2.5 Test full app flow end-to-end on physical iOS and Android devices
|
||||
|
||||
## Phase 7: Notifications
|
||||
|
||||
### 7.1 Journal Push Endpoints
|
||||
|
||||
- [ ] 7.1.1 Implement `POST /api/v1/push/subscribe` — store Web Push subscription for the authenticated user
|
||||
- [ ] 7.1.2 Implement `DELETE /api/v1/push/unsubscribe` — remove a push subscription
|
||||
- [ ] 7.1.3 Send Web Push notification when a shared route is updated
|
||||
|
||||
### 7.2 Push Relay Service
|
||||
|
||||
- [ ] 7.2.1 Build Web Push to APNs relay service (small Go or Node service)
|
||||
- [ ] 7.2.2 Build Web Push to FCM relay service
|
||||
- [ ] 7.2.3 Deploy relay service with Docker Compose alongside existing infrastructure
|
||||
|
||||
### 7.3 Mobile Push Integration
|
||||
|
||||
- [ ] 7.3.1 Register push token on mobile app startup via `expo-notifications`
|
||||
- [ ] 7.3.2 Handle incoming push notifications — display and deep link on tap
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue