# Profile Page — Detail Design Spec

Stand: 2026-05-07
Owner: rebreak-native-ui
Phase: 1 (Detail-Plan, kein Code).
Quellen: `memory/project_profile_page_design.md`, `memory/feedback_anonymity_nickname.md`, `memory/project_llm_per_plan.md`, `ops/UI_MIGRATION_PLAN.md`.

Profile-Page ist das UI-Showpiece. Streak-Tab entfällt komplett und wandert dezent in die Profile-Page. Settings ist parallel und funktional, aber kein Showpiece.

---

## 0. Routing & Datenmodell-Übersicht

Zwei Views, scharf getrennt:

- `/(app)/profile` (eigenes Profil, Hero-Tab in Tab-Bar). Volle Sicht.
- `/profile/[userId]` (fremdes Profil). Anonymisiert: nur nickname, avatar, plan-tier (keine Email, keine Demographics, keine Cooldowns, keine SOS-Stats, keine Liste der blockierten Domains).

Beide laden via `apiFetch` aus eigenen Endpoints — kein client-side Filtern.

---

## 1. Visual Mock (ASCII-Wireframe)

Eigenes Profil — vertikal scrollbar, ein Screen, sechs Sektionen:

```
+------------------------------------------------------------+
|  [<-- back]    Profil                       [icon: cog]    |  <- minimal top bar
+------------------------------------------------------------+
|                                                            |
|         +------------------+                               |
|         |                  |   <- Avatar 96x96, runder     |
|         |    avatar        |      Rahmen, plan-Akzent      |
|         |                  |      (free=gray, pro=orange,  |
|         +------------------+      legend=gold)             |
|         [icon: camera-edit]                                |
|                                                            |
|              Jonas_42                       [icon: pencil] |  <- nickname, inline-edit
|              chahinebrini@gmail.com                        |  <- email read-only,
|                                                            |     subdued grau, klein
|              [pill: legend]   Mitglied seit 12.04.2026     |
|                                                            |
+------------------------------------------------------------+

  STATS                                              [icon: info]
+------------------------------------------------------------+
|  +-------+   +-------+   +-------+   +-----------+         |
|  | 12    |   | 47    |   | 134   |   |   8       |         |
|  | Posts |   | Folg- |   | gebl. |   | Approved  |         |
|  |       |   | ower  |   | Dom.  |   | Domains > |         |
|  +-------+   +-------+   +-------+   +-----------+         |
|     tap         tap         tap            tap (highlight) |
+------------------------------------------------------------+

  STREAK                                       [icon: chevron]  <- collapsible
+------------------------------------------------------------+
|     23 Tage  geschützt                                     |
|     seit 14. April 2026                                    |
|                                                            |
|     longest streak: 41 Tage                                |
|                                                            |
|     COOLDOWN-VERLAUF                                       |
|     -----------------------------------                    |
|     | timeline-rail (1px line, vertical) |                 |
|     |  o  18.04.  16h  Cooldown beendet  |                 |
|     |  |   "Stress nach Arbeit"          |                 |
|     |  o  02.05.  4h   Cooldown abgebr.  |                 |
|     |  |   ohne Reason                   |                 |
|     |  o  06.05.  24h  aktiv             |  [pill: aktiv]  |
|     -----------------------------------                    |
|     [load more — last 30 days]                             |
+------------------------------------------------------------+

  LYRA INSIGHTS                                              <- SOS stats, dezent
+------------------------------------------------------------+
|     Letzte 30 Tage                                         |
|                                                            |
|     5 SOS-Sessions, 4 davon bewältigt   [80% bar]          |
|                                                            |
|     Was hat am meisten geholfen?                           |
|     [#] Atemübung   .........  3 Sessions                  |
|     [#] Spiel       .........  1 Session                   |
|     [#] Reden       .........  1 Session                   |
|                                                            |
|     Häufigste Emotion: Stress                              |
+------------------------------------------------------------+

  ANONYMER BEITRAG ZUR FORSCHUNG       [icon: chevron]   <- collapsed default
+------------------------------------------------------------+
|     Optional. Hilft DiGA-Wirksamkeit zu belegen.          |
|     Nur aggregiert, nie personenbezogen.                  |
|     [link: Mehr erfahren]                                  |
|                                                            |
|     ----- expanded state -----                             |
|     Geburtsjahr           1989          [icon: pencil]     |
|     Geschlecht            divers        [icon: pencil]     |
|     Familienstand         ledig         [icon: pencil]     |
|     Beruf                 Angestellt    [icon: pencil]     |
|     Bundesland            Bayern        [icon: pencil]     |
|     Stadt                 (nicht angeg) [icon: pencil]     |
|                                                            |
|     [button: Einwilligung widerrufen]                      |
+------------------------------------------------------------+
```

