12 Commits

Author SHA1 Message Date
chahinebrini
4580a197dd fix(mail): reactive page (refresh stats + status on scan/connect) + center donut+legend
Two small fixes blocking real "feierabend":

1. Stats-Counter veraltet nach Scan/Connect/Disconnect:
   - mail.tsx hatte zwei separate Data-Sources: useMailStatus (accounts +
     errors + heartbeat) und useMailStats (blockedByDay + blockedByConnection)
   - onScanSuccess + onIntervalChanged + OAuth-onSuccess + disconnect-handler
     refreshten nur useMailStatus → der Account-Collapsible-Counter (kommt
     aus useMailStats.blockedByConnection) blieb veraltet
   - Beobachtet: GMX-Scan-Button meldet "90 blockiert" als Feedback, aber
     Card-Header zeigt weiter 60
   - Fix: refreshAll() = refresh() + refreshStats() parallel. Alle reactive
     callsites (4 Stellen) auf refreshAll umgestellt
   - useMailStats hatte refresh schon exportiert (Z. 153), nur nicht
     verdrahtet

2. Donut + Legend horizontal zentriert:
   - vorher: alignItems center (vertikal), Legend flex:1 → linksbündig mit
     Legend bis Card-Rand gestreckt
   - jetzt: justifyContent center + Legend ohne flex:1 → Block in der Mitte
     mit Whitespace links/rechts

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 00:16:53 +02:00
chahinebrini
8075c8e79c feat(mail): outlook-OAuth scan + daemon initial-sweep + page polish v4
USP-Confirmed: Outlook-OAuth Casino-Bonus-Mail wurde end-to-end gefiltert
(User-verifiziert). Mit dieser Welle ist der Daemon plus alle Scan-Pfade
OAuth-aware.

Backend — Mail-Stack (mo):

- backend/server/utils/mail-auth.ts NEU: zentraler resolveImapAuth-Helper
  kapselt OAuth-vs-AppPassword-Entscheidung. 5-min-Token-Expiry-Puffer,
  race-condition-sicheres Refresh via refreshAndSaveTokens.
- scan.post.ts + scan-internal.post.ts nutzen jetzt resolveImapAuth statt
  decrypt(passwordEncrypted). Vorher: Outlook-Connections wurden still
  übersprungen weil passwordEncrypted='' → decrypt failed. Cron + manueller
  Scan-Button funktionieren jetzt für OAuth-Connections.
- imap-idle: Initial-Sweep via triggerScan(conn) direkt nach Connect-Success.
  Neue Outlook-Connections kriegen sofort einen Full-Folder-Scan statt bis
  zu 30 Min Cron-Lag zu warten. scan-internal scannt ohnehin schon alle
  Folders via imap.list() (Junk, Spam, Archive, Custom) — Multi-Folder-
  Anforderung ist damit erfüllt.

Frontend — Mail-Page Polish v4 (rebreak-native-ui):

- MailDistributionChart: Donut zurück auf 200px (240 wuchs auch in der
  Breite und quetschte die Legend), "Live"-Pill-Header komplett raus
  (paddingTop von 16 auf 13 reduziert für tighteres Layout)
- mail.tsx Page-Hierarchie: "Mehr Infos"-Collapsible wandert von unter
  der Postfach-Liste direkt unter den Hero-Donut. Sub-Beschreibung
  "Blockiert — letzte 30 Tage" entfernt — Title reicht.
- Account-Card Expanded: adaptive Bar-Chart über Connection-Age
  (too-new <24h zeigt Empty-State, 1-14d Day-Buckets via Backend
  ?connectionId=, 15-90d client-Week-Aggregation, >90d Month)
- Account-Card Expanded: Scan-Button "Jetzt scannen" mit Refresh-Icon
  (Memory: kein Pen-Icon, refresh ok). Spinner während Scan, Feedback
  mit Blocked-Count nach Success.

