vinterliste/frontend
Ole-Morten Duesund 6f4c11c7a6 User profile, activity editing, search, OSM links, moderator role,
opt-in /<username>/list, and a feedback channel

Six related features that touch the user model and activity UX:

1. **User profile** (display_name, username, public_list_enabled).
   New `display_name`, `username` (UNIQUE, slug-shaped), and
   `public_list_enabled` columns. PATCH /api/auth/profile is a partial
   update — pass only the fields you want to change, null to clear.
   MeResponse exposes all three. Display name is shown on public
   activities and in the nav; falls back to the email prefix when
   unset.

2. **Change password from the profile editor.** Existing
   /api/auth/password endpoint surfaced in the new Profile.svelte;
   the local-decrypt failure path on a wrong current password is
   mapped to a clean error.

3. **Edit existing activities.** ActivityForm becomes dual-purpose
   (create or edit). Title, tags, date/time, location, and
   visibility are all editable. Visibility transitions decrypt or
   re-encrypt client-side as needed before PATCH, and the IndexedDB
   private-tag index is kept in sync diff-style.

4. **Search.** A search input on Home filters across visible
   activities. Private rows are searched against their decrypted
   cleartext (decrypted once and memoised via $derived, so the work
   is amortised across keystrokes). Matches across title, tags,
   location label, and (for public) author display name.

5. **OpenStreetMap links.** Each row with a location renders the
   label as an OSM link. Smart: coords if present
   (?mlat=&mlon=&map=15/lat/lng → pinned view), else
   /search?query=. Built with the WHATWG URL constructor so
   Norwegian characters and commas survive.

6. **Moderator role + semi-delete by owner.** New is_moderator
   column on users. Owners always delete their own rows; moderators
   can additionally delete any semi or public activity (private is
   excluded — it's invisible to others, so there's no moderation
   case). README documents the manual promotion via sqlite3.

7. **Opt-in /<username>/list.** New server route
   /api/users/:username/list returns the user's public activities
   when both `username` is set AND `public_list_enabled = 1`. 404
   when either condition fails — same response in both cases so the
   route doesn't leak username existence for users who haven't opted
   in. SPA-side, App.svelte parses window.location.pathname on
   mount; falls back to "/" via history.replaceState after
   authenticating from a deep link.

8. **Feedback channel.** New `feedback` table. POST /api/feedback
   for any authenticated user; GET /api/feedback gated to
   moderators. The Feedback.svelte component is dual-mode — the
   form is universal; the list view auto-loads only for
   moderators. Submitter identity (email + display name) is shown
   to moderators so they can follow up; not exposed to the
   submitter themselves.

Schema migrations land via the existing ensureColumn() helper so
scaffold DBs upgrade cleanly. The username UNIQUE constraint is
applied as a partial unique index (WHERE username IS NOT NULL) so
multiple users with NULL usernames don't collide.

All 26 existing tests still pass; typecheck clean for both
tsconfigs; Vite build succeeds.
2026-05-25 12:44:33 +02:00
..
src User profile, activity editing, search, OSM links, moderator role, 2026-05-25 12:44:33 +02:00
index.html Scaffold Vinterliste — end-to-end encrypted winter activity list 2026-05-25 12:27:14 +02:00
svelte.config.js Scaffold Vinterliste — end-to-end encrypted winter activity list 2026-05-25 12:27:14 +02:00
tsconfig.json Scaffold Vinterliste — end-to-end encrypted winter activity list 2026-05-25 12:27:14 +02:00
vite.config.ts Scaffold Vinterliste — end-to-end encrypted winter activity list 2026-05-25 12:27:14 +02:00