Fremdes Profil — drastisch reduziert:

```
+------------------------------------------------------------+
|       [avatar 96px]                                        |
|       Jonas_42       [pill: legend]                        |
|       Mitglied seit April 2026                             |
|       [button: Folgen]   [button: DM senden]               |
+------------------------------------------------------------+
|  +-------+   +-------+   +-----------+                     |
|  | 12    |   | 47    |   |   8       |                     |
|  | Posts |   | Folg- |   | Approved  |                     |
|  |       |   | ower  |   | Domains   |                     |
|  +-------+   +-------+   +-----------+                     |
+------------------------------------------------------------+
|  Letzte Posts (5)  ...                                     |
+------------------------------------------------------------+
```

UX-Notizen:
- Edit-Icons stehen rechts neben dem editierbaren Wert, nicht in einem zentralen "Edit-Mode". Inline-Tap öffnet Bottom-Sheet mit Input.
- Collapse-Chevron rechts oben in Section-Header, animated 180-Grad-Rotation.
- Keine Tier/Score-Kacheln (alte Nuxt-Logik), nur "Mitglied seit"-Datum + Plan-Pill.
- Plan-Pill nutzt Plan-Akzentfarbe (free=neutral-300, pro=brandOrange, legend=Goldverlauf).

---

## 2. Component-Tree

Routen (neu):
- `app/(app)/profile.tsx` → eigenes Profil. Wird Tab-Bar-Eintrag, ersetzt Streak-Tab.
- `app/profile/[userId].tsx` → fremdes Profil. Modal/Stack-Push.

Komponenten (neu):

```
<ProfileScreen>          (top-level page, owns scroll + section refs)
  <ProfileHeader>        (avatar + nickname + email + plan-pill + member-since)
    <AvatarPicker>       (preset-grid + custom-upload trigger)
      <AvatarCropSheet>  (Bottom-Sheet mit Crop-UI für Custom-Photo)
    <NicknameEditSheet>  (Bottom-Sheet, valibot-validiert)
  <StatsBar>             (4 stat-cards horizontal, tappable)
    <StatCard>           (number + label, optional onPress)
  <ApprovedDomainsSheet> (Bottom-Sheet mit Liste der approved domains)
  <BlockedDomainsSheet>  (analog, custom + global summary)
  <StreakSection>        (collapsible, owns streak + cooldown queries)
    <StreakHero>         (currentDays + startDate + longest)
    <CooldownTimeline>   (vertikale Liste, virtualisiert, paginiert)
      <CooldownTimelineItem>
  <LyraInsightsCard>     (SOS-Stats: 30-Tage-Trend + helped-by-Bar)
    <HelpedByBar>        (atemuebung/spiel/reden mit Anteils-Balken)
  <DemographicsAccordion>
    <DemographicsConsentNotice>
    <DemographicsField>  (label + value + edit-icon)
    <DemographicsEditSheet>
```

