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>
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>
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>
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>
- 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>
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>
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>
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>
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.
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).
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.
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.
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.
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.
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).
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).
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/.
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>
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>
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>
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>
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>
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>
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>
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>
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>
## TTS Auto-Play Preference
User-Request: wenn Voice einmal aktiviert, soll Lyra auf jeder Slide
automatisch sprechen — nicht jede Slide extra antippen.
- stores/lyraVoice.ts: zustand-store mit AsyncStorage-Persistence
(@rebreak/lyraVoiceEnabled). Default OFF.
- LyraBubble auto-plays on text-change wenn enabled
- Audio-Button toggled die Preference + stoppt current playback
- Visuell: Button ist orange-filled wenn voice ON, ghost-bordered wenn OFF
- Icon: volume-mute-outline (OFF) / volume-medium / hourglass / stop
- Cleanup beim Unmount (stopLyraSpeech) + bei text-change
Initialisiert via init() in app/_layout.tsx (analog language/theme/appLock).
Locale-keys: audio_play → "Stimme einschalten", neu audio_disable → "Stimme
ausschalten" in 4 Sprachen.
## DiGA Test Codes 011-100
Aktuell 10 Codes (REBREAK-TEST-001..010), aber 100 Android-Tester kommen
morgen onboarding. Migration 20260518_extend_diga_test_codes seeded 90
zusätzliche Codes via generate_series(11, 100) + LPAD-Padding.
- Label: 'test_batch_2026-05-android' für Auditbarkeit (vs '...2026-05'
für die ersten 10)
- grants_plan: 'legend' wie die ersten 10
- ON CONFLICT DO NOTHING — idempotent
Distribution-Pattern: Tester N kriegt Code REBREAK-TEST-<NNN-padded>.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Bug-context: user reports nach Cooldown-Disable auf v0.2.1 Android-Build
reactiviert sich Schutz auto → a11y-Settings bleibt blockiert → keine Screenshots
möglich. v0.3.0 hat den Backend-protectionDisabledAt-Guard der das verhindert,
aber Test-Devices brauchen ein direktes Reset-Tool für Multi-Locale-Screenshots.
Backend:
- POST /api/protection/dev-force-disabled — sets protectionDisabledAt=NOW()
ohne Cooldown-Vorlauf. Production-Guard (rebreak.org-non-staging → 403).
Frontend:
- /debug Android-Section refactored: "Force Reset + Settings öffnen" Button
- Bundle aus 3 Steps:
1. native forceDisable (VPN stop + tamper disarm + filter_enabled=false)
2. backend dev-force-disabled (Anti-Auto-Reactivation-Mark)
3. Settings → Bedienungshilfen öffnen
- Danach: User toggled ReBreak-Service in Android-Settings manuell off
→ frischer a11y-deep-link-Trigger für nächste Screenshot-Iteration
Also: fix /onboarding/welcome → /onboarding (Duo-Rewrite hat den alten Pfad
gelöscht). Route 404 auf Android sichtbar wenn User in debug-toggle 'welcome'
oder 'nickname' tappt.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Bug: 3 Stellen hatten `behavior={Platform.OS === 'ios' ? 'padding' : undefined}`.
Auf Android = `undefined` = KeyboardAvoidingView macht NICHTS → Input wird von
Tastatur verdeckt (chat-input, profile-edit-nickname, room-chat).
Fix: switch zu react-native-keyboard-controller's KeyboardAvoidingView mit
behavior='padding' für beide Plattformen. Funktioniert sauber cross-platform
weil KeyboardProvider schon im root-layout sitzt.
Affected Files:
- components/KeyboardAwareScreen.tsx (used by profile-edit + auth-screens)
- app/dm.tsx (DM chat)
- app/room.tsx (room chat)
lyra.tsx war bereits OK (`'height'` für Android — kein Fix nötig).
iOS-Verhalten unverändert.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- package.json: 0.2.0 → 0.3.0
- app.config.ts: version 0.2.1 → 0.3.0
- iOS buildNumber: 9 → 10
- Android versionCode: 9 → 10
- CHANGELOG.md: v0.3.0 entry with Duo-Onboarding, DiGA, Stripe-pivot, Arabic+RTL,
NEFilter-robust-disable, anti-auto-reactivation, FC always-on, etc.
Note: Android-Build wird vorerst NICHT submittet — Onboarding-Slides müssen für
Android-Protection-Mechanismus (VPN + a11y statt iOS NEFilter + Family Controls)
mit eigenen Pre-Explainer-Screenshots + Texten angepasst werden. Erst dann
v0.3.1 oder gesammelt mit Android.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Bug: /api/stripe/checkout returned "STRIPE_SECRET_KEY fehlt" obwohl Var in
Infisical gesetzt war. Root: Nitro's useRuntimeConfig liest process.env zur
BUILD-Zeit — Stripe-Keys waren beim letzten Build nicht da. Runtime-Override
geht nur via NITRO_-Prefix env-var.
start-staging.sh re-exporten Stripe-Keys mit NITRO_-Prefix damit nitro's
useRuntimeConfig sie zur Laufzeit picked up:
STRIPE_SECRET_KEY → NITRO_STRIPE_SECRET_KEY
STRIPE_WEBHOOK_SECRET → NITRO_STRIPE_WEBHOOK_SECRET
STRIPE_PUBLISHABLE_KEY → NITRO_PUBLIC_STRIPE_PUBLISHABLE_KEY
(Pattern aus bestehender Liste — SUPABASE, DEEPGRAM, CARTESIA etc. nutzen
die gleiche Convention.)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>