Eskalations-Hinweis (nicht in dieser Welle):
- POST /api/mail/scan akzeptiert noch keinen connectionId-Filter →
  Scan-Button-Tap scannt aktuell alle Connections statt nur die
  angeklickte. Kleiner Folge-Patch, nicht blocking.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 23:55:18 +02:00
chahinebrini
1dfb0c647c feat(mail-page): polish v3 + shared HalfDonut + status-dot heartbeat-aware
User-Feedback nach Live-Test:

Frontend (mail page):
- HalfDonut als shared component in components/common/HalfDonut.tsx
  extrahiert (vorher local in ProtectionDetailsSheet). Mail-Page nutzt
  jetzt dieselbe SVG-Math, Animation und Stroke-Style wie der
  Blocker-Schutz-Details-Sheet — visuelle Konsistenz auf einen Blick.
  Mail-Donut: width=168 (kompakter als die 220 in Blocker, weil Legend
  rechts daneben sitzt).
- Donut zeigt Total in der Mitte mit kompaktem Format:
  < 1000 → "999", >=1000 → "1.2k+" / "12k+" / "27k+"
  Headline-Zahl oben links entfällt — Total ist im Donut-Center.
- "Mehr Infos" + "Kürzlich blockiert" zu EINER Top-Level-Collapsible
  zusammengefasst. Beim Aufklappen: Bar-Chart direkt sichtbar, nested
  Collapsible "Kürzlich blockiert" darunter (default zu).
- Account-Card Expanded: per-Connection-Bar-Chart mit adaptive
  Granularität nach Connection-Age:
  · <24h → Empty-State "Daten werden gesammelt, Auswertung nach 24h"
  · 1-14d → Day-Buckets (echte Daten via /api/mail/stats/blocked-by-day
    ?connectionId=)
  · 15-90d → Week-Buckets (client-aggregiert)
  · >90d → Month-Buckets (client-aggregiert)
- Settings-Sheet komplett refactored: State-Machine `mode: 'list' |
  'edit-title' | 'edit-email' | 'edit-password'` mit Back-Pfeil. Inline-
  Edit im selben Sheet statt Sub-Sheet öffnen (FormSheet-Pattern).
  Email-Edit-Row vorbereitet (Backend-PATCH-Endpoint kommt separat).
- Pen-Icons app-weit entfernt: SheetFieldStack-Row, alle Settings-Rows
  auf chevron-forward (Memory-Konvention).

Frontend (MailAccountCard status fix):
- resolveStatusDot nutzt jetzt heartbeat-as-fallback. Vorher: "waiting"
  wenn lastScannedAt=null, egal ob Daemon längst connected war. Jetzt:
  "waiting" nur wenn weder lebendiger Heartbeat noch vergangener Scan
  existiert → frisch verbundene Connections (z.B. OAuth-Outlook 5s nach
  Connect) zeigen direkt "live".
- Behebt User-Beobachtung: "wartet auf erste verbindung" bei Outlook
  obwohl Daemon-Log "connected, auth=xoauth2" zeigt.

Backend (imap-idle daemon):
- getMailboxLock("INBOX") jetzt mit 30s Promise.race-Timeout gewrappt.
- Outlook/XOAUTH2 hat den Edge-Case, dass der Mailbox-Lock lautlos
  hängt nach erfolgreichem connect — die Session bleibt offen ohne
  Fortschritt bis der Renew-Timer (10min) ein imap.close() schickt.
  Mit Timeout wird das Failure-Mode explizit → Auth-Retry-Loop greift
  sauber + last_connect_error mit klarem Text (statt stiller Hänger).
- Root-Cause "warum hängt es" noch nicht behoben — Diagnose nach
  Deploy in Logs (mo).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 23:23:45 +02:00
chahinebrini
206941e5e1 fix(mail-page): UX polish — FAB-revert, legend cap, activity NaNd, instant heartbeat
User-Feedback nach Live-Test:

Frontend:
- FAB raus, Plus-Button zurück in den Account-Liste-Section-Header
  (`add-circle-outline` in brandOrange + Label "Postfach hinzufügen").
  FAB stört am unteren Rand, oben passt zum iOS-NavBar-Pattern.