Komponenten (shared, reuse):
- `<Card>` — vorhanden in components/Card.tsx
- `<Button>` — vorhanden
- `<EmptyState>` — vorhanden, für "noch keine approved domains"
- Bottom-Sheet-Pattern aus `<PostCommentsSheet>` (animated, Backdrop)
- `<StreakBadge>` (existiert) — innerhalb `<StreakHero>` als "longest"-Indikator nutzbar

State-Ownership:
- `<ProfileScreen>` lädt einmalig `/api/profile/me/full` (oder kombiniert me + stats + streak + cooldowns + sos in einem Aufruf — siehe Sektion 3).
- `<NicknameEditSheet>` und `<AvatarPicker>` rufen `PATCH /api/auth/me` auf, invalidieren `useMe()` (cachedMe-Flush) + reload.
- `<ApprovedDomainsSheet>` lazy-loadet on-open `/api/profile/me/approved-domains` (separat, nicht im Initial-Bundle — Liste kann groß werden).
- `<DemographicsEditSheet>` ruft `PATCH /api/profile/demographics` auf.
- `<CooldownTimeline>` paginiert: erste 10 vom Initial-Aufruf, "load more" lädt `/api/profile/me/cooldown-history?cursor=...`.

---

## 3. API-Endpoint-Liste

Existiert bereits:
- `GET /api/auth/me` — eigene basis. Liefert id, email, username, nickname, avatar, plan, streak (current days), created_at. KEINE Stats. KEINE Demographics.
- `PATCH /api/auth/me` — username, nickname, avatar. Muss um demographic-Felder erweitert werden ODER separater Endpoint (siehe unten).
- `GET /api/streak` — current Streak-Row.
- `GET /api/streak/events` — Streak-History (max 50). Enthält "started" / "reset" / "milestone" / "relapse". Cooldown ist DAVON GETRENNT.
- `GET /api/cooldown/status` — nur aktiver Cooldown (single).
- `GET /api/community/posts?userId=...` — vorhanden indirekt (über filter-Params).
- `GET /api/social/profile/[userId]` — fremdes Profil. Returnt nickname, avatar, followersCount, postsCount, tier, recentPosts, isFollowing. Approved-Domains nicht enthalten — muss erweitert werden.
- `GET /api/custom-domains` — eigene custom domains (active + submitted + approved + rejected).

Neu nötig:

| Endpoint | Methode | Zweck | Shape |
|---|---|---|---|
| `/api/profile/me` | GET | Aggregat: alles für eigenes Profil in einem Roundtrip | `{ profile, stats, streak, recentCooldowns[], demographics, sosInsights }` |
| `/api/profile/me/cooldown-history` | GET | Paginated cooldown-Liste | cursor-paginated, je 20, ältere ans Ende |
| `/api/profile/me/approved-domains` | GET | Liste approved domains für expanded sheet | `[{ domain, approvedAt }]` |
| `/api/profile/me/sos-insights` | GET | Aggregierte SOS-Stats (Trends, helped-by-Counts) | siehe shape unten |
| `/api/profile/me/demographics` | PATCH | Demographic-Felder setzen, audit-trail | body: subset; setzt `demographics_consent_at` automatisch auf `now()` wenn null |
| `/api/profile/me/demographics` | DELETE | Einwilligung widerrufen | nullt alle Demographic-Felder + `demographics_consent_at` |
| `/api/social/profile/[userId]` | GET | bereits da, ERWEITERN um `approvedDomainsCount` und `blockedCustomCount` (privacy: NICHT die Liste, nur Anzahl) | additive change |

Aggregat-Endpoint `/api/profile/me` Shape (Vorschlag):

