# Changelog

All notable changes to rebreak-native will be documented in this file.
## v0.4.5 (Build 90 / versionCode 71) — 2026-06-10\n\n# Next Release — Notes

- Fix: the "Protection is off" sheet and the "Protection tampered" push no longer
  fire on devices where protection was never activated locally (fresh installs,
  new devices, simulators). The bypass state now requires a local
  "was active here" flag, set after a successful activation — instead of relying
  on the account-wide backend default. Real bypasses (VPN/profile removed on a
  device that had protection) are still detected.
- Copy: blocklist size updated 208k → 300k across all locales (de/en/fr/ar).
- Lyra avatar: richer, more present reactions. While you type or record a voice
  message, Lyra shows a note-taking pose (like a therapist listening) and keeps
  it while she prepares her reply, then reacts to the tone of the conversation.
- Fix (Android 16): activating VPN protection no longer crashes the app. The
  DNS-filter foreground service used a service type that Android 16 rejects at
  start; switched to the correct `specialUse` type so the filter starts reliably
  on Android 14, 15 and 16.
- Onboarding: the protection step can no longer trap you. If protection can't be
  activated on your device (or activation fails), a "set up later" option now
  appears so you can finish onboarding and enable protection anytime from the
  Protection screen.
- Fix (Android, esp. Samsung): protection no longer silently dies when the phone
  puts the app to sleep. Setup now includes a "turn off battery optimization"
  step — without it, aggressive power management could unbind the accessibility
  lock so protection stopped enforcing while still looking active. On Samsung a
  hint guides you to the stronger "Unrestricted" battery setting.
- Fix (Android): the protection status is now honest. If the accessibility
  service isn't actually running, the app no longer shows "fully protected" — it
  correctly shows the step as open and prompts you to re-enable it.
- Android setup polish: activating the VPN now shows a short spinner until it's
  confirmed (the step no longer looks stuck for a minute), and enabling the
  accessibility lock no longer needs a second tap.
- Stability: realtime updates no longer log spurious errors while the app is in
  the background (the websocket close on suspend is expected and now silent).\n
## v0.4.4 (Build 90 / versionCode 70) — 2026-06-08\n\n# Next Release

## Improved
- Protection lock (Android) is now surgical: it only blocks Rebreak's *own* sensitive screens — deactivating Rebreak's device administrator, turning off Rebreak's accessibility service, and disconnecting or changing Rebreak's VPN. Managing, force-stopping or uninstalling *other* apps is completely unaffected, and the accessibility-services list, device-admin list and other apps' info pages stay fully navigable. (Previously the lock could over-block entire settings lists, which also risked a Play review rejection.)
- Uninstall protection now relies on the device administrator: the OS itself greys out "Uninstall" and "Force stop" for an active admin, and the only bypass — deactivating the admin — stays locked by the accessibility service. Net effect: Rebreak can't be removed, but you are never blocked from removing or force-stopping any other app.
- Accessibility onboarding guide: while you're in Android's settings, a passive on-screen hint ("Rebreak: …") now appears at the bottom to point you to the right toggle. After you switch the service on, Settings is reset to its home screen (no leftover deep page or search term) and you're routed back to the app after a short moment so it reliably detects the service.\n
## v0.4.4 (Build 90 / versionCode 70) — 2026-06-08\n\n# Next Release

## Improved
- Protection lock (Android) is now surgical: it only blocks Rebreak's *own* sensitive screens — deactivating Rebreak's device administrator, turning off Rebreak's accessibility service, and disconnecting or changing Rebreak's VPN. Managing, force-stopping or uninstalling *other* apps is completely unaffected, and the accessibility-services list, device-admin list and other apps' info pages stay fully navigable. (Previously the lock could over-block entire settings lists, which also risked a Play review rejection.)
- Uninstall protection now relies on the device administrator: the OS itself greys out "Uninstall" and "Force stop" for an active admin, and the only bypass — deactivating the admin — stays locked by the accessibility service. Net effect: Rebreak can't be removed, but you are never blocked from removing or force-stopping any other app.
- Accessibility onboarding guide: while you're in Android's settings, a passive on-screen hint ("Rebreak: …") now appears at the bottom to point you to the right toggle. After you switch the service on, Settings is reset to its home screen (no leftover deep page or search term) and you're routed back to the app after a short moment so it reliably detects the service.\n
## v0.4.1 (Build 87 / versionCode 67) — 2026-06-08\n\n# Next Release

## Improved
- Onboarding accessibility step (Android): now opens a guided sheet first that explains the two parts most people get stuck on — allowing "Display over other apps", then finding ReBreak in the list and flipping the switch — with a screenshot and an indicator showing exactly where to tap. Shown only on the first attempt.
- Lyra now talks you through onboarding automatically (voice auto-enabled while onboarding; mutable any time via the speaker button, and your app-wide voice setting is left untouched afterwards).
- Lyra's avatar no longer freezes on intro animations (e.g. the empathy wave) — it now settles back into a living idle loop instead of stopping on the last frame.
- Onboarding protection step now has a back button.\n
## v0.4.0 (Build 85 / versionCode 66) — 2026-06-07\n\n# Next Release

## New
- Device detail view: tap any device on the Devices page to open a sheet with its connection status, when it was connected, and a protected/unprotected coverage donut (same visual as your profile)
- "New device connected" push notification — when a Mac or Windows computer is bound via Rebreak Magic, your phone(s) get notified
- Devices page now updates in real time the moment a new computer is paired — no manual refresh needed
- Onboarding now sets up the full protection using the exact same guided, gated step flow as the protection screen (single source of truth) — Android: VPN → Device Administrator → Accessibility (strict order: the tamper lock has to come last, otherwise it would block the device-admin screen); iOS: App Lock → Screen Time passcode → content filter. Previously the device-admin / screen-time hardening steps only existed in the protection screen after onboarding.
- Before each system permission dialog in onboarding, a short confirm sheet now explains exactly which button to tap (e.g. on iOS app lock: "tap the bottom button, not the blue one") and requires a deliberate "I understand" checkbox — stops users from blindly clicking past the dialog and tapping the wrong button.

## Changed
- Device list now shows device-specific icons (iPhone / Android / MacBook / PC) instead of generic outlines
- Stationary protection (Mac/Windows) now runs exclusively via Rebreak Magic — the manual offline profile download has been removed. The offline profile would have shipped the removal password in plain text inside the file (bypass risk); with Magic the lock password stays server-side and is never shown to the user.
- Mac DNS profile hardened with `ProhibitDisablement` — the filter can no longer be toggled off in System Settings.