- Half-Donut Legend strikt max Top-3 + "Sonstige" — Threshold von ≤4
  auf ≤3 gesenkt. Auch bei 4 Connections wird jetzt schon komprimiert.
- Hero-Donut-Subtitle "über N Postfächer" entfernt — Title-Block ist
  jetzt eine Zeile: "XX blockiert · ● Live"
- Activity-Log default-collapsed war schon richtig (kein Change)
- Activity-Item-Redesign: x-Icon-Pille raus, Zeit + Provider als
  Sub-Zeile unter dem Subject ("vor 2h · GMX"), kein Zeit-Label rechts mehr

Bug-Fix — NaNd in Activity-Row:
- Root-Cause: snake_case/camelCase-Mismatch. Backend liefert
  `receivedAt`, `senderEmail`, `senderName`, `connectionId` (camelCase),
  Frontend-Type hatte snake_case → undefined-Werte → `new Date(undefined)`
  → NaN → "NaNd"-Render
- MailBlockedItem-Type auf camelCase umgestellt + nested `connection`-Objekt
  (passt jetzt zum Backend-Response)
- formatDate mit Number.isFinite-Guard — gibt null zurück bei ungültigem
  Datum statt NaN-String zu rendern

Backend (imap-idle daemon):
- Daemon schreibt jetzt unmittelbar nach `client.connect()` einen Heartbeat
  (last_idle_heartbeat_at = NOW()) + clear last_connect_error parallel
- Vorher: User sah 2-9min lang "wartet auf erste verbindung" obwohl
  Connection längst aktiv war (Heartbeat kam erst beim ersten NOOP-Cycle)
- Re-Connect-Pfad nach AUTHENTICATIONFAILED ist automatisch mit
  abgedeckt (geht durch denselben connect-Block)
- ESM-Daemon, kein Build-Step — Pipeline scp + pm2-restart reicht

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 22:55:50 +02:00
chahinebrini
432d9d27a3 feat(mail-page): hero-donut + FAB + collapsible bar-chart + legend truncation
UX-Welle nach User-Feedback aus dem ersten Live-Test der Mail-Page:

Page-Hierarchie neu (top → bottom):

1. HALF-DONUT als HERO-Karte — bisherige "BLOCKIERT XX über N Postfächer Live"-
   Banner-Card weg, Inhalt ist jetzt Title-Zeile innerhalb der Donut-Karte
   (rendert nur ab ≥2 Connections; Fallback-Stats-Row für 0-1 Connections)
2. Postfach-Liste (Account-Cards aus letztem Refactor — schlanker Header)
3. NEU: "Mehr Infos"-Collapsible — Bar-Chart "Blockiert letzte 30 Tage"
   liegt jetzt versteckt drin (default collapsed)
4. Activity-Log "Kürzlich blockiert" (unverändert)
5. NEU: FAB unten rechts — 56pt brandOrange Kreis mit "+"-Icon,
   öffnet ConnectMailSheet. Section-Header-Plus-Button entfällt.

Half-Donut Legend-Truncation:
- ≤3 Connections → alle anzeigen
- =4 Connections → alle anzeigen
- ≥5 Connections → Top-3 by blocked-count + "Sonstige"-Bucket
  · Donut: 4 Segmente (Top-3 + OTHER_COLOR grau)
  · Legend: 4 Zeilen (Top-3 fett, "weitere"-Zeile in regular grau)

Backend: GET /api/mail/stats/blocked-by-day?connectionId=<uuid> als
optionaler Filter (für per-Connection-Bar-Chart in expanded Account-Card,
in dieser Welle noch nicht im UI verdrahtet — Erweiterung kommt wenn
gewünscht).

FAB-Details (iOS-diskreter Shadow statt Material-Glow):
- position absolute, right 24, bottom = tabBarHeight + insets.bottom + 16
- 56pt, borderRadius 28, brandOrange BG, weißes Plus-Icon
- ScrollView paddingBottom angehoben damit kein Content unter dem FAB clipped

