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>
3.7 KiB
3.7 KiB
1. Schema
- 1.1 Add
visibility_enumPostgreSQL enum (private,public) topackages/db/src/schema/journal.tsusingjournalSchema.enum() - 1.2 Add
visibilitycolumn tojournal.routestable, typevisibility_enum, defaultprivate - 1.3 Add
visibilitycolumn tojournal.activitiestable, typevisibility_enum, defaultprivate - 1.4 Create
journal.route_sharestable with columns:id(text PK),routeId(FK to routes, cascade delete),userId(FK to users, cascade delete),permission(enum: view/edit),createdAt. Add unique constraint on(routeId, userId) - 1.5 Add
forkedFromIdnullable text column tojournal.routes, FK toroutes.idwithonDelete: "set null" - 1.6 Add
contributorsjsonb text array column tojournal.route_versions - 1.7 Run
pnpm db:pushand verify schema locally
2. Permissions Logic
- 2.1 Create
apps/journal/app/lib/permissions.server.tswithcanView(routeId, userId)andcanEdit(routeId, userId)— owner always has full access, public routes viewable by anyone, shared routes respect permission level - 2.2 Update route detail loader (
routes.$id.tsx) to checkcanView— return 404 for unauthorized. PasscanEditresult to the component for conditional UI (edit button, share button) - 2.3 Update route list loader (
routes._index.tsx) to include routes shared with the current user alongside owned routes
3. Sharing UI
- 3.1 Add visibility toggle to route detail page (owner only): form with
intent=toggleVisibility, shows current state and toggle button - 3.2 Create user search API route (
api.users.search.ts): acceptsqquery param, returns matching users by username/display name (max 10, excludes requesting user), debounce-friendly - 3.3 Create share dialog component and integrate on route detail page (owner only): user search input, permission dropdown (view/edit), current shares list with remove button. Uses form actions for add/remove share
4. Forking
- 4.1 Create fork API route (
api.routes.$id.fork.ts): verify user is logged in, source route is public, copy route + latest GPX + metadata to user's collection withforkedFromIdset, create v1 version, redirect to new route - 4.2 Add "Fork" button on route detail page: visible when route is public, viewer is logged in, and viewer is not the owner
- 4.3 Show "Forked from [route name]" link on route detail page when
forkedFromIdis set and original route is viewable
5. Contributor Tracking
- 5.1 Update Planner callback handler (
api.routes.$id.callback.ts): extract contributor identity from JWT, pass to version creation - 5.2 Update
updateRouteinroutes.server.tsto accept and storecontributorsarray on new route version rows - 5.3 Display contributors on route detail page: per-version contributor names in version history, combined unique contributors in route header
6. Privacy & i18n
- 6.1 Update privacy manifest page (
privacy.tsx): document route visibility settings, sharing behavior, forking, and contributor tracking - 6.2 Add i18n keys (en + de) for: visibility labels (private/public), share dialog strings, fork button/label, contributor display, "forked from" text
7. Testing
- 7.1 Unit tests for
permissions.server.ts: owner access, public access, shared view/edit access, no access, null user on public route - 7.2 Unit tests for fork logic: successful fork of public route, reject fork of private route, forkedFromId set correctly, fork starts as private
- 7.3 E2E test: create route, toggle visibility to public, verify accessible when logged out
- 7.4 E2E test: share route with another user at view level, verify they can see it but not edit; share at edit level, verify edit access