## Fixed
- Android onboarding: if the VPN permission dialog failed to open (e.g. another always-on VPN active, work profile, or certain OEM quirks), the protection step would silently get stuck with no dialog and no error message — especially on Play Store builds, where the underlying error was swallowed. The step now surfaces the real error and offers a retry instead of dead-ending.\n
## v0.3.13 (Build 85 / versionCode 64) — 2026-06-07\n\n# Next Release

## New
- Add Windows PC as a protected device: the "Add device" menu on the Devices page now opens the Rebreak Magic pairing flow for both Mac and Windows (6-digit code, like the Mac app)
- Device overview now shows separate progress bars for mobile (iOS/Android) and computer (Mac/Windows) slots
- New device matrix: Pro = 1 mobile + 1 computer, Legend = 3 mobile + 2 computers (seamless protection on up to 5 devices)

## Improved
- Rebreak Magic sheet: choose between Mac and Windows, platform-specific download links and instructions, desktop slot counter
- Rebreak Magic sheet is now fully localized (DE/EN/FR/AR) — previously German-only
- Settings: Rebreak Magic entry now mentions Mac & Windows protection

## Fixed
- Devices page no longer shows the legacy desktop enrollment flow (replaced by Magic pairing)\n
## v0.3.13 (Build 84 / versionCode 64) — 2026-06-07\n\n# Next Release

## Features

- **iOS protection setup is now a guided 3-step flow (unsupervised devices).** The two
  separate toggles (URL filter + app lock) are replaced by a clear, gated sequence
  with a progress indicator: Step 1 activate FamilyControls, Step 2 set the Screen
  Time passcode (now mandatory, no longer optional), Step 3 activate the VPN / URL
  filter. The banner turns fully green only when all three are done. The flow
  reflects the real current state on open (no reset). MDM/NEFilter devices and the
  Android path are unaffected.
- The Screen Time passcode step no longer shows an "open settings" button. iOS does
  not allow third party apps to deep link to Screen Time, so the step now shows clear
  written instructions plus the generated code and a confirmation button.

## Fixes

- **iOS: the VPN permission dialog no longer pops up aggressively on every app open.**
  It now only appears when protection should be active and the VPN profile was
  actually removed (the bypass case). During setup or normal operation it stays quiet.
- **Calls: incoming call no longer auto-rejects after force-quit (iOS).** The
  launch-time CallKit zombie cleanup was firing in the same launch as a freshly
  reported cold-start incoming call and ending it. The cleanup is now delayed and
  conditional (5s grace, only when the call store has no live call), so a real
  incoming call is preserved while leftover zombies are still cleared.
- **Dev build no longer crashes on launch** when the CallKit native module is absent.
  Calls are simply disabled in builds without it; production builds are unaffected.

## Polish

- **Avatar placeholders are now a neutral grey instead of blue.** Users without a
  photo show their initials on a subtle grey circle (iOS contact style) rather than
  the dominant blue. Applies to the feed, DMs, header and profile.
- **The optional profile milestone prompt now follows cumulative protected days**
  (which never reset) instead of the consecutive streak. Users who relapse and
  restart still reach the milestones over time, so the friendly profile nudge reaches
  everyone, not only users with a long unbroken run.

## Notes

- Protection-coverage streak (half-donut + progress bar): backend is live, this build
  picks up the frontend. Coverage accumulates from the first protection-state report.\n
## v0.3.13 (Build 82 / versionCode 63) — 2026-06-07\n\n# Next Release

## Features

- **iOS protection setup is now a guided 3-step flow (unsupervised devices).** The two
  separate toggles (URL filter + app lock) are replaced by a clear, gated sequence
  with a progress indicator: Step 1 activate FamilyControls, Step 2 set the Screen
  Time passcode (now mandatory, no longer optional), Step 3 activate the VPN / URL
  filter. The banner turns fully green only when all three are done. The flow
  reflects the real current state on open (no reset). MDM/NEFilter devices and the
  Android path are unaffected.
- The Screen Time passcode step no longer shows an "open settings" button. iOS does
  not allow third party apps to deep link to Screen Time, so the step now shows clear
  written instructions plus the generated code and a confirmation button.

## Fixes

- **iOS: the VPN permission dialog no longer pops up aggressively on every app open.**
  It now only appears when protection should be active and the VPN profile was
  actually removed (the bypass case). During setup or normal operation it stays quiet.
- **Calls: incoming call no longer auto-rejects after force-quit (iOS).** The
  launch-time CallKit zombie cleanup was firing in the same launch as a freshly
  reported cold-start incoming call and ending it. The cleanup is now delayed and
  conditional (5s grace, only when the call store has no live call), so a real
  incoming call is preserved while leftover zombies are still cleared.
- **Dev build no longer crashes on launch** when the CallKit native module is absent.
  Calls are simply disabled in builds without it; production builds are unaffected.

## Polish

- **Avatar placeholders are now a neutral grey instead of blue.** Users without a
  photo show their initials on a subtle grey circle (iOS contact style) rather than
  the dominant blue. Applies to the feed, DMs, header and profile.
- **The optional profile milestone prompt now follows cumulative protected days**
  (which never reset) instead of the consecutive streak. Users who relapse and
  restart still reach the milestones over time, so the friendly profile nudge reaches
  everyone, not only users with a long unbroken run.

## Notes

- Protection-coverage streak (half-donut + progress bar): backend is live, this build
  picks up the frontend. Coverage accumulates from the first protection-state report.\n
## v0.3.13 (Build 79 / versionCode 62) — 2026-06-06\n\n# Next Release

## Fixes
- **Calls: fixed phantom/zombie incoming calls (iOS).** After an incoming call ended without the in-app call screen ever mounting (iOS shows the native CallKit banner, not our `/call` screen), the call store stayed stuck in the `ended` state forever. The `ended → idle` reset only lived in the `/call` screen, which never mounts for banner-only incoming calls. A stuck `ended` state then silently blocked every subsequent incoming call (RING + VoIP push were received but ignored by the `status !== 'idle'` guard), so accepting from the banner produced a phantom CallKit call that ticked as "active" with no real connection, and the caller saw a missed call.
  - Store now self-heals back to `idle` after a call ends, decoupled from the call screen (fallback timer in `teardown`).
  - `receiveIncoming` and the realtime ring handler now treat a stale `ended` state as acceptable (clear + proceed) instead of dropping the new call.
  - `onAnswer` now ends the native CallKit call when the store has no `incoming` call, preventing a phantom "active" call.
  - `RNCallKeep.endAllCalls()` on app launch clears leftover CallKit zombies from a previous session.