Edge-Cases:
- 0 Accounts → FAB sichtbar, Donut/Stats/Charts/Log versteckt + EmptyState
- 1 Account → Donut hidden (nur mit ≥2 Connections sinnvoll), Fallback-Stats-Row
- limitReached + FAB-Tap → bestehender Plan-Alert (FAB ist visuell nicht disabled)

Memory: Pull-to-refresh + bestehendes 30s-Status-Polling reichen für "wartet
auf erste verbindung"→"aktiv"-Übergang nach OAuth-Connect (Daemon-Heartbeat
braucht initial 2-9min, mo-Befund). UX-Polish-Option für später: in der
Initial-Phase einen freundlicheren "Verbinde gerade…"-Status anzeigen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 22:39:45 +02:00
chahinebrini
09d85180b6 fix(mail/oauth): drop User.Read scope — MS rejects multi-resource at /token
Microsoft V2.0 OAuth-Spezifikation: ein einzelner /token-Exchange darf nur
Scopes EINES Resource-Servers enthalten. Unsere bisherige Scope-Liste
mischte:

  https://outlook.office.com/IMAP.AccessAsUser.All  (outlook.office.com)
  User.Read                                          (graph.microsoft.com)

Im /authorize akzeptiert MS das (Multi-Consent-Screen), aber beim Token-
Exchange wirft MS AADSTS70011:
  "The provided value for the input parameter 'scope' is not valid.
   One or more scopes [...] are not compatible with each other."

Fix: User.Read raus. Display-Name in der App entfällt vorerst — Email
kommt sauber aus id_token.preferred_username (bei Consumer-MS-Accounts
typisch die Login-Email). Falls Display-Name künftig gebraucht wird →
separater Graph-Token-Exchange via On-Behalf-Of-Pattern.

Plus: ConnectMailSheet zeigt jetzt im roten Error-Banner den echten
Backend-Error (API-Status + Body) statt nur generischen Text — sonst
würden wir solche MS-Spezifika nie auf dem Device sehen.

Hans-Müller-Memo Section 3.1 (Datenkategorien) + Section 4.1
(Datenschutzerklärung) müssen entsprechend zurückgerollt werden — siehe
separater DSB-Update-Stream.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 22:16:01 +02:00
chahinebrini
fc69a14f25 feat(mail): outlook oauth — full end-to-end (backend + daemon + frontend)
Microsoft hat App-Passwords für consumer-Outlook im September 2024 abgeschaltet.
Diese Welle bringt OAuth2/XOAUTH2-Support als zweiten AuthMethod-Pfad — Gmail/
iCloud/GMX/Yahoo bleiben unangetastet auf App-Password.

Backend (rebreak-backend):

- POST /api/mail/oauth/microsoft/init: PKCE-Flow-Start, generiert
  code_verifier + Authorization-URL, persistiert pending state mit TTL
- POST /api/mail/oauth/microsoft/callback: Token-Exchange (PKCE, kein
  client_secret weil Public Client), id_token-Decode für Email, MailConnection
  upsert mit auth_method='oauth2_microsoft' + encrypted Tokens
- Token-Refresh-Util backend/server/utils/ms-oauth.ts + DB-Function
  refreshAndSaveTokens(connectionId, clientId) mit optimistic-concurrency-
  Race-Condition-Schutz (UPDATE WHERE oauth_token_expiry = <gelesener-wert>,
  bei affected_rows=0 → frischen Wert lesen statt nochmal refreshen sonst
  invalid_grant via Token-Rotation)
- Neue Tabelle oauth_pending_states (TTL via createdAt + Cleanup-Job-TODO)
- [id].delete.ts: echter OAuth-Disconnect — DB-Token-Löschung + Audit-Log
  (MS hat keinen Drittanbieter-Revoke-Endpoint, daher User-Information-Pflicht
  per Frontend-Modal, siehe DSB-Memo Section 5.1)