```ts
{
  profile: { id, email, nickname, username, avatar, plan, createdAt },
  stats: {
    postsCount: number,
    followersCount: number,
    followingCount: number,
    blockedCustomCount: number,   // user-eigene custom domains aktiv
    blockedGlobalCount: number,   // size der globalen Blocklist (Kontext)
    approvedDomainsCount: number, // submitted by user und approved
  },
  streak: { currentDays, longestDays, startDate, isActive, avgMonthlySavings },
  recentCooldowns: Array<{
    id, cooldownStartedAt, cooldownEndsAt, resolvedAt, cancelledAt, reason,
    status: 'active' | 'resolved' | 'cancelled',
    durationHours: number, // computed
  }>, // erste 10
  hasMoreCooldowns: boolean,
  demographics: {
    consentAt: string | null,
    birthYear: number | null,
    gender: string | null,
    maritalStatus: string | null,
    profession: string | null,
    bundesland: string | null,
    city: string | null,
  },
  sosInsights: {
    last30Days: { sessions: number, overcome: number, overcomeRate: number },
    helpedBy: { breathing: number, game: number, talk: number, other: number }, // counts
    topEmotion: string | null,
  },
}
```

SOS-Insights-Aggregat: aus `sos_sessions` (existiert) + `urge_logs` (existiert). `helpedBy` durch Heuristik aus `gamesPlayed`-Json + `breathingCount` + `messages`-Length. Edge-Case: 0 Sessions → null-State, UI zeigt EmptyState "noch keine SOS-Session".

Approved-Domains-Count: count der `domain_submissions` mit `status='approved'` und `userId=user.id`. Nicht aus `user_custom_domains` — da steht der approved-Status ebenfalls, aber `domain_submissions` ist source of truth für "von dir submitted und approved".

---

## 4. DB-Schema-Änderungen

Neue Spalten auf `Profile` (alle nullable, opt-in):

```prisma
model Profile {
  // ...bestehend...
  birthYear              Int?      @map("birth_year")            // nur Jahr (1900-2024), keine vollen Geburtsdaten
  gender                 String?   // 'male' | 'female' | 'divers' | 'no_answer'
  maritalStatus          String?   @map("marital_status")        // 'single' | 'partnered' | 'married' | 'divorced' | 'widowed' | 'no_answer'
  profession             String?                                  // freitext, max 80 chars
  bundesland             String?                                  // ISO-3166-2:DE Code (z.B. 'DE-BY')
  city                   String?                                  // freitext, max 80 chars
  demographicsConsentAt  DateTime? @map("demographics_consent_at") @db.Timestamptz(6)
  lyraVoiceId            String?   @map("lyra_voice_id")         // siehe UI_MIGRATION_PLAN §5 — gleiche Migration mitnehmen
}
```

Migration-File: `backend/prisma/migrations/20260507_add_profile_demographics_and_lyra/migration.sql`

```sql
ALTER TABLE "rebreak"."profiles"
  ADD COLUMN "birth_year"               INTEGER,
  ADD COLUMN "gender"                   TEXT,
  ADD COLUMN "marital_status"           TEXT,
  ADD COLUMN "profession"               TEXT,
  ADD COLUMN "bundesland"               TEXT,
  ADD COLUMN "city"                     TEXT,
  ADD COLUMN "demographics_consent_at"  TIMESTAMPTZ,
  ADD COLUMN "lyra_voice_id"            TEXT;

-- Index nur wenn wirklich für Aggregations-Queries gebraucht (DiGA-Reports auf
-- bundesland/birthYear). Initial: keine Indizes — kann später additiv kommen.
```

Validierungen (server-side, in `me/demographics.patch.ts`):
- `birthYear`: integer, 1900..currentYear-13 (DSGVO Mindestalter 13).
- `gender`: enum-Liste oben.
- `maritalStatus`: enum-Liste oben.
- `bundesland`: regex `^DE-(BW|BY|BE|BB|HB|HH|HE|MV|NI|NW|RP|SL|SN|ST|SH|TH)$`.
- Freitext-Felder: trim, max-Length, kein HTML, kein URL-Pattern (anti-spam).