- **DM header: online dot now matches the online text.** The green online dot on the partner avatar used the follow-gated presence (`isOnline` = online AND you follow them), while the "online" text next to it used raw presence. In a DM the dot now uses raw presence too, so it shows whenever the partner is online — consistent with the text, regardless of follow relationship. (Looked like an Android-only bug but was the follow gate + asymmetric follow between the test accounts.)\n
## v0.3.13 (Build 76 / versionCode 59) — 2026-06-04\n\n### Fixes

- DM image viewer: opening a shared photo no longer jitters — the image now fills the screen smoothly instead of snapping from a square placeholder to its real size on load
- DM info sheet: tapping a shared image now opens the full-screen viewer on top of the sheet and returns to the sheet when you close it, instead of kicking you back to the DM
- DM header status: the online / typing indicator no longer flickers. "Online" is now held steady through brief presence hiccups (no more rapid switching to "last seen"), and "typing…" stays stable while the other person is composing instead of dropping out on every thinking pause
- Lyra Coach chat: Lyra no longer pulls in leftover content from your last SOS crisis session — the SOS flow and the casual Coach chat are now fully separated. Stray internal prompts and raw text that could surface as chat bubbles are filtered out, and any already-affected chat history cleans itself up the next time you open the Coach
- Chat list: conversations now sort newest-first and re-order live — as soon as a new message or call comes in (from the other person or sent from any of your devices), that chat jumps to the top, instead of the list staying in a fixed order
- Chat list: removed the phone emoji in front of "Audio call" in the conversation preview for a cleaner look
- DM typing indicator: moved out of the header into an Instagram-style in-thread bubble at the bottom of the conversation (partner avatar + animated wave dots). The header now stays on online / last-seen, and the "typing…" bubble auto-scrolls into view\n
## v0.3.13 (Build 76 / versionCode 59) — 2026-06-04\n\n### Fixes

- DM image viewer: opening a shared photo no longer jitters — the image now fills the screen smoothly instead of snapping from a square placeholder to its real size on load
- DM info sheet: tapping a shared image now opens the full-screen viewer on top of the sheet and returns to the sheet when you close it, instead of kicking you back to the DM
- DM header status: the online / typing indicator no longer flickers. "Online" is now held steady through brief presence hiccups (no more rapid switching to "last seen"), and "typing…" stays stable while the other person is composing instead of dropping out on every thinking pause
- Lyra Coach chat: Lyra no longer pulls in leftover content from your last SOS crisis session — the SOS flow and the casual Coach chat are now fully separated. Stray internal prompts and raw text that could surface as chat bubbles are filtered out, and any already-affected chat history cleans itself up the next time you open the Coach
- Chat list: conversations now sort newest-first and re-order live — as soon as a new message or call comes in (from the other person or sent from any of your devices), that chat jumps to the top, instead of the list staying in a fixed order
- Chat list: removed the phone emoji in front of "Audio call" in the conversation preview for a cleaner look
- DM typing indicator: moved out of the header into an Instagram-style in-thread bubble at the bottom of the conversation (partner avatar + animated wave dots). The header now stays on online / last-seen, and the "typing…" bubble auto-scrolls into view\n
## v0.3.13 (Build 71 / versionCode 54) — 2026-06-04\n\n### Fixes

- Voice calls: in-call speaker button now actually routes audio to the phone speaker (instead of being a no-op). The route is re-applied after WebRTC has set up its audio session, so it survives the call-connect transition
- DM screen: keyboard really stays open now when tapping the send button — the previous fix only handled the keyboard-Enter case. Send button stays mounted while the message is being uploaded so the touch target doesn't disappear underneath your finger
- DM screen: tuned the gap above the input bar so the last message sits at a comfortable middle distance (not too tight, not floating too high)
- DM info sheet: the partner avatar now renders correctly for users with a default/list avatar (not just custom photo uploads), using the same avatar component as the header. The chevron now sits inline right next to the name
- DM info sheet: tapping a shared image now opens the same full-screen viewer as in the chat (rounded corners + save button) instead of doing nothing behind the sheet

### Features