- Consent-Gate auch in scan.post.ts + scan-internal.post.ts (Cron-Trigger
  war ohne Consent-Check = DSGVO-Lücke, jetzt geschlossen mit
  skippedNoConsent-Field in Response)

IDLE-Daemon (backend/imap-idle/index.mjs, mo):

- XOAUTH2-Auth-Branch via getCredentialsForConnection() — wenn
  auth_method='oauth2_microsoft', Token-Expiry-Check (<5min remaining →
  proaktiver Refresh), sonst decrypted accessToken zu ImapFlow
- AUTHENTICATIONFAILED-Recovery: bis 3× reaktiv refresh + reconnect, danach
  last_connect_error='auth_revoked' (kein Endlos-Loop)
- IDLE_RENEW_INTERVAL_MS = 10min — passt für MS 29min-Timeout (gleich wie
  Gmail/iCloud)
- Consent-Pause: Connections mit consent_at=null laufen IDLE weiter (für
  exists-Event-Wiederaufnahme), aber triggerScan() ist deaktiviert bis
  consent erteilt
- start-idle-staging.sh: MS_OAUTH_CLIENT_ID explizit weiterleiten in den
  inneren bash -c-Block (war Infisical-Var, ging aber durch strict-mode
  verloren)

Frontend (rebreak-native-ui):

- Outlook-Tile re-aktiviert (war disabled mit "Kommt bald" seit Sept-2024-
  Awareness), authMethod-Discriminator löst statt Email+Pw-Form den
  OAuth-Flow aus
- ConnectMailSheet: neuer view-State 'oauth_warning' (Outing-Effekt-Hinweis
  per Hans-Müller-Memo Section 6.1) + 'oauth_pending' (Browser-Step-Spinner)
- Deep-Link-Handler app/auth/mail-oauth-callback.tsx — auto-registriert
  durch expo-router-File-Routing, kein Native-Rebuild (scheme 'rebreak'
  schon im app.config.ts)
- mailConnectDraft-Store: pendingOAuthConnectionId für Title-Edit-Sheet
  direkt nach Connect
- MailAccountCard: Password-Row hidden für OAuth-Connections, Post-Disconnect-
  Modal mit MS-Account-Anleitung (DSB-konform — kompensiert fehlenden
  Drittanbieter-Revoke-Endpoint mit User-Information)

Hans-Müller-DSB-Memo (mail-outlook-oauth-dsgvo-review.md):

- Section 4.1 Datenschutzerklärung-Textbaustein: "Wir widerrufen den Token
  aktiv bei Microsoft"-Satz raus (war faktisch falsch — MS hat keinen
  Drittanbieter-Revoke). Neuer Wortlaut: DB-Löschung + User-Anleitung
  account.microsoft.com → Sicherheit → App-Berechtigungen
- Section 4.1: User.Read-Scope offen dokumentiert mit Datenminimierungs-
  Klausel (Scope breiter, wir nutzen NUR Display-Name + Email-Claim)
- Section 5.1: ehrliche Doku dass MS keinen RFC-7009-Revoke hat
- Section 9 Anwalts-Themen: neue Frage 5 zur Art. 17-Erfüllung trotz
  fehlendem MS-Revoke

Architektur-Eigenschaften:

- Generisches AuthMethod-Framework — Gmail/iCloud/Yahoo können später als
  reine Config-Erweiterung OAuth bekommen, kein Refactor nötig
- Token-Encryption via bestehendes crypto.ts (AES-256-GCM, Key aus
  Infisical)
- Consent-Gate konsistent: ConnectMailSheet-Consent-Step VOR Provider-
  Auswahl (Frontend), backend-Endpoint 412 wenn consent fehlt, Daemon +
  Scan-Endpoints pausieren bei consent_at=null

Open follow-ups:

- oauth_pending_states-Cleanup-Cron für abgelaufene Entries (TODO im
  Backend-Code dokumentiert)