DSGVO-Audit-Trail: Setzen eines beliebigen Demographic-Feldes setzt `demographics_consent_at = now()` falls null. Widerruf (DELETE) nullt alle Felder PLUS `demographics_consent_at`. Optional zusätzlich Append-only-Log-Tabelle `demographics_consent_log` (created_at, action='granted'|'revoked', user_id) — Empfehlung: jetzt parken, reicht später nachzurüsten falls BfArM/DiGA-Audit das fordert.

---

## 5. UI-Differential-Logik (eigenes vs fremdes Profil)

Nur auf eigenem Profil (`/(app)/profile`):
- Email-Anzeige (klein, subdued, unter nickname)
- Avatar-Edit + Nickname-Edit
- Streak-Sektion komplett
- Cooldown-Timeline
- Lyra-Insights / SOS-Stats
- Demographics-Sektion (mit Edit + Widerruf)
- Liste der eigenen blockierten Custom-Domains (in Sheet)
- Liste der eigenen approved domains (in Sheet)

Auf fremdem Profil (`/profile/[userId]`):
- Avatar (resolveAvatar), nickname (Fallback username), Plan-Pill, Mitglied-seit
- Stats: Posts, Follower, Approved-Domains-Count (motivational signal — siehe Sektion 6)
- Letzte 5 Posts
- Follow-Button + DM-Button
- KEIN: email, demographics, cooldowns, sos-insights, blocked-domains-Liste

Backend-Enforcement:
- `/api/profile/me` und `/api/profile/me/*` benutzen ausschließlich `requireUser(event).id`. Kein `userId`-Param. Kein `?as=...`.
- `/api/social/profile/[userId]` returnt nie email, nie demographics, nie cooldowns, nie sos-insights — auch wenn der Caller selbst der gleiche User ist (eigener Self-View geht zwingend über `/api/profile/me`).
- Frontend hat zwei Routen, jede bindet sich an einen Endpoint. Kein gemeinsamer Component-Tree mit "isOwn"-Flag — separate Components, lehrt die Trennung im Code.

---

## 6. Risiken + Open Questions

### 6.1 Image-Cropper-Library für RN (Expo SDK 53, New Architecture)

| Library | Pro | Contra | Native-Module | Expo-kompat. |
|---|---|---|---|---|
| `react-native-image-crop-picker` | Mature, native UIs (UIImagePickerController + UCrop), Cropper-Quality top | Native-Module → eject/prebuild nötig (haben wir schon, dev-client läuft), Expo-Plugin existiert nicht offiziell, Maintenance-Drift | ja | mit prebuild + manuell config-plugin |
| `expo-image-picker` + `expo-image-manipulator` | Pure Expo, keine native-Coordination | Kein interaktiver Crop — manipulator macht nur fest definierte crop-Boxes ohne UI; eigener Crop-UI = Eigenbau auf `react-native-gesture-handler` + `reanimated` | nein | ja |
| `react-native-image-cropper` (custom) | Flexibel, JS-only | Maintenance fragwürdig (unmaintained), keine native-Performance | nein | ja |
| `@react-native-community/image-editor` | offiziell genug, paired mit `expo-image-picker` | wieder kein interaktiver Crop, nur api-crop | nein | ja |

**Empfehlung:** `expo-image-picker` (existiert bereits in package.json) für Pick + ein eigenes leichtes Crop-Sheet auf `react-native-reanimated` + `react-native-gesture-handler` (existieren). Square-only-Crop reicht für Avatar-Use-Case. Vermeidet Native-Module-Coordination mit zied/backyard und hält die Expo-only-Constraint. `react-native-image-crop-picker` als Phase-5-Followup, falls Square-Crop dem User zu wenig ist und z.B. Zoom-Pan-Pinch-Quality nicht reicht.

### 6.2 Cooldown-Timeline — Liste vs Chart vs Heatmap