- Voice calls now show up in the chat: after every call the conversation bubbles up to the top of the chat list with "📞 Audio call" as the last-message preview, and an inline call-note row appears in the chat thread itself (Instagram-style — with duration if the call connected, or "Missed call" / "Call declined" / "No answer" if it didn't)
- Voice calls: new speaker button in the call screen between Mute and Hangup (volume-medium icon → volume-high when active). Works on both iOS (AVAudioSession route override) and Android (AudioManager speakerphone)
- Mobile dev tooling: new `./dev.sh mobile` command auto-detects connected iPhone (USB) + Android (ADB) and builds + launches dev clients on both in parallel, sharing a single Metro bundler
- DM image viewer is now a swipeable gallery — tapping any photo (in the chat or the info sheet) opens a carousel of all images shared in that conversation, starting at the one you tapped. Swipe left/right to browse, with a position counter (e.g. 2 / 6); the save button always targets the current image

### Changes

- DM chat background is now always the clean solid style (white in light mode, black in dark) — removed the per-chat background picker again for simplicity
- DM voice notes restyled to Instagram-style waveforms: incoming notes have black bars on a light grey bubble, your own notes have white bars on a mint-green bubble. While playing, the upcoming part dims to grey and fills back in as it progresses\n
## v0.3.13 (Build 70 / versionCode 53) — 2026-06-03\n\n### Fixes

- DM screen: tuned the gap above the input bar so the last message sits at a comfortable middle distance (not too tight, not floating too high)
- DM screen: the keyboard now stays open after sending a message (Instagram/WhatsApp style) — it only dismisses when you tap elsewhere, instead of closing on every send
- DM info sheet: the partner avatar now renders correctly for users with a default/list avatar (not just custom photo uploads), using the same avatar component as the header. The chevron now sits inline right next to the name
- DM info sheet: tapping a shared image now opens the same full-screen viewer as in the chat (rounded corners + save button) instead of doing nothing behind the sheet

### Features

- DM image viewer is now a swipeable gallery — tapping any photo (in the chat or the info sheet) opens a carousel of all images shared in that conversation, starting at the one you tapped. Swipe left/right to browse, with a position counter (e.g. 2 / 6); the save button always targets the current image

### Changes

- DM chat background is now always the clean solid style (white in light mode, black in dark) — removed the per-chat background picker again for simplicity
- DM voice notes restyled to Instagram-style waveforms: incoming notes have black bars on a light grey bubble, your own notes have white bars on a mint-green bubble. While playing, the upcoming part dims to grey and fills back in as it progresses\n
## v0.3.13 (Build 69 / versionCode 52) — 2026-06-03\n\n### Fixes

- DM screen: fixed the last message being half-hidden behind the input bar on initial open. The keyboard-closed clearance now accounts for the input bar's safe-area shift (insets.bottom), matching the comfortable gap of the keyboard-open state

### Features

- DM chat background is now per-chat customizable (Instagram-style). Default is a clean solid background (white in light mode, black in dark) instead of the patterned WhatsApp-style background. In the chat's info sheet you can switch each conversation between "Clean" and "Pattern" — stored locally per chat on your device. Incoming bubbles get a subtle grey tint on the clean background so they stay readable. Localized DE/EN/FR/AR\n
## v0.3.13 (Build 68 / versionCode 51) — 2026-06-03\n\n### Fixes

- DM screen: bottom gap on initial open tightened — the last message now sits directly above the input bar. The keyboard-closed padding was double-counting the input bar's own layout slot, leaving a large empty gap every time you opened a chat
- DM image lightbox: photos now actually show rounded corners — the viewer container is sized to the image's real aspect ratio (via onLoad), so the rounding lands on the visible photo instead of the empty letterbox margins of a fixed square
- Lyra coach: removed the "welcome back" greeting that popped up on every first open of the coach each session, regardless of protection status or language (it was always German and unconditional). Will return later only when it's actually warranted
- Chat list performance: the conversation list + unread badge now load via a single indexed query (one row per conversation) instead of pulling up to 500 messages and de-duplicating on the fly — added DB indexes on direct messages. Invisible to users, keeps the chat tab fast as message volume grows

### Features

- DM image lightbox: tap a shared photo → opens with a "Save" button that stores the image to your Photos library (downloads remote images first, asks for photo-add permission once). Localized DE/EN/FR/AR\n
## v0.3.13 (Build 67 / versionCode 50) — 2026-06-03\n\n### Fixes

- DM screen: bottom gap on open tightened — the last message now sits directly above the input bar instead of leaving a visible gap (reduced the keyboard/input-bar clearance padding)

### Features

- DM image lightbox: tap a shared photo → it now opens with rounded corners and a "Save" button that stores the image to your Photos library (downloads remote images first, asks for photo-add permission once). Localized DE/EN/FR/AR\n
## v0.3.13 (Build 65 / versionCode 50) — 2026-06-03\n\n### Fixes

- DM screen: message text bumped a tick larger (14 → 15px, line height 21 → 22) for better readability
- DM screen: faster open — active conversations are now cached in memory for the session (stale-while-revalidate). Reopening a chat shows the messages instantly instead of a loading spinner + full re-fetch every time; the history still refreshes in the background and merges live. Many users reported the chat felt slow to load — this removes the blocking spinner on every open
- DM / tab bar: unread badge no longer stuck — a voice note (or any message) that arrived live via realtime while the chat was already open is now marked read server-side (previously only the history GET marked read), so the count chip on the Chat tab clears correctly after you read & reply and go back
- DM screen: keyboard avoidance — messages now scroll above the keyboard while typing (WhatsApp/Instagram behavior); previously the newest messages were hidden behind the floating input bar
- DM screen: bottom spacing tightened — last message no longer floats too high above the input bar when keyboard is open (was double-counting the input bar height)
- DM screen: voice note replay — playing a finished voice note again now restarts from the beginning (replayAsync) instead of jumping the position dot to the end; resume-after-pause still keeps its position
- DM header: partner online status now reflects real presence live (was gated behind the follow-graph, so DM partners you don't follow never showed as online); updates in realtime via the presence sync channel
- DM screen: voice note waveform redesigned to look natural like WhatsApp — 34 thicker bars (3px, rounded caps) with deterministic per-message amplitude variance + speech envelope (was 80 uniform thin bars that looked hardcoded/solid)

### Features

- Community posts: @mentions are now highlighted — when Lyra posts a thank-you after a custom domain is approved and mentions the requester (e.g. @Hamed), the mention now stands out in the accent color + bold instead of blending into the body text. Works app-wide and unicode-aware (also matches non-latin nicknames)
- DM header: typing indicator — shows "schreibt" with animated pulsing dots when the partner is typing (Instagram/WhatsApp style); ephemeral Supabase broadcast (no DB write), deterministic per-pair channel, throttled send (max 1×/1.5s), auto-clears after 4s, stops immediately on send; neutral-grey color (no green, per online-status convention)
- i18n: presence.typing string added for DE/EN/FR/AR
- Settings → Rebreak Magic: new entry to bind your iPhone via the Rebreak Magic Mac app — generates a 6-digit pairing code (10min TTL, single-use), shows the Mac DMG download, lists connected Macs; no email/password needed on the Mac, the app authenticates purely via the code from your phone
- Add Mac sheet: subtle banner pointing to the new Rebreak Magic flow (existing manual mobileconfig flow stays unchanged for power users)\n
## v0.3.13 (Build 62 / versionCode 50) — 2026-06-03\n\n### Fixes
- DM screen: message text bumped a tick larger (14 → 15px, line height 21 → 22) for better readability
- DM screen: faster open — active conversations are now cached in memory for the session (stale-while-revalidate). Reopening a chat shows the messages instantly instead of a loading spinner + full re-fetch every time; the history still refreshes in the background and merges live. Many users reported the chat felt slow to load — this removes the blocking spinner on every open
- DM / tab bar: unread badge no longer stuck — a voice note (or any message) that arrived live via realtime while the chat was already open is now marked read server-side (previously only the history GET marked read), so the count chip on the Chat tab clears correctly after you read & reply and go back
- DM screen: keyboard avoidance — messages now scroll above the keyboard while typing (WhatsApp/Instagram behavior); previously the newest messages were hidden behind the floating input bar
- DM screen: bottom spacing tightened — last message no longer floats too high above the input bar when keyboard is open (was double-counting the input bar height)
- DM screen: voice note replay — playing a finished voice note again now restarts from the beginning (replayAsync) instead of jumping the position dot to the end; resume-after-pause still keeps its position
- DM header: partner online status now reflects real presence live (was gated behind the follow-graph, so DM partners you don't follow never showed as online); updates in realtime via the presence sync channel
- DM screen: voice note waveform redesigned to look natural like WhatsApp — 34 thicker bars (3px, rounded caps) with deterministic per-message amplitude variance + speech envelope (was 80 uniform thin bars that looked hardcoded/solid)

### Features
- Community posts: @mentions are now highlighted — when Lyra posts a thank-you after a custom domain is approved and mentions the requester (e.g. @Hamed), the mention now stands out in the accent color + bold instead of blending into the body text. Works app-wide and unicode-aware (also matches non-latin nicknames)
- DM header: typing indicator — shows "schreibt" with animated pulsing dots when the partner is typing (Instagram/WhatsApp style); ephemeral Supabase broadcast (no DB write), deterministic per-pair channel, throttled send (max 1×/1.5s), auto-clears after 4s, stops immediately on send; neutral-grey color (no green, per online-status convention)
- i18n: presence.typing string added for DE/EN/FR/AR
- Settings → Rebreak Magic: new entry to bind your iPhone via the Rebreak Magic Mac app — generates a 6-digit pairing code (10min TTL, single-use), shows the Mac DMG download, lists connected Macs; no email/password needed on the Mac, the app authenticates purely via the code from your phone
- Add Mac sheet: subtle banner pointing to the new Rebreak Magic flow (existing manual mobileconfig flow stays unchanged for power users)\n
## v0.3.13 (Build 60 / versionCode 48) — 2026-06-02\n\n### Fixes
- DM screen: keyboard avoidance — messages now scroll above the keyboard while typing (WhatsApp/Instagram behavior); previously the newest messages were hidden behind the floating input bar (KeyboardStickyView moves via transform, viewport didn't shrink — fixed by adding keyboardHeight to FlatList bottom padding)
- DM screen: voice note waveform redesigned to look natural like WhatsApp — 34 thicker bars (3px, rounded caps) with deterministic per-message amplitude variance + speech envelope (was 80 uniform thin bars that looked hardcoded/solid)

### Features
- DM screen: typing indicator in header — shows "schreibt" with animated pulsing dots when the partner is typing (Instagram/WhatsApp style); ephemeral Supabase broadcast (no DB write), deterministic per-pair channel, throttled send (max 1×/1.5s), auto-clears after 4s, stops immediately on send; neutral-grey color (no green, per online-status convention)
- i18n: presence.typing string added for DE/EN/FR/AR\n
## v0.3.13 (Build 56 / versionCode 46) — 2026-06-01\n\n### Features
- DiGA milestone modal: at day 3, 7, 10 clean — celebratory bottom sheet with soft demographic data ask; milestone-specific emoji+color (orange/purple/gold); AsyncStorage tracks per-user/per-milestone shown state; auto-opens DemographicsAccordion in profile; never shows if demographics already filled; dismissed cleanly with "Vielleicht später"
- Lyra coach: gelegentlicher DiGA-Demografie-Hinweis kontextuell im Gespräch — nur bei positiven Momenten, max. einmal pro Session, sofortiges Akzeptieren bei Ablehnung, streng user-initiated (kein heimliches Extrahieren)
- Chat list: search second stage — typed query shows "Neue Unterhaltung" section with user search results below active conversations; debounced 300ms; only shows users not already in conversations; tap → opens DM immediately
- Chat list: last message shows "🎤 Sprachnachricht" / "📷 Foto" fallback when voice/image sent (was showing empty)
- Push notifications: voice messages send "🎤 Sprachnachricht", images "📷 Foto" in preview (was "📎 Anhang" for all)
- DM screen: voice notes (WhatsApp-style) — mic button when input is empty, tap to record, checkmark to send, trash to cancel; audio bubbles with circular play/pause button, position dot, deterministic waveform (played = accent color, unplayed = muted), duration counter, distinct bubble background from text messages
- Shared VoiceRecordingBar component (Coach + DM unified look): trash left, live waveform + timer center, send right; accent color and send icon configurable per context

### Fixes
- Blocker iOS Layer 3 (Screen Time Passcode): card now visible in locked-in state (was hidden once URL filter + App Lock both active — users could never reach it); screentime confirmed status loaded from backend on mount; guarded to unsupervised/VPN+FC path only (not MDM/NEFilter)
- Blocker iOS Layer 3: redesigned card with numbered step instructions (iOS has no deep link to passcode dialog — steps guide user: open ST → tap "Use Passcode" → enter code); URL fallback chain App-Prefs:SCREEN_TIME → App-Prefs:root=SCREEN_TIME → openSettings
- i18n: mic_access permission strings added for DE/EN/FR/AR; Layer 3 step strings added for all 4 languages\n
## v0.3.13 (Build 54 / versionCode 44) — 2026-06-01\n\n### Features
- DM screen: voice notes (WhatsApp-style) — mic button when input is empty, tap to record, checkmark to send, trash to cancel; audio bubbles with circular play/pause button, position dot, deterministic waveform (played = accent color, unplayed = muted), duration counter, distinct bubble background from text messages
- Shared VoiceRecordingBar component (Coach + DM unified look): trash left, live waveform + timer center, send right; accent color and send icon are configurable per context
- DM screen: info sheet (85% height, FormSheet) with shared media grid (3-col), partner profile link, image lightbox
- DM screen: avatar tap in header navigates to partner profile
- DM screen: info icon (ℹ) in header opens info sheet
- Coach: Instagram-style voice recording bar — trash (left) + waveform + timer (center) + send (right)
- Coach: silence/speech detection via audio metering — dots when silent, animated bars when speaking
- Coach: trash button flashes red briefly on cancel (Instagram-style)
- iOS Layer 3: Screen Time Passcode setup flow — generate code, set in iOS Settings, stored on backend

### Fixes
- Blocker iOS Layer 3 (Screen Time Passcode): card now visible in locked-in state (was hidden once URL filter + App Lock both active — users could never reach it); screentime confirmed status loaded from backend on mount; guarded to unsupervised/VPN+FC path only (not MDM/NEFilter)
- Android: Force Stop bypass blocked — Samsung SubSettings/FrameLayout class detection fixed in a11y tamper lock
- Android: Force Stop confirmation dialog now detected and blocked
- Android: a11y service label corrected to "ReBreak — Schutz" (HIGH_CONFIDENCE_KEYWORD match)
- Arabic STT: switched to Deepgram nova-3 (nova-2-general dropped Arabic support)
- DM: scroll-to-bottom now reliable via scrollToOffset(999999) on Android (scrollToEnd miscalculates content height)
- DM: voice recording timer uses Date.now() diff — eliminates Android setInterval jitter
- DM: voice bars fill full width via flex:1 + space-evenly
- DNS filter: own domains (rebreak.org, rebreak.app) bypass blocklist — fixes OAuth Google callback

### Backend
- Mail classifier v1.2: FS-token +20pts, extreme-percent (≥100%) +20pts, casino in sender name +30pts, block threshold lowered 50→40
- Screen Time Passcode API: POST/GET /api/protection/screentime-passcode
- mail_classification_samples row-cap cron: max 100k rows, daily pruning (prevents disk-full)

### Infrastructure
- CI/CD: race condition fixed — deploy lock prevents webhook + GH-Actions colliding
- CI/CD: health check retry loop (12×5s = 60s max) instead of single sleep 5
- Hetzner: 20GB block volume attached, Docker moved to /mnt/data (freed 14GB on root)
- Deepgram nova-2-general → nova-3 for all languages\n
## v0.3.13 (Build 50 / versionCode 40) — 2026-06-01\n\nlayer 3 for ios / fix a11y\n
## v0.3.13 (Build 46 / versionCode 36) — 2026-05-31\n\nDM-Chat: Die letzte Nachricht wird jetzt zuverlässig oberhalb der Eingabezeile angezeigt — kein manuelles Nachscrollen mehr beim Öffnen oder nach dem Senden.

Chat-Übersicht: Zeitangaben sind feiner abgestuft — neben Minuten/Stunden jetzt auch Wochentag (z.B. „Mi"), danach Tage, Wochen, Monate und Jahre statt nur Datum.\n
## v0.3.13 (Build 44 / versionCode 35) — 2026-05-31\n\nDM-Chat: scrollt jetzt zuverlässig zur neuesten Nachricht — auch nach eigenen gesendeten Nachrichten und beim Laden von Bildern.

Lyra-Sprachnachrichten: Wenn du auf Arabisch oder Türkisch sprichst, antwortet Lyra jetzt auch in der richtigen Sprache (Backend-Fix).

Push-Nachrichten bei Direktnachrichten: stabiler — Fehler werden jetzt geloggt, damit verlorene Pushes nicht mehr unsichtbar sind (Backend-Fix).\n
## v0.3.13 (Build 44 / versionCode 33) — 2026-05-31\n\nlyra multilanguage fix\n
## v0.3.13 (Build 42 / versionCode 32) — 2026-05-30\n\nChat-DM: Lange auf eine Nachricht drücken öffnet das Aktions-Menü jetzt direkt an der Nachricht (WhatsApp-Stil) statt zentriert. Neu: Emoji-Reaktionen (oben), Antworten und Kopieren bei fremden Nachrichten, sowie Löschen für eigene Nachrichten (für alle). Reaktionen erscheinen als kleine Bubbles unter der Nachricht. Gelöschte Nachrichten zeigen „Nachricht gelöscht".

Community: Likes an Kommentaren färben sich jetzt sofort und erscheinen in Echtzeit, auch wenn jemand anderes liked.

Optik: Menüs (Profil, Chat-Aktionen) haben jetzt einen sauberen iOS-Blur-Hintergrund. Neuer gebrandeter Startbildschirm beim App-Start.\n
## v0.3.13 (Build 40 / versionCode 31) — 2026-05-30\n\nChat-DM: Lange auf Nachricht drücken öffnet jetzt das native Aktions-Menü (Antworten, Liken, Kopieren). Likes erscheinen Insta-Style als Herz unter der Nachricht.\n
## v0.3.13 (Build 27 / versionCode 18) — 2026-05-30\n\nPush-Notifications für Chat: Du erhältst jetzt Pushes bei neuen Direkt-Nachrichten und Raum-Nachrichten. Abschaltbar in den Einstellungen.\n
## v0.3.13 (Build 26 / versionCode 16) — 2026-05-30\n\nneue push für chat\n
Format: [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
Versioning: `version` follows SemVer, `versionCode` is monotonically increasing.

---

## [Unreleased]

---

## [0.3.6] — buildNumber 15 / versionCode 11 — 2026-05-25

### Changed
- **MDM-managed iPhones**: FC-Authorization-Toggle-UI wird bei MDM-verwalteten Geräten
  ausgeblendet. Der Schutz läuft in diesem Fall vollständig via MDM-managed VPN-Layer —
  kein manueller Toggle nötig oder sinnvoll.

### Backend
- **Layer-2 Country-Curated-Pivot**: Layer 2 ist jetzt ausschliesslich kuratiert (Rebreak-Admin).
  Custom-Domains laufen nur noch über Layer 1 (VPN). Vollständige Entkopplung der beiden
  Schichten; Travel-Detection via Cellular-MCC geplant.

---

## [0.3.5] — buildNumber 14 / versionCode 11 — 2026-05-22

### Fixed
- **Android VPN-Blocklist-Self-Heal** (`9a22bcd`) — Nach der ersten Schutz-Aktivierung
  lud `RebreakVpnService` die Blocklist nicht, wenn sie zur Startzeit noch nicht bereit
  war (0 Domains aktiv). Neuer Self-Heal-Mechanismus: der Service prüft nach Tunnel-Start,
  ob die Blocklist leer ist, und lädt sie mit exponentiellem Backoff nach
  (1 s → 2 s → 4 s, max 3 Versuche). Kein Geräte-Neustart mehr nötig.
- **iOS Layer-1 VPN: File-Protection-Fix** — `blocklist.bin` wird jetzt mit
  `FileProtectionType.completeUntilFirstUserAuthentication` geschrieben statt dem
  strengeren `.complete`. Damit ist die Datei nach dem ersten Entsperren seit Boot
  für die PacketTunnel-Extension dauerhaft lesbar — auch wenn der Tunnel startet,
  während das Gerät gesperrt ist.
- **iOS Layer-1 VPN: Self-Heal** — Blocklist-Retry mit Backoff (3 s → 10 s → 30 s →
  60 s → 120 s → 300 s, max 20 Versuche). War die Blocklist beim Tunnel-Start leer
  (Data-Protection-Fenster), lädt sie sich nach dem ersten Entsperren automatisch nach
  — kein manueller Sync oder VPN-Neustart nötig.

### Added
- **VIP-Liste (Geblockte Domains)**: Eigene Domains und Stichwörter zur persönlichen
  Blocklist hinzufügen. Landabhängige kuratierte Vorschläge (DE, AT, CH, FR, AR).
  Slot-System: 10 Slots für Pro, 20 Slots für Legend — Web und Mail teilen sich einen
  gemeinsamen Pool (kein Free-Tier). Kachel-UI mit Slot-Fortschrittsanzeige.
- **VIP Slot-Replace mit Cooldown**: Bestehende Slots können ersetzt werden; ein
  12-Stunden-Cooldown verhindert schnelles Umgehen der Blocklist.
- **Curated-Domain-Vorschläge**: Beim Hinzufügen einer Domain werden bekannte
  Glücksspiel-Anbieter aus der jeweils passenden Länderliste vorgeschlagen.

### Fixed
- **Chat Inverted-FlatList**: Nachrichten rendern jetzt korrekt von unten nach oben
  (Standard-Chat-Layout). Vorheriges Layout ließ neue Nachrichten oben erscheinen.
- **Chat Listen-Hänger**: FlatList blockierte bei langen Nachrichtenlisten nicht mehr
  den Main-Thread — virtualisierung greift korrekt.

---

## [0.3.0] — versionCode 10 — 2026-05-17

### Added
- **Duo-style Onboarding (9 Slides)**: Vollständiger Onboarding-Flow — Welcome → Privacy → Nickname → DiGA-Choice → DiGA-Code → Plan → Payment → Protection → Done. Lyra-Bubble mit TTS-Audio-Button auf jedem Slide. Pre-Explainer-Screenshots vor iOS-Permission-Dialogen. Confetti-Animation + Top-5-FAQ-Accordion auf Done-Screen.
- **DiGA-Redeem-Endpoint + 100 Test-Codes**: Backend-Endpoint für DiGA-Code-Einlösung. 100 vordefinierte Test-Codes (REBREAK-TEST-001 bis REBREAK-TEST-100) für QA und Reviewer.
- **Lyra Voice TTS Auto-Play + Voice-Button in Bubble**: TTS startet automatisch beim Slide-Einblenden; Voice-Button in der Lyra-Bubble startet/stoppt Audio on-demand. DiGA-Accessibility-Feature.
- **Stripe Web-Checkout-Integration**: Pro 3,99 EUR/Monat, Legend 7,99 EUR/Monat, je 14-Tage-Trial. Checkout-Redirect via `expo-web-browser`, Webhook bestätigt Plan-Aktivierung serverseitig.
- **Arabisch (Arabic) + RTL-Support**: Vollständige arabische Lokalisation mit automatischem RTL-Layout-Switching.
- **NEFilter Robust Disable**: 2-step Apple-Pattern für zuverlässiges Deaktivieren des URL-Filters. `resetUrlFilter` als Recovery-Pfad bei code-5-Fehlern. `ProtectionOffSheet` ersetzt bisherigen Alert.
- **Family Controls always-on**: Kein "Bald"-Placeholder mehr — FamilyControls-Entitlement (Distribution-approved) ist vollständig aktiv.
- **Stripe Tier-Rename + Checkout-Refactor**: Pläne heißen jetzt konsistent `pro` / `legend` (statt alter Naming-Varianten). Checkout-Endpoint neu strukturiert.
- **protectionDisabledAt (Backend)**: Server-seitige Timestamps verhindern Auto-Reaktivierung nach manuellem Deaktivieren durch den User.
- **FaqAccordion-Komponente (shared)**: FAQ-Accordion extrahiert als `components/FaqAccordion` — geteilt zwischen DoneSlide und `help/faq`-Seite.

### Changed
- **ProtectionSlide: Platform.OS-Dispatch**: iOS-Pfad aktiviert NEFilter + FamilyControls. Android-Pfad aktiviert VPN-Permission + a11y-TamperLock (VPN-Permission-Dialog → a11y-Pre-Explainer → a11y-Settings-Open → AppState-Auto-Detect-Return → tamperLock armed → finished).
- **Blocker Single-Banner-Logik**: `lockedIn` ist nur `true` wenn BEIDE Schutz-Ebenen aktiv sind (iOS: urlFilter && appDeletionLock; Android: VPN && a11y). Kein falsches "Locked In" mehr bei Teilschutz.

### Fixed
- **Android Keyboard-Covers-Input (Chat/DM)**: `react-native-keyboard-controller` ersetzt bisherigen KeyboardAvoidingView-Workaround — Input-Feld bleibt beim Tippen korrekt sichtbar.
- **Protection Cooldown Auto-Disable Race**: Stale-State nach Cooldown-Ablauf korrigiert — kein falscher "aktiv"-Zustand mehr.
- **Nickname Validation + Duplicate-Check**: Echtzeit-Prüfung auf bereits vergebene Nicknames im Onboarding.
- **DiGA-Code Auto-Format**: Code-Eingabe formatiert automatisch (Großbuchstaben, Bindestriche).

### i18n
- Französisch (fr) — 3. Sprache (war bereits in v0.2.x als Beta eingeführt, jetzt vollständig).
- Arabisch (ar) + RTL — neu in v0.3.0.

### Backend
- `protectionDisabledAt`-Timestamp auf User-Profil — verhindert Auto-Reactivation nach manuellem Disable.
- DiGA-Code-Redemption-Endpoint (`POST /api/diga/redeem`) — 100 Test-Codes, Plan-Hochstufung auf `legend`.
- Migration `20260517_add_lyra_post_i18n_key`: `i18n_key TEXT` auf `community_posts` (nullable, non-blocking). **Pending Deploy auf Hetzner** — Feature-Flag `USE_TEMPLATE_CATALOG=false` (default) haelt dieses Feature deaktiviert bis Migration deployed ist.

---

## [0.2.1] — versionCode 9 — 2026-05-16

### Fixed
- **Android-Crash nach AccessibilityService-Aktivierung**: BadgeDrawable in der Tab-Bar craste mit `IllegalArgumentException`, weil das Theme `AppCompat` statt `MaterialComponents` nutzte. Theme-Parent auf `Theme.MaterialComponents.DayNight.NoActionBar.Bridge` geändert via Config-Plugin — idempotent nach jedem prebuild.
- **Doppel-Splash auf Android**: Native Android-Splash und JS-Splash sahen unterschiedlich aus (zwei aufeinanderfolgende Splash-Screens). Splash-Image auf `icon.png` umgestellt, matched jetzt exakt den JS-Splash und Landing-Screen.

---

## [0.2.0] — versionCode 8 — 2026-05-16

### Added
- **Mac-DNS-Profil — Vollautomatik**: Die App erkennt per DoH-Handshake, sobald der Mac eine DNS-Anfrage stellt, und springt automatisch auf Schritt 3 im Setup-Flow. Kein manuelles Tippen mehr nötig.
- **Device-Account-Binding**: Pro- und Legend-Konten sind gegen Account-Switch-Bypass geschützt. Wer sich auf einem bereits gebundenen Gerät mit einem anderen Konto anmeldet, bekommt eine Sperre (DEVICE_LOCKED) — und der ursprüngliche Account wird per E-Mail benachrichtigt.
- **Custom Mail-Filter (Cue Avoidance)**: Eigene E-Mail-Adressen oder Domains von Glücksspiel-Anbietern können direkt in der App eingetragen werden. Auto-Detect schlägt bekannte Muster vor. Free/Pro: je 5 Adressen + 5 Domains. Legend: je 10. Die neue Unified-Filter-Section ersetzt alle bisherigen separaten Blocker-Einträge.
- **Like + Kommentar Realtime**: Reaktionen anderer User werden live in der Feed-Karte aktualisiert — kein manuelles Refresh.
- **Chat v1.0 (DM-only)**: Direkt-Nachrichten mit Profilbildern, Suche, Unread-Badge in der Tab-Bar. Gruppen-Chats werden für v1.0 nicht unterstützt.
- **Avatar-Zuschnitt via iOS-Nativ**: Der Profilbild-Zuschnitt nutzt jetzt Apples eigenes UIImagePickerController-Crop statt eines eigenen Sheets — zuverlässiger, native Look.
- **Hilfe-Bereich in Settings**: Neue Seiten für FAQ, Kontakt, Über Rebreak und Krisen-Hotlines. Notifications-Bereich überarbeitet.
- **Global-Blocklist-Vorabprüfung**: Beim Hinzufügen einer Custom-Adresse wird vorab gegen die zentrale Blockliste geprüft — kein Slot wird verbraucht, wenn die Domain sowieso schon global gesperrt ist.
- **FamilyControls standardmäßig aktiv** in lokalen Dev-Builds (`dev-iphone.sh`, `clean-ios.sh`).

### Changed
- **Devices-Seite neu gestaltet**: Zweizeilige Einträge mit animierter Fortschrittsleiste, UIMenu für Mac/Windows-Auswahl, zentrale Button-Komponente durchgehend genutzt.
- **Game-Over-Modal überarbeitet**: Höhenbegrenzung, Keyboard-Avoidance, neuer Regenerate-Button.

### Fixed
- **Mac in Geräteliste-Bug behoben**: Mac-Geräte wurden unter bestimmten Umständen nicht korrekt in der Liste angezeigt.

---

## [0.1.0] — versionCode 7 — 2026-05-15

### Added
- feat(native): device-info API headers — model, name, osVersion sent on every request via lib/api.ts
- feat(native): DeviceLimitSheet visual differentiation — „Dieses Gerät"-Pill distinguishes current device from others in the list

### Changed
- refactor(native): profile-page i18n keys — full sweep of 24 leftover Vue-i18n `{var}` patterns replaced with `%{var}` (vue-i18n-style removed, expo-localization pattern applied consistently)
- chore(native): Google-OAuth always shows account-picker — `prompt=select_account` added to OAuth request, prevents silent re-auth with wrong account
- refactor(native): feed page uses `groupedBg` for card contrast instead of flat background

### Fixed
- fix(native): mail-account-edit modal-conflict + double-click bug — modal coordination corrected, prevents overlapping sheet opens
- fix(native): sign-out now calls `WebBrowser.coolDownAsync` to clear OAuth cookies — prevents stale Google session on next login
- fix(backend): Test-User `charioanouar@gmail.com` correctly identified as MrLonely (device-info schema + merge heuristic + test-user detection, auto-deployed via pipeline)

---

## [0.1.0] — versionCode 6 — 2026-05-15

### Added
- feat(i18n): French as 3rd app language (`fr`) for test customers — full locale file, settings picker, device-locale auto-detect (a981deb-fix)
- feat(native): Lyra voice picker for legend tier — 3 ElevenLabs voices (Default / Voice 1 / Voice 2), backed by PATCH /api/profile/me/lyra-voice (d9bb7ef)
- feat(native): realtime debug page (DEV-only) — connection-state, reconnect-counter, token-expiry countdown, channel list, rolling event-log (a0d67f3)
- feat(backend): Lyra voice TTS-routing — userLyraVoiceId takes priority over default (legend-gated) (76f8595)

### Changed
- refactor(mail): removed Groq LLM borderline layer — deterministic Score+Layer2.5 pipeline only. Resolves DSGVO-P0 items (no third-country data transfer). Layer 2.5 (Brand+Random) continues to catch Apple Hide-My-Email pattern (f2e3c00)

### Fixed
- fix(native): `protectedDevices.filter is not a function` — Array.isArray guard on API response (a0d67f3)

---

## [0.1.0] — versionCode 5 — 2026-05-14

### Added
- feat(mail): multi-layer email classifier — Brand+Random detection, Relay-Decoder (Apple Hide-My-Email), Score-based routing, Groq borderline-review, ML-Sampling pipeline (b2f0471)

### Fixed
- fix(mail): reactive Mail page — stats + connection status refresh on scan/connect (4580a19)
- fix(mail): donut card layout rebuilt from scratch — centered, breathing room, no truncation (2ea0cfe)
- fix(mail): donut card layout justify-start instead of center (aac6c00)
- fix(mail): legend rows justify-between + per-connection bar-chart sparse-data zoom (b47ac24)
- fix(mail): legend takes natural width inside card + bar-chart always trims to hit-range (55cba9a)
- fix(mail): legend vertically centered against donut center number (778d3b6)
- fix(mail): revert marginBottom hack — layout was breaking out of card (1d93ada)
- fix(mail): legend bottom-aligned with donut baseline for visual centering (c218287)
- fix(mail): DSGVO Art. 17 — manual sample cleanup on account delete (be4e163)

---

## [0.1.0] — versionCode 4 — 2026-05-07

### Added
- feat(mail): Outlook OAuth scan + daemon initial-sweep + page polish v4 (8075c8e)

### Fixed
- fix(idle-daemon): sync MS_OAUTH_SCOPES with backend — drop User.Read, add email (fd1cb91)

---