- Anwalts-Klärung Hans-Müller Section 9 (DPA-Anspruch ohne MS-Lizenz +
  Art. 17 mit User-Information statt Revoke-Endpoint)
- TIA (Transfer Impact Assessment) für MS-Sub-AV — Hans-Müller-Draft-Aufgabe
- Outlook-Tile-Wieder-Aktivierung ist live, aber Phase-1-Production-Test
  steht aus (User Test auf iPhone nach Pipeline-Deploy)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 21:04:14 +02:00
chahinebrini
b7909d77e4 feat(mail): custom title + settings collapsible + stats charts + provider filter
Mail-Page-Refactor — Privacy-friendly + DiGA-tauglich:

- Custom title pro mail-connection (z.B. "Privat-Gmail" statt voller E-Mail).
  Memory-Pattern: Anonymität via Nickname jetzt auch für Mail-Adressen
  sichtbar, Datenminimierung. Title nullable, Fallback auf Email-Domain.
- Schema-Migration mail_connection_title (additiv, NULL default für Bestand)
- Endpoint PATCH /api/mail-connections/:id mit title-Validation (max 60,
  trim, leerer String → NULL)
- "Passwort ändern"-Collapsible → vollwertige "Einstellungen"-Sektion:
  Title editieren · Email read-only · Passwort neu setzen · Verbindung
  trennen (mit Confirm-Dialog)
- EditMailTitleSheet als FormSheet-Pattern für Title-Edit
- mailConnectDraft-Store kriegt Title-Feld für Pre-Fill bei Re-Open

Zwei neue Stats-Charts auf der Mail-Page:

- MailBlockedByDayChart — 30-Tage-Bar-Chart, Plain-View-Bars (Pattern wie
  Sparkline-Profile), Empty-State bei 0 Cooldowns
  · Backend: GET /api/mail/stats/blocked-by-day?days=30
- MailDistributionChart — Half-Donut via react-native-svg, Top-5 Connections
  + "Sonstige", rendert nicht bei ≤1 Connection
  · Backend: GET /api/mail/stats/blocked-by-connection

Activity-Log mit Provider-Filter:

- Filter-Chips Mo Gmail/Outlook/iCloud/etc. über bestehendem Activity-Log
- GET /api/mail/results?provider=X (war vorher hardcoded all)
- Endpoint-Naming-Fix in useMailResults (war /api/mail/blocked, jetzt
  korrekt /api/mail/results — UI-Agent hatte falschen Path geraten)

Backend-Side-Effects:

- imap-providers util resolveProviderMeta(host) — gibt {provider, label,
  isCustomDomain} zurück, von 3 Endpoints konsumiert
- /api/mail/status erweitert: title, provider, providerLabel,
  isCustomDomain im Account-Shape
- /api/mail/results erweitert: connection-Sub-Objekt pro Entry +
  provider-Filter-Query

Open follow-ups (TODOs):

- deleteOldMailBlocked-Cron löscht <24h → Bar-Chart-Daten weg. Retention
  auf 90 Tage hochsetzen oder Cron stoppen.
- POST /api/mail/connect könnte die neue connection.id im Response
  mitliefern → Title-PATCH direkt ohne Extra-GET (UI-Agent-Empfehlung).
- /api/mail/status zeigt nur active Connections — paused mit Title wären
  unsichtbar. Entscheiden.

18 neue i18n-Keys (mail.title_*, mail.settings_*, mail.row_*,
mail.disconnect_confirm_*, mail.stats.*, mail.filter.all) in DE + EN.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 19:06:01 +02:00
chahinebrini
a3f892ddac fix(native/mail): duplicate add-button in empty state + intro hints in ConnectMailSheet
- mail.tsx: hide section-header "+" button when accounts.length === 0 — MailEmptyState's CTA is the sole add trigger; also replaces Pressable with TouchableOpacity
- MailEmptyState: Pressable → TouchableOpacity (no-Pressable rule)
- SheetFieldStack: add optional `intro?: ReactNode` prop rendered in a flexShrink:1 ScrollView above chips/active-input so it compresses gracefully when the keyboard is up
- ConnectMailSheet: move app-password guide + green AES block into `intro` prop so they're visible from the start, before the user types anything

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 23:39:22 +02:00
chahinebrini
51697c3aa4 feat(tier): plan-change briefing sheet + over-limit cards (Phase 2 UI)
- components/plan/PlanChangeSheet.tsx — upgrade/downgrade briefing per pricing-tiers.md §4
  (fetches GET /api/plan/change-preview; gains/keeps/changes; recovery-safety line;
  billing hint w/o purchase button; CTA row, no 'are you sure?' interstitial)