Optionen:
- **Liste (vertikales Timeline-Rail):** zeigt Datum, Dauer, Reason, Status pro Eintrag. Detailreich, gut lesbar, scrollbar, paginierbar.
- **Bar-Chart (horizontale x-Achse Datum, y-Achse Dauer-h):** Übersichtlich, aber Reason geht verloren, Tap-to-detail wäre extra.
- **Heatmap (calendar-grid):** Schick, aber Cooldowns sind selten (vielleicht 1-3/Monat). Heatmap mit hauptsächlich leeren Zellen wirkt leer.

**Empfehlung: Liste mit minimalem vertikalem Timeline-Rail (1px-Linie + Punkten).** Begründung: Cooldowns sind sparse Events mit narrativem Wert (Reason ist informativ — "Stress nach Arbeit"), nicht numerisch-aggregierbar. Ein Chart würde das Personal/Reflexive verlieren. Das Rail gibt visuelle Hierarchie ("damals — jetzt") ohne Charting-Overhead. Status-Pills (aktiv/beendet/abgebrochen) farb-codiert. Detail-Tap für extended Info. Das ist die "Liebe zum Detail"-Lösung: Lesefluss > Datendichte.

### 6.3 Demographic-Felder — Pflicht oder optional, wann fragen?

Hard rule: optional, opt-in, jederzeit widerrufbar (DSGVO Art-9 + DiGA).

Wann fragen:
- **Nicht im Onboarding.** Das schreckt ab und kollidiert mit dem "Du gehst nicht allein"-Brand. Onboarding bleibt schlank.
- **Nicht via aufdringliches Modal.** Kein "Hey willst du nicht…" Pop-up beim Profile-Open.
- **Genau eine Stelle:** collapsible Sektion am unteren Ende der Profile-Page mit klarem Title "Anonymer Beitrag zur Forschung" + erklärendem Subtext + Link "Mehr erfahren" (Modal mit DSGVO/DiGA-Erklärung). Komplett collapsed by default. User entdeckt es organisch.
- **Sanfter Nudge nach 30+ Tagen Streak:** EINMAL eine dezente in-app-Banner-Karte (in der Home-Feed, nicht als Modal): "Hilf der Forschung — anonyme Demographics tragen zur DiGA-Wirksamkeitsstudie bei". Tap führt zur Profile-Demographics-Section. Banner danach gedismissed via AsyncStorage.

### 6.4 Anti-Vanity-Metric — was ist motivierend?

Insta-like Stats Reihenfolge fließt psychologisch in den User ein. Vorschlag:

- **Posts** und **Follower** sind klassische Vanity-Metrics — bei Glücksspiel-Recovery können sie schädlich sein (Druck, Vergleich, "echter Aktiver"-Performance).
- **Approved-Domains** ist die einzige Metric, die den Beitrag des Users zur kollektiven Sicherheit misst — direkt rebreak-Mission-aligned.
- **Blockierte Domains** (eigene custom + globale Anzahl als Kontext) zeigt persönlichen Schutz-Stand.

**Empfehlung-Reihenfolge in Stats-Bar (links→rechts):** Posts, Follower, Geblockt, **Approved Domains** (rechts, mit dezenter Akzentfarbe brandOrange-tinted bg statt neutral, gleiche Größe — kein "biggest is best" aber visuell hervorgehoben).

Open Question für User: Sollen wir Follower-Count komplett weglassen und durch was Sinnvolleres ersetzen (z.B. "Tage geschützt" als Zahl)? "Follower" kann in Recovery-Context die falsche Dynamik triggern. Alternative: 4 Cards = Posts / Tage-geschützt / Geblockt / Approved-Domains.

### 6.5 Weitere Open Questions

