- backend: skip Expo alert push to iOS devices that already received VoIP push
(CallKit + banner = double ring)
- native: receiveIncoming no longer triggers InCallManager.startRingtone —
CallKit/ConnectionService play their own ring. Dedup if same callId
arrives twice (Realtime + VoIP-Push race).
- MediaLightbox component extracted from dm.tsx. Image now fills a fixed
full-screen box with contentFit=contain instead of an onLoad-computed
aspect ratio, removing the square->real-size jump ("jitter") on open.
- Info-sheet images: render a nested MediaLightbox inside the FormSheet
(stacks above the sheet modal) and track lightboxSource. Removes the
close-sheet-then-reopen workaround that switched context back to the DM.
- Typing indicator: heartbeat (every 2s while focused + non-empty) instead
of keystroke-only sends, so "typing…" holds through thinking pauses;
receiver clear raised to 6s. stop on blur/send/empty.
- Presence: debounce going offline by 12s (online immediate) so brief
presence-sync gaps no longer flicker "Online" <-> "last seen".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Backend (voice-call groundwork, no call engine yet):
- Profile.callsEnabled (Boolean default true) + migration
- canCall(caller,callee): mutual-follow AND callee.callsEnabled — server-side hard guard
- POST /api/me/calls-enabled (opt-out toggle), GET /api/chat/can-call/:userId
- expose callsEnabled in /api/auth/me
Frontend:
- "Allow calls" toggle in Profile privacy section (default on, optimistic+rollback)
- Me.callsEnabled + i18n DE/EN/FR/AR
Bundled DM UI work from this session:
- image lightbox is now a swipeable carousel over all shared images (+ counter)
- keyboard stays open after sending (input ref refocus)
- voice notes: Instagram-style waveforms (own=white/mint, other=black/grey),
removed the blue progress dot; lazy-load expo-media-library with clean fallback
- expo-linear-gradient + expo-media-library deps
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Magic-Mac-Hub (/api/magic/devices):
- Filter boundToPlan war zu eng \u2014 iPhone/iPad ohne aktiven Plan-Lock
fielen raus. Jetzt: alle UserDevice-Rows des Users ausser den
magic-enrolled, plus ProtectedDevice mit Dedupe.
Native /devices Page:
- MacBook erschien doppelt: einmal als UserDevice (registriert via
Magic-Mac, model=Mac14,9) und einmal als ProtectedDevice (alter
DNS-Flow). Dedupe per platform-key (mac/ios/android/win):
wenn UserDevice mit gleicher Plattform existiert, blende
ProtectedDevice aus.
- Slot-Counter zaehlt jetzt nach dedupe (totalRegistered).
- getDmConversations: DISTINCT ON (partner) ORDER BY partner, created_at DESC
→ one row per conversation in a single indexed query instead of fetching
up to 500 rows and de-duplicating in JS
- add indexes on direct_messages (sender_id,created_at DESC),
(receiver_id,created_at DESC), (receiver_id,read_at) — table had none, so
every conversation-list load (runs per user on app launch for the badge)
was a full-table scan + sort
- lyra.tsx: drop the welcome-back greeting that fired on every first coach
open per session regardless of protection status/language (always German,
unconditional). Endpoint kept for future conditional use
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
serverAssets approach didn't bundle the template into the Nitro
output (no .output-staging/server/chunks/raw/ dir, no asset-storage
mount in nitro.mjs). Logs confirm: '[Magic] Profile template missing
in serverAssets'.
Drop serverAssets entirely. Inline the template (~2KB) as a TS
constant in backend/server/utils/magic-profile-template.ts. Build-
robust, no FS/storage dependency at runtime. Canonical source of
truth remains ops/mdm/rebreak-mac-dns-filter.mobileconfig — keep in
sync manually until/unless we add a codegen step.
Beim Aufnehmen ersetzt jetzt eine volle Pill-Bar die Eingabe:
- Links: Trash-Icon (neutral, dezent)
- Mitte: Live-Dot (brandOrange) + animierte Waveform-Bars + Timer
- Rechts: Senden-Button (brandOrange, Pfeil-Icon)
Keine roten Farben mehr, kein separater Mic-Button beim Aufnehmen.
Mic-Button verschwindet komplett während Recording (erst wieder
sichtbar wenn aufgehört). Konsistent mit Rebreak-Farbschema.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
scrollToEnd() unterschätzt Content-Höhe auf Android und stoppt
konsistent eine Message zu früh (verifiziert per adb-Screenshot).
scrollToOffset({offset:999999}) wird auf den echten Max-Wert geclampt
und landet immer am absoluten Ende der Liste.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
User generiert 4-stelligen Code in der App, setzt ihn manuell als
Screen Time Passcode → ReBreak speichert ihn auf dem Backend.
Damit kann niemand Screen Time deaktivieren → deny-removal bleibt
aktiv → App nicht deinstallierbar ohne den Passcode.
Backend:
- Profile.screentimePasscode Feld (Migration add_screentime_passcode)
- POST /api/protection/screentime-passcode — Code speichern
- GET /api/protection/screentime-passcode — Code abrufen (nach Cooldown)
iOS UI (blocker.tsx):
- ScreentimePasscodeCard erscheint wenn Layer 1 + 2 aktiv (iOS only)
- Code-Generierung → Einmal-Anzeige → Deep-Link zu Settings → Screen Time
- Bestätigung speichert Code auf Backend, Card zeigt Confirmed-State
Locales: DE/EN/FR/AR screentime_* Keys
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
iCloud-Sign-In Pattern: wenn ein neues Gerät versucht sich anzumelden
und das Plan-Limit erreicht ist, kann der User auf einem bereits
angemeldeten Gerät bestätigen — Code wird auf BEIDEN Geräten gezeigt
für visuellen Vergleich (verhindert Code-Forwarding-Attacken).
Backend:
- New table device_approval_requests + supabase_realtime + RLS
- POST /api/devices/approvals — create (new device)
- GET /api/devices/approvals — list pending (existing devices)
- GET /api/devices/approvals/:id — status poll (new device)
- POST /api/devices/approvals/:id/approve — approve + atomic evict
- POST /api/devices/approvals/:id/reject — reject
- POST /api/devices/approvals/:id/email — trigger email fallback
- POST /api/devices/approvals/email/:token — magic-link approve (no auth)
- Email-Template via Resend (lyra-neutral, security-formal)
- 10min TTL, 6-digit numeric codes (crypto-random)
Frontend (rebreak-native):
- DeviceApprovalIncomingSheet — existing devices: code + device-picker + Allow/Reject
- DeviceApprovalPendingSheet — new device: code + spinner + 'Send via email'
- useDeviceApprovalRealtime — postgres_changes subscription
- DeviceLimitReachedSheet — neues CTA 'Auf anderem Gerät bestätigen'
- i18n DE/EN/FR/AR
Migration läuft automatisch via prisma migrate deploy bei push.
The smart isNearBottomRef gating was too restrictive — own sent messages,
image-loads, and incoming partner messages were sometimes not scrolled to.
Adopt the room-chat pattern: always scroll on messages.length change and
onContentSizeChange. Drop isNearBottomRef + firstContentSizeChangeRef +
onScroll handler.
MDM-VPN-Pivot (Phase F.2 done):
- ops/mdm/profiles/rebreak-iphone-protection.mobileconfig auf v5 mit
com.apple.vpn.managed Payload + OnDemandUserOverrideDisabled. iPhone-User
kann ReBreak-VPN-Profile nicht entfernen und "Bedarf verbinden"-Toggle
ist disabled. allowEnablingRestrictions empirisch widerlegt für FC-Toggle-
Lock — out.
- DEV-removable Variante als Test-Profile dazu.
- Bootstrap-Tool (rebreak-supervise.sh) + Supervision-Identity-Setup-Doc.
- PHASES.md updated mit empirischen Befunden.
App-side MDM-Detect (Pfad-a Banner-Logic):
- modules/rebreak-protection: getDeviceState() returnt mdmManaged via
Heuristik NETunnelProviderManager.count > 1 (App selbst kann nur einen
eigenen erstellen, MDM-Push fügt einen zweiten hinzu).
- DeviceLayers.mdmManaged?: boolean Type.
- blocker.tsx: lockedIn-Bedingung erweitert um mdmManaged. Bei MDM-managed
iPhones wird der App-Lock-Card (FC-Authorization-Toggle UI) ausgeblendet
weil der per-App FC-Toggle nicht lockbar ist und durch den MDM-VPN-Layer
redundant.
Layer-2-Country-Curated-Pivot:
- backend: vip-swap.post.ts raus, suggest.post.ts rein. Curated-domains
durch admin (separate Tabelle/Pfad), getrennt von User-Custom-Domains.
- Admin-APIs für curated-domain Pflege (index.get + [id].patch).
- seed-country-blocklists Script für initiale Curated-Domain-Liste.
- protection/webcontent-domains.get refactored für Country-Curated-Pfad.
- Migration drop_vip_swap_fields.sql + schema.prisma adjusted.
- docs/concepts/layer2-country-pivot.md mit Architektur + Decision-Trail.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Der setTimeout(80)+onImageLoad-Ansatz war ein Timing-Hack gegen ein
strukturelles Problem (lazy Item-Measurement unter Fabric -> scrollToEnd
landet zu kurz). Stattdessen jetzt inverted FlatList: Index 0 sitzt
permanent am Bildschirmrand, neueste Nachricht immer sichtbar.
- dm.tsx: inverted + reversedMessages, Gruppen-Logik gespiegelt,
manuellen Auto-Scroll + keyboardHeight-State entfernt
- ChatBubble.tsx: onImageLoad-Prop entfernt (obsolet)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Der VipSwapSheet wurde im selben Tick geöffnet wie der AddDomainSheet
dismisst — iOS verschluckt dann das zweite Modal, der Swap-Dialog kam
nie sichtbar. 320ms-Delay (Muster wie fromDetailsToExplainer).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- useCustomDomains: CustomDomain um vipDeferUntil/vipEvictAt, AddDomainResult
um vipFull/newDomainId; addDomain liefert vipFull durch; submitVipSwap()
- VipSwapSheet (neu): Dialog wenn VIP voll — User wählt eine eigene Domain,
die in 24h ersetzt wird
- VipDomainList: Badge „wird in Xh ersetzt" auf der ersetzten Kachel
- blocker.tsx: vipFull → AddDomainSheet zu, VipSwapSheet auf
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Bug 1 — Chat-Liste: RefreshControl nutzte React-Querys `isRefetching`,
das bei JEDEM Background-Refetch (focus-/stale-getriggert) true wird →
nach Zurück-Navigation hing der Pull-to-Refresh-Spinner endlos. Fix:
eigener `userRefreshing`-State, nur bei explizitem Pull-to-Refresh true,
im finally zurückgesetzt.
Bug 2 — Conversation scrollte nicht bis zur letzten Nachricht, wenn die
ein Bild war: onContentSizeChange-scrollToEnd feuerte vor dem Bild-Load.
Fix: ChatBubble bekommt onImageLoad-Callback, die letzte Bild-Nachricht
triggert nach dem Laden erneut scrollToBottom.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Neue RedirectTestCard im Debug-Screen mit zwei Buttons:
- Kontrolle: tipico.de direkt öffnen
- Test: httpbin-302-Redirect → tipico.de
Spielt den Casino-Mail-Fall nach (erlaubter Zwischen-Host → 302 →
blockierte Domain), um zu prüfen ob der DNS-Filter die Zieldomain auch
nach einem Redirect noch sinkholet. Frontend-only.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- PostCard: Bilder mit borderRadius 10 + overflow:hidden — Ecken wieder rund
- dm.tsx: myUserId synchron aus useAuthStore statt async getSession —
behebt den hängenden Lade-Spinner beim Zurück aus einer Conversation
(async getSession-Fenster auf jedem Mount → enabled-Flackern der Query)
- ChatBubble: expo-image memory-disk-Cache + 200ms-Transition für
smootheres Bild-Laden
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Custom-Domain-Slots sind jetzt EIN gemeinsamer Pool für web + mail
(Pro 10 / Legend 20) statt getrennter web/mail-Buckets. Free-Tier ist
entfallen — PLAN_LIMITS hat nur noch pro + legend, getPlanLimits
defaultet auf pro.
Backend:
- plan-features: customDomains ist eine Zahl (CustomDomainLimits weg)
- index.post: Slot-Check gegen Gesamt-Count, Fehler einheitlich LIMIT_REACHED
- index.get: liefert { items, count, limit }
- change-preview + coach/message an die neue Form angepasst
Frontend:
- useCustomDomains: count/limit (Zahlen) statt countsByType/limits
- AddDomainSheet: ein generischer Limit-Hinweis (error_limit_reached)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Blocker-UI:
- FilterTile: Trash-Button → status-aware Freigabe-Button (Freigeben/Erneut/
in-Prüfung); RemoveDomainSheet entfernt — kein Domain-Entfernen mehr in der UI
- VIP-Liste landabhängig: zeigt die komponierte Endpoint-Liste statt nur
eigener Customs; Land über Geräte-Region (expo-localization)
- VIP-Realtime: refetch bei Domain-Add/Approve/Reject, pulsierender Ring
für neue/active/submitted Chips
VIP-Komposition (webcontent-domains):
- Hybrid: Customs auf 30 gekappt, 20 Slots fest für die kuratierte Top-Liste
reserviert — Customs können die Top-Gambling-Domains nicht verdrängen
Add-Check (custom-domains POST), für web reaktiviert — 3 Fälle gegen
Layer 1 (global) + Layer 2 (kuratierte VIP):
- weder global noch kuratiert → normaler active-Eintrag
- global + kuratiert → alreadyProtected, kein Slot
- global, nicht kuratiert → inGlobalNotVip; per addToVip als status=approved
speicherbar (kein Slot, nur VIP-Liste)
DE-Gambling-Liste 30→36, nach Relevanz sortiert (erste 20 = reservierte Plätze)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Meine Filter (unified web + mail_domain, Typ-Badge pro Kachel, Slot-Pool-
Zaehler) + collapsible VIP-Liste (Zweitschutz-Beschreibung, read-only). Die
3 redundanten Web-Bloecke (CustomFilterOverview + DomainSection Eigene Domains)
entfernt. Slot-Pool rein frontend (limits.web+limits.mail), kein Backend noetig.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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>
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>
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).
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.
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).
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>