376 Commits

Author SHA1 Message Date
chahinebrini
0d28682749 fix(ios-vpn): PacketTunnel-Extension CFBundleVersion/Short an App angleichen
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 23:13:54 +02:00
chahinebrini
5a16cf771b feat(ios-protection): v1 NEPacketTunnelProvider DNS-Sinkhole als Layer-1
Neuer iOS-Layer-1-Filter: ein NEPacketTunnelProvider-DNS-Sinkhole — MDM-frei,
ab iOS 16, Parität zum Android-VPN-DNS-Filter. Ersetzt den Apple-seitig
blockierten NEURLFilter als Default. NEURLFilter-/PIR-Code bleibt inaktiv als
iOS-26-Upgrade-Pfad erhalten (User-Entscheidung).

Neues Extension-Target RebreakPacketTunnelExtension/:
- PacketTunnelProvider.swift — TUN-Setup (virtuelle DNS-IP 10.0.0.1, nur diese
  Route ins TUN), Read-Loop, NXDOMAIN-Sinkhole, Upstream-Forward via
  NWConnection zu 1.1.1.1, Blocklist-Reload via Darwin-Notification.
- DnsFilter.swift / HashList.swift / DomainHasher.swift — Swift-Ports der
  Android-DNS-Filter-Logik. blocklist.bin-Format (sortierte big-endian UInt64,
  SHA-256-Prefix) 1:1 beibehalten, mmap statt Heap-Load.

RebreakProtectionModule.swift:
- activateUrlFilter startet jetzt den Packet-Tunnel via NETunnelProviderManager
  (Default-Layer-1, On-Demand-Auto-Reconnect aktiv).
- NEURLFilter-Code in activateNeUrlFilter ausgelagert (inaktiv, behalten).
- getDeviceState/disable lesen bzw. stoppen den Tunnel-Status.

with-rebreak-protection-ios.js: zweites app_extension-Target, klassischer
Embed-Pfad (dstSubfolderSpec 13), packet-tunnel-provider-Entitlement + App-Group.
app.config.ts: zweites appExtensions-Target.