- debug.tsx: PlanOverrideToggle routes every flip through PlanChangeSheet first
- devices.tsx + protectedDevices.ts: 'degraded' status (red, inline 'protection expired —
  remove the profile yourself' hint, no green checkmark); maxProtectedDevices limit hint
- mail.tsx + MailAccountCard.tsx + useMailStatus.ts: over-limit banner + paused-account
  greyed-out + PausedBadge (all defensive — only shows if backend sends the  field)
- blocker.tsx: free-tier transparency hint ('Grundschutz aktiv — voller Schutz: Pro/Legend')
  + custom-domain over-limit banner
- locales: plan.change.* + plan_limit.* (de + en)

tsc clean. Backend side (GET /api/plan/change-preview, paused/degraded fields) in progress
in parallel — UI built defensively to work before it lands.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:21:47 +02:00
chahinebrini
d7b15e231a feat(theme): Dark Mode Wave 2 — blocker, mail, chat, community, notifications, all remaining screens
Wave 2 = ALLE app-files die in Wave 1 noch hardcoded waren. Komplette App-weit
theme-aware-Migration jetzt durch. Legacy `import { colors }` flat export
vollständig eliminiert.

Migrated this wave:

Top-level Screens:
- app/urge.tsx (makeStyles factory mit ~20 colors)
- app/room.tsx + dm.tsx + games.tsx
- app/(app)/chat.tsx + mail.tsx + coach.tsx + notifications.tsx
- app/profile/[userId].tsx + profile/edit.tsx (INPUT_STYLE in body moved)
- app/debug.tsx + auth/callback.tsx

Blocker (7):
- AddDomainSheet, CooldownBanner, DeactivationExplainerSheet, DomainGrid,
  ProtectionCard, ProtectionDetailsSheet, ProtectionLockedCard

Mail (3):
- ConnectMailSheet, EditMailAccountSheet, MailEmptyState

Chat (1):
- ChatBubble, ChatInput

Community/Posts/Notifications:
- PostCard, PostCardSkeleton, ComposeCard, PostCommentsSheet
- NotificationsDropdown
- StreakBadge (Nativewind classes durch inline dynamic styles ersetzt)

Reusable Sheets:
- WheelPickerModal, OptionsBottomSheet, DeviceLimitReachedSheet

Urge subsystem (5):
- InlineRatingDrawer, ShareSuccessDrawer, UrgeStats, SosFeedbackModal,
  Breathing

Profile components:
- DigaMissionBanner

Pattern: useColors() hook in component body, makeStyles(colors) factory wo
StyleSheet.create vorher hardcoded war. 11 base-tokens (bg/surface/
surfaceElevated/border/text/textMuted/brandOrange/brandBlue/success/error/
warning) nutzen colors.light vs colors.dark scheme.

Bewusst NICHT migriert (semantic colors):
- DigaMissionBanner amber (#fffbeb, #854d0e) — DiGA-brand, nicht neutral
- Lyra-thinking #3b82f6 in urge.tsx — Lyra-brand-color
- scrollDownBtn #374151 — intentional dark floating-button

TS clean. Test: Settings → Theme → Dark — alle screens sollen jetzt dunkel
werden ohne white-flashes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:51:02 +02:00
RaynisDev
b58588cf3c initial commit: rebreak-monorepo (RN app + standalone Nitro backend) 2026-05-06 07:13:43 +02:00