1. **Plan-Pill Design** — Free=neutral, Pro=orange, Legend=gold sichtbar oder zu plakativ? Soll Legend einen subtilen Goldverlauf bekommen oder nur ein Icon?
2. **Bundesland-Erfassung** — Reicht ISO-3166-2:DE Code (16 Bundesländer)? Oder brauchen wir Stadt+PLZ für DiGA-Reporting? PLZ ist DSGVO-sensibler (3-Stellen-PLZ ist Pseudonym-grenzwertig).
3. **Email auf eigenem Profil zeigen?** Spec sagt ja. Aber subtil + read-only. Falls Sign-Up via Apple/Google: Display "via Apple Sign-In" — wir haben aktuell keinen Provider-Marker im `/api/auth/me`. Brauchen wir `provider`-Feld in der me-response? (Quick win — aus `user.app_metadata.provider`.)
4. **Member-Since Datum-Format** — "12. April 2026" oder "April 2026"? Letzteres ist privacy-friendlier auf fremden Profilen (UserId-Lookup wenn jemand das Datum kombiniert).

---

## 7. Implementation-Reihenfolge (Phase-2/3 Vorschlag)

Phase 2 (Skeleton, dummy-Daten):
1. Tab-Bar `_layout.tsx` ergänzen um `<NativeTabs.Screen name="profile">`. Rest unverändert.
2. `app/(app)/profile.tsx` Skeleton mit 6 Sektionen, alle Hardcoded-Demo-Daten.
3. Components-Stubs anlegen (ProfileHeader, StatsBar, StreakSection, LyraInsightsCard, DemographicsAccordion, CooldownTimeline) in `components/profile/`.

Phase 3 (Wire-up):
4. `/api/profile/me` aggregat-Endpoint backend-side bauen.
5. Migration `20260507_add_profile_demographics_and_lyra` schreiben + auf staging deployen.
6. `useProfileMe`-Hook + Komponente connecten.
7. `/api/profile/me/cooldown-history` + Pagination im Frontend.
8. `/api/profile/me/sos-insights` + LyraInsightsCard.
9. `/api/profile/me/demographics` PATCH/DELETE + Edit-Sheets.
10. AvatarPicker mit expo-image-picker + Custom-Square-Crop-Sheet.

Phase 4 (Polish):
11. `/profile/[userId]` Route mit reduzierter View.
12. `/api/social/profile/[userId]` erweitern um approvedDomainsCount.
13. Skeleton-Loading-State, Empty-States, Animated-Collapse.

---

## 8. Files (relevant für spätere Phasen)

Backend:
- `backend/prisma/schema.prisma` (Profile-Model erweitern)
- `backend/prisma/migrations/20260507_add_profile_demographics_and_lyra/migration.sql` (neu)
- `backend/server/api/profile/me.get.ts` (neu)
- `backend/server/api/profile/me/cooldown-history.get.ts` (neu)
- `backend/server/api/profile/me/sos-insights.get.ts` (neu)
- `backend/server/api/profile/me/approved-domains.get.ts` (neu)
- `backend/server/api/profile/me/demographics.patch.ts` (neu)
- `backend/server/api/profile/me/demographics.delete.ts` (neu)
- `backend/server/api/social/profile/[userId].get.ts` (extend)
- `backend/server/db/profile.ts` (extend für demographic-fields + audit-stamp)

Frontend:
- `apps/rebreak-native/app/(app)/profile.tsx` (neu)
- `apps/rebreak-native/app/(app)/_layout.tsx` (Tab hinzufügen)
- `apps/rebreak-native/app/profile/[userId].tsx` (neu)
- `apps/rebreak-native/components/profile/*` (neue Komponenten-Sammlung)
- `apps/rebreak-native/hooks/useProfileMe.ts` (neu)
- `apps/rebreak-native/lib/demographics.ts` (Enum-Listen + Validierung, neu)
- `apps/rebreak-native/locales/{de,en}.json` (neue String-Namespaces `profile.*`)
- `apps/rebreak-native/components/AppHeader.tsx` (Profile-Item route → `/(app)/profile`)