NICHT auf echtem Gerät verifiziert — NE-Packet-Tunnel laufen nicht im
Simulator. Ungetestete Annahmen im Code mit "UNGETESTETE ANNAHME" markiert.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 23:13:54 +02:00
chahinebrini
a713070d25 feat(protection): VIP umfasst auch approved Custom-Domains
getWebCustomDomains schliesst nur noch 'rejected' aus — 'approved' Domains
bleiben in der Layer-2-VIP (Zweitschutz, falls Layer 1 aus ist). Reihenfolge:
pending zuerst (keine Layer-1-Deckung → duerfen nie aus dem 50er-Cap fallen),
dann approved neueste-zuerst (Ueberlauf = aelteste approved, via Layer 1 gedeckt).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 22:44:19 +02:00
chahinebrini
ced749018b fix(custom-domains): web-Domains nicht am alreadyGlobal-Check abweisen
Der alreadyGlobal-Pre-Check (Domain schon in der 208k-Layer-1-Blocklist →
kein Custom-Slot) gilt jetzt nur noch fuer Mail-Typen. Fuer type='web'
uebersprungen: Web-Custom-Domains speisen die Layer-2-VIP-Liste, eine separate
Schicht — eine global (Layer 1) gelistete Domain muss in die VIP koennen,
gerade weil Layer 2 das Netz ist, wenn Layer 1 aus ist.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 22:14:18 +02:00
chahinebrini
c3390a0fed fix(blocker): webContent-Sync von URL-Filter entkoppeln
syncWebContentDomains war als Side-Effect an syncBlocklist gehaengt, das nur
bei aktivem URL-Filter laeuft. Layer 2 haengt aber an Family Controls — der
Sync lief nie wenn nur App-Lock/FC aktiv war. Jetzt eigene syncWebContent-
Funktion, ungated: Mount + App-Foreground + nach Domain-Add/-Remove.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 21:54:11 +02:00
chahinebrini
bc65c7172c fix(protection): webcontent-domains — fehlenden getWebCustomDomains-Import
Der Endpoint nutzte getWebCustomDomains ohne Import → ReferenceError → HTTP 500.
server/db/ ist nicht auto-importiert (nur server/utils/), daher expliziter
Import wie in allen anderen 15+ db/domains-Konsumenten.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 21:53:49 +02:00
chahinebrini
9dfccfcbaa fix(protection): webcontent-domains-Endpoint konsolidiert — VIP-Komposition
Ersetzt die statische v1 des Endpoints durch die per-User-VIP-Komposition:
eigene Web-Custom-Domains zuerst + globale Auffuellung → dedup → Cap 50.
v1-Reste entfernt (backend/data/, serverAssets-Eintrag) — eine Datenquelle
(backend/server/data/gambling-domains.json, direkter JSON-Import).
pnpm build:backend gruen verifiziert.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 21:21:22 +02:00
chahinebrini
72cd195d36 feat(blocker): VIP-Domain-Sektion + Cooldown-Friction beim Entfernen
Neue VIP-Sektion in der Blocker-Page: eigene Web-Custom-Domains als Tiles mit
Zaehler (X/10 Pro, X/20 Legend), Hinzufuegen frei. Entfernen geht durch einen
3-Klick-Friction-Flow (RemoveDomainSheet, gespiegelt vom Deactivation-Muster) —
kein Block-Abbau im Craving-Moment. free-Tier-Zweig entfernt (kein Free mehr).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 21:12:09 +02:00
chahinebrini
cc2d963d1f feat(protection): runtime-Sync + cache-first fuer iOS Layer-2-Domain-Liste
syncWebContentDomains (gespiegelt von syncBlocklist): holt die Domain-Liste vom
Backend, cached sie als webcontent-domains.json im App-Group-Container, ETag/304,
Reapply nach Sync wenn FC aktiv. loadWebContentDomains liest cache-first, faellt
auf die gebuendelte gambling-domains.json zurueck (Offline-Seed). Getriggert am
selben Punkt wie syncBlocklist (useBlocklistSync).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 20:21:54 +02:00
chahinebrini
86ed603a45 feat(protection): GET /api/protection/webcontent-domains — runtime-updatebare Layer-2-Domain-Liste
Neuer Nitro-Endpoint serviert die kuratierte Gambling-Domain-Liste pro Land
(DE/GB/FR) aus backend/data/gambling-domains.json. Auth wie alle
/api/protection/*-Routen (requireUser). 50-Domain-Cap pro Land serverseitig
erzwungen. Liste pflegen = JSON editieren + _meta.version hochzaehlen + deploy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 20:21:54 +02:00
chahinebrini
50d3c6a8e1 fix(protection): Layer 2 — Family Controls default-on + Resource-Bundle-Pfad
- familyControlsEnabled jetzt default-on (Apple-Distribution-Entitlement
  freigegeben); nur REBREAK_ENABLE_FAMILY_CONTROLS=0 schaltet ab.
- gambling-domains.json ins Pod-Verzeichnis verschoben + Podspec resource_bundles
  auf within-pod-Pfad korrigiert — CocoaPods buendelt keine Dateien ausserhalb
  des Pod-Roots (Bundle blieb sonst leer → Layer 2 griff nicht).
- Layer 2 (webContent-Filter) damit verifiziert funktionierend.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 20:13:57 +02:00
chahinebrini
ad51ce5099 feat(protection): Layer 2 in FC-Aktivierung einhaengen + URLFilter-Extension-Version
Layer-2-webContent-Filter laeuft jetzt automatisch bei activateFamilyControls/
activate mit (Helper applyWebContentLayer). URLFilterExtension CFBundleVersion/
ShortVersion an die App angeglichen. Apple-DTS-Report einreichfertig.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 19:25:24 +02:00
chahinebrini
627ddce995 Merge branch 'feat/ios-webcontent-layer2' — iOS Schutz-Layer-2 (webContent)
WebKit webContent-Filter via ManagedSettings (MVP-Plumbing):
applyWebContentFilter/clearWebContentFilter, gebuendelte Gambling-Domain-Liste
DE/GB/FR (Starter), JS-Bridge + Hook. Braucht nur Family Controls.
Auto-Trigger-Gating bewusst offen — TODO(layer2-gating).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 18:11:23 +02:00
chahinebrini
b1e46a065a chore(assets): Android-Onboarding-Screenshots (a11y + VPN-Permission, de)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 18:09:42 +02:00
chahinebrini
2839564469 fix(custom-domains): Per-Bucket-Limit-Check via Backend counts/limits
Web-/Mail-Limit getrennt gegen apiCounts/apiLimits geprueft (Single Source of
Truth); Legacy-Response ohne counts/limits faellt auf Backend-Rejection zurueck.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 18:09:42 +02:00
chahinebrini
35a71a9068 feat(presence): Online-Presence-Provider + Hooks
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 18:09:42 +02:00
chahinebrini
29bbf23405 feat(protection): iOS NEURLFilter-Spike + PIR-Server-Ops
NEURLFilter-Stack (iOS 26): Extension RebreakURLFilter -> URLFilterExtension
umbenannt, url-filter-provider-Entitlement, Bloom-Prefilter-Extension,
PIR-Client-Config (pirServerURL/pirAuthToken via Build-Env).
PIR-Server-Ops unter ops/pir-server/ (Dockerfile, build-and-deploy, Patches,
DTS-Report). backend/scripts/generate-pir-input.ts erzeugt die PIR-Datenbank.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 18:09:42 +02:00
chahinebrini
6cd3a78aaf feat(protection): iOS Layer-2 webContent-Filter (ManagedSettings) — MVP-Plumbing
WebKit-interner Content-Filter via ManagedSettingsStore().webContent als
stilles Sicherheitsnetz. Blockt eine kuratierte, laenderabhaengige Top-
Gambling-Domain-Liste plus systemseitig Adult-Content (.auto-Variante).
Braucht NUR Family Controls — kein MDM, kein neues Entitlement, keine
Config-Plugin-Aenderung.

- gambling-domains.json: gebuendelte Starter-Liste (DE/GB/FR), je <=50
  Domains (Apple-Hartlimit), klar als STARTER markiert. Via Podspec-
  resource_bundles ins App-Bundle gepackt.
- applyWebContentFilter / clearWebContentFilter: zwei native AsyncFunctions.
  Land via Locale.current.region, iOS 16+ gegated, FC-Auth vorausgesetzt.
- JS-Bridge (Module-Decl, types, web-stub, lib/protection.ts) + Actions im
  useProtectionState-Hook. getDeviceState liefert webContentFilter-Layer mit.

KEINE Auto-Trigger-Logik — Layer 2 ist vorerst nur explizit aufrufbare
Capability. Siehe TODO(layer2-gating) im Swift-Modul und lib/protection.ts.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 16:07:44 +02:00
chahinebrini
c6604f02df chore(release): v0.3.4 — enable Family Controls (Apple distribution entitlement approved) 2026-05-20 07:13:13 +02:00
chahinebrini
ea152a9169 feat(debug): debug-page in TestFlight via EXPO_PUBLIC_ENABLE_DEBUG flag
debug.tsx war hart __DEV__-gated → der Protection-Log-Viewer (v0.3.3)
wäre im TestFlight-Build unerreichbar gewesen.

eas.json production-Profil setzt jetzt EXPO_PUBLIC_ENABLE_DEBUG=1.
debug.tsx + HeaderDropdownMenu prüfen `__DEV__ || EXPO_PUBLIC_ENABLE_DEBUG`.
Für den echten App-Store-Release einfach das Flag aus eas.json nehmen.
2026-05-20 05:42:00 +02:00
chahinebrini
5a44b8162e chore(release): v0.3.3 — FC-gating + native log-viewer + expo-image 2026-05-20 05:34:57 +02:00
chahinebrini
f318364c7e fix(protection): gate Family Controls on entitlement + native log viewer
Verifiziert via Build-11-.ipa-Inspektion + Device-Logs:
- com.apple.developer.family-controls FEHLT im signierten Distribution-Build
  (Plugin gated korrekt auf REBREAK_ENABLE_FAMILY_CONTROLS — Apple-
  Distribution-Approval pending).
- Device-Log beweist: ManagedSettingsAgent-XPC → "error 159 Sandbox
  restriction" → NSCocoaErrorDomain:4099. Kein Daemon-/Race-Problem,
  Retry zwecklos.

Root-Cause des FC-Bugs: app.config.ts hatte familyControlsEnabled hart
auf true (falscher "approved"-Kommentar) → App bot App-Lock an obwohl
sandbox-blockiert. FAMILY_CONTROLS_AVAILABLE wurde nirgends konsumiert.

Fixes:
- app.config.ts: familyControlsEnabled an REBREAK_ENABLE_FAMILY_CONTROLS
  gekoppelt (== Plugin-Gating) → in TestFlight/production false.
- blocker.tsx: iOS App-Lock-Card nur wenn FAMILY_CONTROLS_AVAILABLE.
  lockedIn akzeptiert URL-Filter allein wenn FC build-seitig fehlt.
- ProtectionSlide.tsx: Onboarding überspringt den App-Lock-Step (FC)
  wenn nicht verfügbar — URL-Filter allein = vollwertiger Schutz.

Native Log-Viewer (für NEFilter-Debug ohne Mac/Console.app):
- Swift: getProtectionLogs/clearProtectionLogs lesen SharedLogStore aus
  App-Group-UserDefaults.
- lib/protection.ts wrapper + TS-module-types.
- debug.tsx: ProtectionLogCard (iOS) — native NEFilter/FC-Logs in der
  App sichtbar, copy/clear/refresh.
2026-05-20 05:32:06 +02:00
chahinebrini
b8e4b02b88 perf(images): migrate react-native Image → expo-image (memory+disk cache)
Avatare (Dicebear-URLs), Chat-Attachments und Feed-Bilder wurden bei
jedem App-Reload neu vom Netzwerk geladen — RN Image hat nur flüchtigen
Memory-Cache. expo-image (~3.0.11) bringt persistenten Disk-Cache
(cachePolicy 'memory-disk' default).

14 Files migriert: UserAvatar, ChatBubble, RoomCard, ChatInput, PostCard,
ComposeCard, NotificationsDropdown, AppHeader, ProfileHeader,
AddDomainSheet, DomainGrid, room, profile/edit, signup.

API-Mapping: resizeMode→contentFit. PostCard onLoad las e.nativeEvent.
source — expo-image liefert e.source direkt (sonst wäre der Post-Bild-
Aspect-Ratio-Fix still gebrochen).

PostCard: nur Image-Zeilen angefasst, Like/Count/Memo-Logik unberührt
(memory/feedback_minimal_post_changes.md).

Kommt mit v0.3.3 (expo-image ist Native-Modul, braucht neuen Build).
2026-05-20 04:49:11 +02:00
chahinebrini
a9015d1951 chore(release): v0.3.2 — bug-fix bundle (permission/cooldown/onboarding) 2026-05-20 04:30:24 +02:00
chahinebrini
306a8fda30 fix(blocker): cooldown-elapsed modal platform-specific (no Android text on iOS)
Modal zeigte auf iOS "Du kannst den ReBreak-Bedienungshilfe-Dienst jetzt
in den Einstellungen ausschalten" — Bedienungshilfe/Accessibility-Service
ist ein Android-Konzept, existiert auf iOS nicht.

iOS: NEFilter + Family Controls werden von forceDisable() vollständig
abgeschaltet, User muss nichts in Settings tun. Neue iOS-Variante zeigt
nur "Cooldown abgelaufen — Schutz deaktiviert." + OK, kein Settings-Button.

Android: unverändert (a11y-Service braucht Settings-Deeplink).

i18n DE/EN/FR/AR: cooldown_elapsed_message_ios neu.
2026-05-20 04:25:15 +02:00
chahinebrini
312c668ae9 feat(onboarding): back-button between steps + language switcher on welcome
Back-Button:
- OnboardingNavContext liefert der Shell einen optionalen goBack-Handler
  (kein prop-drilling durch 8 Slides).
- OnboardingShell: chevron-back links neben der Progress-Bar wenn goBack
  gesetzt ist.
- Controller: goToLinearPrevious() + BACK_ALLOWED-Liste. Back nur auf
  privacy/nickname/diga_choice/plan/payment — NICHT welcome (erste),
  done (final), diga_code (eigener onBack), protection (Backend-Step +
  Permission-Flow).

Language-Switcher:
- WelcomeSlide: 4 Sprach-Pills (DE/EN/FR/AR) oben rechts. User kommt
  während Onboarding nicht zu Settings — sonst kein Weg die Sprache
  zu wechseln. setLanguage persistiert + flippt RTL für AR.
2026-05-20 04:20:22 +02:00
chahinebrini
34005803da fix(protection): cooldown-elapse must set protectionDisabledAt in state endpoint
Bug: nach Cooldown-Ablauf reaktivierte sich der Schutz automatisch.

Root-Cause: Race zwischen zwei Endpoints die abgelaufene Cooldowns auflösen.
/api/cooldown/status setzt korrekt profile.protectionDisabledAt. /api/
protection/state — alle 5s während Cooldown gepollt — machte beim Ablauf
NUR resolveCooldown(), ohne protectionDisabledAt. state.get gewann das Race
fast immer → protectionDisabledAt blieb null → protectionShouldBeActive=true
→ Frontend-Bypass-Detection reaktivierte den Schutz.

Fix: state.get.ts setzt im expired-Branch ebenfalls protectionDisabledAt +
cooldownJustResolved-Flag.
2026-05-20 04:17:24 +02:00
chahinebrini
c32eeeb070 fix(protection): NEFilter retry + FamilyControls 4099 recovery sheet
Tester reports: nach 'Don't Allow' im System-Dialog reagiert Re-Request
nicht (NEFilter), plus FamilyControls wirft NSCocoaErrorDomain:4099
(XPC-Daemon-Failure). Mehrere TestFlight-User betroffen.

Swift native:
- resetUrlFilter: 800ms delay nach remove + 3x retry-loop bei code 5
- activateFamilyControls: 3x retry-loop mit Backoff bei 4099

JS:
- PermissionDeniedSheet generic via variant prop (nefilter|family_controls)
- Blocker + Onboarding: 4099-detect → Recovery-Sheet mit 3-Step-Fallback

I18n DE/EN/FR/AR: blocker.family_controls_error.* keys
2026-05-20 03:51:33 +02:00
chahinebrini
73f70b5e28 fix(language): auto-sync from user_metadata.locale at sign-in
Bug: User mit iOS-Sprache=Arabisch sah App auf Englisch wenn
Localization.getLocales() auf seinem Setup nicht zuverlässig 'ar'
zurückgab (iOS-Region≠Sprache, App-Override etc).

Fix: bei sign-in (init() initial-getSession + onAuthStateChange für
SIGNED_IN events) wird session.user.user_metadata.locale gelesen.
Wenn AsyncStorage @rebreak/language NOCH NICHT gesetzt ist (User hat
keine explicit Choice gemacht) → silent apply der server-locale
(inkl. RTL-flip, KEIN Restart-Alert).

Respektiert User-Choice: wenn AsyncStorage gefüllt ist (z.B. User hat
manuell in Settings gewechselt), bleibt das gewinnen.
2026-05-19 22:02:34 +02:00
chahinebrini
63a1a3b550 fix(staging): export NITRO_* env-mapping for BREVO_API_KEY + hook secrets
start-staging.sh re-exports Infisical secrets als NITRO_*-prefixed envs
damit Nitro's runtimeConfig sie zur Laufzeit overrided (Default ist
Build-Time evaluated, also "" wenn nicht im Runner-Env).

Drei neue Mappings:
- BREVO_API_KEY → NITRO_BREVO_API_KEY
- HOOK_SEND_EMAIL_SECRETS → NITRO_HOOK_SEND_EMAIL_SECRETS
- MAIL_SENDER_EMAIL → NITRO_MAIL_SENDER_EMAIL

Ohne diese Mapping bleibt config.brevoApiKey leer im laufenden Backend →
send-email Hook fired 500 (siehe Smoke-Test).
2026-05-19 18:32:01 +02:00
chahinebrini
bdfcc40a6c feat(auth-hook): send-email hook for dynamic sender-name + i18n subject
GoTrue-Send-Email-Hook (v2.155+) → backend rendert Mails selbst, schickt
via Brevo Transactional API. Vorteil: pro Mail-Typ × Locale eigener
Sender-Display-Name UND Subject (GoTrue's eingebauter Mailer ist global
statisch).

Files:
- backend/server/utils/mail/templates.ts — 5 mail-types × 4 locales
  Sender-Name, Subject, Title, Body, HelpText, Privacy-Label.
  HTML-Renderer mit Nunito + Icon + DSGVO-Footer (analog public/templates).
- backend/server/utils/mail/brevo.ts — Brevo Transactional API client
  (POST /v3/smtp/email, separate REST-API-Key vom SMTP-Key)
- backend/server/api/auth-hooks/send-email.post.ts — Endpoint mit
  Standard-Webhooks Signature-Verify (HMAC-SHA256, timing-safe compare,
  multi-secret-rotation support)
- backend/nitro.config.ts — runtimeConfig: brevoApiKey,
  hookSendEmailSecrets, mailSenderEmail

Requires (außerhalb dieses commits):
- Infisical staging: BREVO_API_KEY (✓), HOOK_SEND_EMAIL_SECRETS (✓)
- docker-compose: GOTRUE_HOOK_SEND_EMAIL_{ENABLED,URI,SECRETS} (✓)
- GoTrue upgrade v2.154 → v2.189 (✓, mit auth.factor_type owner-transfer)
2026-05-19 18:04:14 +02:00
chahinebrini
a0dff80ced ui(signup): privacy-notice shield als app-icon-look
🛡️ Unicode-Schild → grünes (22c55e) rounded-md Square mit weißem
Ionicons shield-checkmark, 32x32. Konsistent mit App-Icon-Style.
2026-05-19 17:57:02 +02:00
chahinebrini
a8e094be9d fix(mail-templates): banner layout — icon left, wordmark centered
Vorher: Icon und 'ReBreak'-Wordmark untereinander (vertikal gestapelt).
Jetzt: 3-Spalten-Tabelle — Icon links (48x48), Wordmark mittig,
gleichgroßer Spacer rechts (52px) für visuelle Mitte.

Icon-Größe von 64x64 → 48x48 reduziert für besseres Höhen-Verhältnis.
2026-05-19 13:34:25 +02:00
chahinebrini
f2545738ee feat(mail-templates): Nunito font, app-icon header, DSGVO footer
- App-Icon (128x128, resized von 1024x1024, 6.7KB) im dunklen Header
  über dem 'ReBreak'-Wordmark, abgerundete Ecken (border-radius:14px)
- Nunito (Google Fonts, weights 400/600/700) für allen Body-Text,
  Inline font-family auf jedem Element wegen Gmail-Style-Stripping.
  OTP-Code bleibt monospace (Apple SF Mono / Menlo)
- DSGVO-Footer: rebreak.org + Datenschutz-Link (i18n'd: Datenschutz /
  Privacy / Confidentialité / الخصوصية)
- 5 Templates konsistent: confirmation, recovery, magic_link, invite,
  email_change
2026-05-19 11:10:49 +02:00
chahinebrini
12e140e25b refactor(signup): remove firstName/lastName fields (DSGVO data-minimization)
Felder wurden nirgendwo gelesen/angezeigt (nur in raw_user_meta_data
gespeichert ohne Verwendung). Inkonsistent mit OAuth-Flow der sie
gar nicht erfasst. Entfernt:
- 2 Inputs aus signup.tsx
- firstName/lastName aus signUp metadata-Typ + data
- 8 i18n-keys (de/en/fr/ar)
- DB-Cleanup via SQL für 5 existing User (raw_user_meta_data - 'first_name' - 'last_name')

Art. 5(1)c DSGVO: nur Daten verarbeiten die für Zweck notwendig sind.
Nickname allein reicht — Anonymität-Pattern (memory/feedback_anonymity_nickname.md).
2026-05-19 11:05:18 +02:00
chahinebrini
512949f851 fix(nitro): explicit publicAssets dir=../public so templates ship in .output
Nitro resolved default publicAssets `dir: "public"` relativ zu srcDir
(`server/`) statt zum rootDir. Folge: backend/public/templates/*.html
wurde nicht in .output/public/ kopiert → GoTrue lädt 404.

Fix: explicit `dir: "../public"` zeigt auf <projectRoot>/public/.
2026-05-19 10:58:18 +02:00
chahinebrini
5434254f74 feat(auth,mail): pw-reset OTP-flow + custom mail templates + account-switch cleanup
- Phase 3 PW-Reset: 3 screens (forgot-password → reset-otp → new-password),
  verifyOtp({type:'recovery'}), new updatePassword() action
- Custom Brevo-Mail templates (backend/public/templates/) — 5 HTMLs with
  go-template i18n (de/en/fr/ar incl. RTL for AR), OTP-only (no link),
  ReBreak branding
- signUp metadata.data.locale aus i18n.language → templates resolven Sprache
- Account-Switch-Bug fix: signOut() resettet alle 10 user-spezifischen stores
  + invalidateMe()
2026-05-19 10:49:23 +02:00
chahinebrini
b9b397b346 chore(release): v0.3.1 + eas-release.sh script
- package.json + app.config.ts (Expo source-of-truth) auf 0.3.1
- versionCode/buildNumber bleibt 10 (native files schon synced)
- eas-release.sh: build+submit für TestFlight/Play-Internal, default
  iOS, --android/--both flags, version-check-prompt vor build

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 08:11:18 +02:00
chahinebrini
5c539f8937 feat(presence,sheets,chat): tester-build polish bundle
Online-Status (Phase 1+):
- UserAvatar mit 4 Size-Variants (sm/md/lg/xl) + integrierter Online-Dot
- OnlinePresenceProvider: Supabase-Channel + Following-Filter
- ChatHeaderStatus: "Online" neutral / "vor X min" offline
- useLastSeen + Heartbeat (60s interval + AppState-background ping)
- Privatsphäre-Toggle in profile/index

Sheets:
- FormSheet Android-keyboard-fix (Dimensions.get('screen'), kein
  useWindowDimensions-Kollaps), useKeyboardHandler statt manual
  Keyboard.addListener, state-reset on re-open
- PostCommentsSheet same Pattern + close-after-submit + drag bis under
  app-header
- ConnectMailSheet form-view refactor: scrollable, AES-Banner als
  footnote, field-order email→pw→label, fixed 0.85 über alle Steps

Chat:
- DmChatBackground iOS klecks fix (G transform statt nested Svg)
- ChatInput Lyra-1:1 (keyboardWillShow, surfaceElevated bubble,
  arrow-up send, attachment links)
- dm/room/chat headers + conversation-list nutzen UserAvatar
- Foreign-Profile "Nachricht"-Button öffnet richtige DM

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 08:06:47 +02:00
chahinebrini
19b569927a fix(presence): missing imports in 3 endpoints
Same pattern as touchLastSeen: getLastSeenBatch, setPresenceVisible,
getFollowingIds wurden im db/profile.ts implementiert aber nicht in den
Endpoints importiert. Alle 3 warfen 500 ReferenceError → grüner Dot
zeigte sich nie + Toggle silently failed.

Nitro's auto-import covered nur defineEventHandler/getQuery etc., NICHT
unsere eigenen db-layer helper.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 07:16:18 +02:00
chahinebrini
c89f541069 fix(presence): missing touchLastSeen import in /api/me/last-seen
Endpoint warf 500 jedem Heartbeat-Call (ReferenceError) — Floodete
pm2-Logs. Import war im DB-Layer-File implementiert aber nicht vom
Endpoint importiert.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 06:35:11 +02:00
chahinebrini
0ca0afb1e1 feat(presence): online-status backend (Phase 1)
Insta-style Online-Status mit Following-Filter + User-opt-out:
- Profile.lastSeenAt + Profile.presenceVisible (default true)
- GET /api/presence/last-seen?userIds=... batch, server-side filter
  durch Follow-Relation + presenceVisible
- GET /api/me/following → User-IDs für client-side Channel-Filter
  (Supabase Realtime Presence hat keine server-side Filter)
- POST /api/me/presence-visibility Toggle
- POST /api/me/last-seen Heartbeat (Phase-1-Fallback bis Edge-Function)
- /api/auth/me extended um presenceVisible für Settings-Initial-State

DB-Layer nutzt raw SQL bis Migration auf staging gelaufen ist
(Prisma-Client refresh erst nach CI generate).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 06:23:08 +02:00
chahinebrini
3c73a8b44a feat(community/posts): ?userId= query filter
Foreign-Profile-Page rendert User-Posts jetzt via PostCard mit dem
gleichen Daten-Shape wie Index. `?userId=<uuid>` filtert auf Posts
dieses Users — kombinierbar mit `category=...`, Bot-Kategorie-Mapping
wird übersteuert wenn explizite userId mitgegeben.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 04:06:13 +02:00
chahinebrini
bca00e03a5 feat(social/profile): approvedDomainsCount in foreign-profile response
Frontend Foreign-Profile-Page rendert 3 Stat-Cards (Posts/Followers/
Approved) — counter für approved DomainSubmissions hat im Endpoint
gefehlt → undefined im UI.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 03:45:17 +02:00
chahinebrini
c6e2116084 feat(community): admin/lyra-post Multi-Locale (motivation/tipp/etc.)
User-Bug: Admin-getriggerter Lyra-Post (Topic 'motivation' o.ä.) zeigte
trotz FR-locale im Frontend immer Deutsch. Root: LYRA_SYSTEM_PROMPT hatte
hartcodiert 'Auf Deutsch' → Groq output war immer DE.

Selbe Multi-Locale-Pattern wie approve.post.ts:
- 4 parallele Groq-Calls (Promise.allSettled) für de/en/fr/ar
- System-Prompt nimmt Lang-Parameter: 'Antwort AUSSCHLIESSLICH in <lang>'
- Content als JSON {de,en,fr,ar} gespeichert
- customContent-Path (Admin tippt selbst Text) bleibt plain — Admin schreibt
  in seiner gewählten Sprache, PostCard zeigt allen Usern denselben Text

Frontend (PostCard.tsx) parsed JSON bereits richtig via
resolveLocalizedJsonContent (vorheriger Commit 44a3348).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 00:44:05 +02:00
chahinebrini
44a3348845 feat(community): Domain-Approval-Lyra-Posts multi-locale (de/en/fr/ar)
Bug: User mit FR-locale sahen Lyra-Confirmation-Posts trotzdem auf Deutsch
(Banner/Tabs richtig FR). Root: approve.post.ts generierte den Text via
Groq mit hartcodiertem 'auf Deutsch'-Prompt, speicherte als plain content.

Server (approve.post.ts):
- 4 parallele Groq-Calls (Promise.allSettled) — de + en + fr + ar
- Per-Locale-PROMPT_CFG mit subject/action/statsLine/thanksSegment-Texten
- Locale-aware Number-Format (toLocaleString('de-DE'|'en-US'|'fr-FR'|'ar-EG'))
- Content als JSON {de:'...',en:'...',fr:'...',ar:'...'} gespeichert
- Mindestens DE muss gelingen, sonst kein Post (Sicherheit gegen halbe Posts)
- ~4x Groq-cost pro Post (sehr günstig bei Llama-3.3-70b, parallel-latency
  bleibt ähnlich)

Frontend (PostCard.tsx):
- resolveLocalizedJsonContent() — try-parsed JSON content
- Wenn JSON-Object mit Locale-Keys → pickt i18n.language, fällt auf DE → EN
- Sonst plain content (Legacy-Posts, Comments, User-Posts unverändert)
- Quick-Reject auf '{' first-char vermeidet JSON.parse-Overhead für 99.9%
  der Text-Posts

Legacy-Posts in DB bleiben DE-only (kein retroaktiver Multi-Locale-Rewrite).
Neue Posts ab Deploy haben alle 4 Sprachen.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 00:29:02 +02:00
chahinebrini
fe2096309f fix(auth): Apple-Logo weiß in OAuth-Button
Vorher: fill='#0a0a0a' (schwarz) auf bg-neutral-900 (schwarzem Button) →
Logo unsichtbar. Erste TestFlight-Build (v0.3.0) hatte das noch drin —
Fix für v0.3.0-rebuild oder v0.3.1 hotfix.

Beide AppleIcon-Komponenten in signin.tsx + signup.tsx lokal dupliziert
(nicht in shared component) → beide separat editiert.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 00:24:10 +02:00
chahinebrini
23cc147231 feat(auth/ios): native Apple Sign-In via expo-apple-authentication
Vorher: stores/auth.ts hatte TODO + fiel auf Supabase-Web-OAuth-Flow zurück,
was fehlschlug mit 400 'Unsupported provider: missing OAuth client ID' weil
der Supabase-Apple-OAuth-Provider nicht konfiguriert ist.

Jetzt: native Flow ohne Supabase-Provider-Config —
- expo-apple-authentication.signInAsync() → identityToken
- supabase.auth.signInWithIdToken({provider:'apple', token}) verifiziert direkt
  gegen Apple's Public-Keys (kein Client-Secret-JWT-Setup nötig)
- User-Cancel (ERR_REQUEST_CANCELED) → leeres Resultat statt Error
- Platform-Guard: Apple-Path nur auf iOS

app.config.ts: ios.usesAppleSignIn=true → Expo prebuild generiert das
com.apple.developer.applesignin-Entitlement in die .entitlements. Beim
ersten EAS-Build wird die Capability auto-registriert im Apple-Developer-
Portal für org.rebreak.app.

signin.tsx + signup.tsx: Apple-Button conditional auf Platform.OS==='ios'
gerendert. Android-User sehen nur Google-Button (auf Android gibt es kein
natives Apple Sign-In).

App-Store-Submission-Pflicht (Apple Guideline 4.8 — wer OAuth-Login mit
3rd-Party-Provider anbietet, muss auch Apple Sign-In bieten).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 00:13:45 +02:00
chahinebrini
534f978b4e fix(onboarding): FAQ-answers + iOS-screen-time pointer alignment
FAQ-Bug-Fix + Component-Extraction:
- DoneSlide nutzte qkey.replace('q','a') → 'faq_q1'.replace('q','a')='faa_q1'
  weil .replace nur das ERSTE q matched (in "fa**q**"), nicht das in "q1".
  → Antworten resolved gegen non-existent key, raw key gerendert.
- Fix: explizite ID-Array [1,2,4,5,8] mit `help.faq_q\${id}` / `help.faq_a\${id}`.
- Shared FaqAccordion-Component extrahiert (components/FaqAccordion.tsx)
  mit 2 Varianten: 'card' (help/faq.tsx) + 'pills' (DoneSlide inline).
- app/help/faq.tsx + DoneSlide nutzen jetzt beide den shared component.

ScreenshotPointer-Alignment für iOS Screen-Time-Permission:
- iOS Family-Controls-Dialog: "Continue/Continuer/Fortfahren" ist LINKS-grau,
  "Don't Allow" ist RECHTS-blau (Apple platziert decline prominent, accept
  zurückhaltend bei Screen-Time-Permission). Pointer muss daher nach LINKS,
  nicht zentriert wie beim NEFilter-Dialog.
- ScreenshotPointer: neuer alignment-Prop ('left'|'center'|'right') →
  translateX (-80|0|+80 dp).
- ProtectionSlide iOS Phase B: pointerAlignment="left" durchgereicht.
- Phase A (url_filter) + alle Android-Phasen bleiben center.

Release-Prep (zied):
- CHANGELOG.md v0.3.0-Block erweitert (TTS, Stripe-Pricing, Keyboard-Fix,
  Single-Banner, FAQ-Extraktion, i18n-Status, Backend-Pending-Migration).
- version 0.3.0 + buildNumber 10 + versionCode 10 schon vorher gesetzt.
- eas.json production-Profil ready; Android-serviceAccountKeyPath bleibt
  TODO (User-Action: Google-Cloud-Service-Account anlegen).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 23:59:56 +02:00
chahinebrini
2e409efaf0 feat(onboarding/android + backend/lyra-i18n): platform-dispatch + post-catalog scaffold
Android-Onboarding (Platform.OS dispatch in ProtectionSlide):
- Neue Phasen für Android: preexplain_vpn → preexplain_a11y → a11y_pending
- AppState-Listener: nach Settings-Rückkehr auto-poll isAccessibilityEnabled
  → wenn live, armTamperLock + finish (kein Fokus-Klick nötig)
- onboardingAssets: 8 neue Mappings (android_vpn + android_a11y × 4 Locales)
- Screenshots: vpn-permission + a11y-rebreak-row pro Locale
- Locale-Keys: protection_url_android, protection_lock_android, cta_open_a11y,
  cta_check_a11y, dialog_button_vpn_ok, dialog_button_a11y_toggle, tap_marker_hint_*

Lyra-Post i18n Phase 1 (Scaffold, feature-flag OFF by default):
- schema.prisma: CommunityPost.i18nKey String? (nullable)
- migration 20260517_add_lyra_post_i18n_key: ALTER TABLE ADD COLUMN i18n_key
  (NICHT auto-deployed — `prisma migrate deploy` als separater Step)
- server/lib/lyraPostCatalog.ts: 15 Templates skelettiert + pickRandomTemplate
- cron/lyra-post: USE_TEMPLATE_CATALOG=true Branch → speichert i18nKey;
  default false → LLM-Path unverändert (zero-risk-deployment)
- community.createPost: optionaler i18nKey-Parameter
- posts.get: i18nKey in API-Response
- PostCard: 3-Zeilen-Branch — i18nKey ? t('lyra_posts.'+id) : content
- stores/community: i18nKey?: string|null im Interface
- de.json: lyra_posts-Block mit 15 IDs + DE-Texten

Single-Banner-Verhalten auf Android verifiziert:
lockedIn=urlFilter && appDeletionLock funktioniert weiter — auf Android
alias appDeletionLock ← tamperLock; onboarding arms tamperLock, also
nach onboarding-done direkt ProtectionLockedCard sichtbar.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 23:48:25 +02:00