Archive the monolithic route-features spec and replace with 9 focused OpenSpec changes: multi-day-routes, waypoint-notes (with POI snapping), undo-redo, local-dev-stack, route-sharing, route-discovery, activity-photos, osm-overlays, plus the existing changelog and komoot-import. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4.6 KiB
4.6 KiB
1. Infrastructure
- 1.1 Enable Garage container in
infrastructure/docker-compose.yml: uncomment the existing service, addgarage_datavolume, add health check, addgarage.tomlconfig file with single-node setup - 1.2 Create
activity-photosbucket in Garage: add an init script or document thegarage bucket createcommand, generate API key with read/write access to the bucket - 1.3 Add S3 environment variables to Journal service in docker-compose:
S3_ENDPOINT,S3_ACCESS_KEY,S3_SECRET_KEY,S3_BUCKET,S3_PUBLIC_ENDPOINT
2. S3 Client & Server Utilities
- 2.1 Add
@aws-sdk/client-s3and@aws-sdk/s3-request-presignerdependencies to the Journal app - 2.2 Create
apps/journal/app/lib/storage.server.ts: S3 client singleton,generateUploadUrl(key, contentType, maxBytes),generateDownloadUrl(key, expiresIn),deleteObject(key),headObject(key)functions - 2.3 Add env validation for S3 variables in Journal app startup (fail loudly if missing when photo feature is used)
3. Database Schema
- 3.1 Add
activityPhotostable topackages/db/src/schema/journal.ts: id, activityId (FK to activities, cascade delete), s3Key, altText, width, height, sizeBytes, status (pending/active/deleted), createdAt - 3.2 Push schema with
pnpm db:pushand verify table exists in local PostgreSQL
4. Upload API
- 4.1 Create
apps/journal/app/routes/api.activities.$id.photos.ts: POST handler that validates ownership, checks photo count limit (max 20), generates presigned PUT URL, creates pendingactivity_photosrow, returns URL + photo ID - 4.2 Create
apps/journal/app/routes/api.activities.$id.photos.$photoId.ts: PATCH handler to confirm upload (verify S3 object exists via HEAD, set status to active), DELETE handler to soft-delete (set status to deleted) - 4.3 Register both API routes in
apps/journal/app/routes.ts
5. Client-Side Image Processing
- 5.1 Create
apps/journal/app/lib/image-processing.ts:resizeImage(file, maxDimension)using canvas, outputs WebP blob (JPEG fallback), strips EXIF by virtue of canvas redraw, handles orientation viacreateImageBitmap - 5.2 Create
apps/journal/app/components/PhotoUploader.tsx: file input (accept image types), processes each file throughresizeImage, requests presigned URL from API, uploads to S3 via fetch PUT, confirms via PATCH, shows progress per photo - 5.3 Add size and count validation in the uploader: max 5 MB per photo after processing, max 20 photos per activity, show clear error messages
6. Photo Display
- 6.1 Update activity detail loader (
activities.$id.tsx) to fetch active photos for the activity and generate presigned GET URLs (1 hour expiry) - 6.2 Create
apps/journal/app/components/PhotoGallery.tsx: responsive grid (1/2/3 columns),object-coverthumbnails,loading="lazy", click to open lightbox - 6.3 Create
apps/journal/app/components/PhotoLightbox.tsx: full-screen overlay, left/right navigation, close on escape/backdrop click, alt text display - 6.4 Integrate PhotoGallery into activity detail page, show "Add photos" button for owner
7. Photo Management
- 7.1 Add delete button (trash icon) on each photo in gallery view (owner only), calls DELETE endpoint, optimistically removes from UI
- 7.2 Add alt text editing: inline text input below each photo in edit mode, saves via PATCH endpoint
8. Cleanup
- 8.1 Add cleanup logic in
storage.server.ts: function to find photos with statusdeletedorpendingolder than 1 hour, delete S3 objects, remove database rows - 8.2 Call cleanup on activity detail page load (non-blocking) and on app startup
9. Privacy Manifest
- 9.1 Update
apps/journal/app/routes/privacy.tsx: add "Photos" section documenting S3 storage, EXIF stripping, deletion behavior, data export plans - 9.2 Add note about EXIF stripping in the upload UI (tooltip or help text explaining GPS/metadata removal)
10. i18n
- 10.1 Add translation keys (en + de) for: "Add photos", "Delete photo", "Photo uploaded", upload errors, photo count limit, alt text placeholder, EXIF stripping explanation, privacy manifest photo section
11. Testing
- 11.1 Unit tests for
image-processing.ts: verify resize output dimensions, WebP output, size within limits, EXIF data absent from output - 11.2 Unit tests for
storage.server.ts: presigned URL generation, delete, head object (mock S3 client) - 11.3 Unit tests for photo API routes: ownership validation, count limits, status transitions (pending -> active -> deleted)
- 11.4 E2E test: upload a photo on an activity, verify it appears in the gallery, delete it, verify it disappears