# Recovery-Log 2026-05-10 — Lost Work + Workflow-Regeln **Stand:** 2026-05-10 **Verantwortlich:** Chahine **Anlass:** verlorene UI-Arbeit nach mehrfachen `git stash`/`cherry-pick`-Zyklen am 9. Mai --- ## 1. Was passiert ist (Timeline) ### 1.1 Auslöser — Cutover-Incident 7. Mai 22:17 `apps/rebreak/` (Nuxt) → `backend/` (Standalone Nitro) Cutover. Force-Push aus dem neuen Mac-Repo zu `RaynisDev/rebreak.git` triggerte den Server-Webhook, der scheiterte: - `cd /srv/rebreak/apps/rebreak`-Pfad existierte im neuen Layout nicht - Auth-Middleware crashed mit HTTP 500 (`Cannot read properties of undefined (reading 'url')`) weil `backend/nitro.config.ts.runtimeConfig` keine `supabase`-Section hatte - ALLE authentifizierten Endpoints kaputt Rollback: `git reset --hard origin/main` → HEAD auf `922d5dc`. Tag `pre-revert-2217` als Sicherung gesetzt. Siehe `ops/CUTOVER_PLAN.md` §1.3 für volle Incident-Beschreibung. ### 1.2 Folgesymptom — Stash-Hopping am 9. Mai Nach dem Reset arbeitete der User intensiv am Cherry-Pick-Workflow zwischen `main` und `upgrade/sdk-54`. Reflog zeigt **10+ Branch-Switches in 4 Stunden** (14:51–18:11). Pattern: ``` commit auf upgrade/sdk-54 → checkout main → cherry-pick (selber Commit, neuer Hash) → checkout upgrade/sdk-54 → uncommitted changes: git stash → ... nächster Commit ... ``` `git fsck --no-reflogs --lost-found` zeigt **9 dangling WIP-Stash-Commits** als Resultat: `wip-pre-cherrypick`, `wip-pre-daemon-fix`, `wip-pre-daemon-push`, `wip-pre-backend-push-2`, `wip-pre-speak-fix-2`, `wip-mdm-session`, plus 3× `wip: sdk-54 ui/backend changes`. ### 1.3 Konkreter Verlust — Commit `35189b9` "wip-pre-cherrypick" Am 9. Mai 17:57 wurde ein Stash mit gerade fertiggestellter UI-Arbeit angelegt — der Stash-Apply lief nicht sauber zurück (Conflict + `git checkout .` zum Aufräumen, oder `git stash drop` ohne saubere `pop`). Die Arbeit landete als dangling Merge-Commit `35189b9`, war aber im Working Tree weg. **Was im Stash war:** | File | Was wäre drin gewesen | |---|---| | `components/games/GameOverScreen.tsx` | 256 → **468 Zeilen**: StarRating, RiveAvatar, tier-aware Lyra-Messages, Rating-Form, Share-to-Community | | `components/urge/UrgeGames.tsx` | Header-Refactor, `scoreLabel`/`goodScore`-Props | | `app/settings.tsx` | LanguageIcon-Block, dynamic icon-rendering | | `locales/de.json` + `en.json` | `gameOver.lyra_title_*` / `lyra_body_*` Keys (record/good/ok/low), Rating-Strings, Share-Strings | User hat das nach 24h beim Test gemerkt: SOS sah aus „wie alter Stand" — kein neuer GameOverScreen, kein Snake-Score-Dashboard, OpenAI-TTS statt ElevenLabs. --- ## 2. Recovery-Aktion 2026-05-10 `35189b9` Files via `git checkout 35189b9 -- ` ins Working Tree zurückgeholt. Locales **chirurgisch** gemerged (Python-Script für `gameOver`-Section only — andere Sections — Mail-Status, Auth-Errors etc. — blieben unangetastet, weil aktuelle de.json/en.json neuere Strings enthielten die in 35189b9 nicht waren). Plus: `urge.tsx` + `lib/sosTtsQueue.ts` umgestellt von `endpointForProvider(currentProvider())` (alter TtsProviderToggle-Pfad mit OpenAI-Default) auf `/api/coach/speak` — der **tier-aware Backend-Dispatcher** (siehe §5). Backend (`backend/server/api/coach/speak.post.ts`) war bereits korrekt fertig (mtime 10. Mai 16:18, Plan-aware: Free→Google / Pro→Cartesia / Legend→ElevenLabs) — kein Touch nötig. --- ## 3. Was JETZT NOCH FEHLT (nicht aus 35189b9 wiederhergestellt) | Feature | Status | Notiz | |---|---|---| | **Game-Sharing-Post** | offen | Aus User-Erinnerung: Game-Result-Sharing zur Community (Post mit Score + Lyra-Caption). Nicht in 35189b9 enthalten — wahrscheinlich anderer dangling stash. **TODO separat**. | | **TtsProviderToggle Wiring** | offen, aber nicht kritisch | Component existiert (`components/urge/TtsProviderToggle.tsx`), nirgends gerendert. Laut `ops/UI_MIGRATION_PLAN.md §3 Tab 5 Debug` gehört der in `__DEV__`-Tab. | Game-Sharing kommt in nächster Session. Anderen dangling stash-Commits prüfen via `git show ` aus `git fsck --lost-found` Output. --- ## 4. Workflow-Regeln gegen Wiederholung ### 4.1 KEIN rapides Stash + Cherry-Pick mehr **Verboten:** mehrere `git stash` hintereinander während Branch-Switching. `git stash list` darf nie länger als 1 Eintrag werden. **Stattdessen (in Priorität):** 1. **`git worktree add`** — zweiter Working-Tree für andere Branches: ```bash git worktree add ../rebreak-main main # Cherry-pick im 2. Worktree, kein stash nötig cd ../rebreak-main git cherry-pick git push cd ../rebreak-monorepo ``` Beide Trees sind unabhängig, parallel benutzbar in zwei IDE-Fenstern. 2. **Commit-First-Pattern** — vor jedem `checkout` immer committen (auch WIP-commits sind besser als stash): ```bash git add -A && git commit -m "wip: in progress" git checkout main # ... arbeit auf main ... git checkout upgrade/sdk-54 # WIP commit unverloren, kann amended werden ``` 3. **NIE `git stash drop`** — nur `git stash pop`. Wenn pop conflicted: NICHT mit `git checkout .` aufräumen, sondern Conflict-Markers manuell auflösen + committen. ### 4.2 Recovery-Kommandos für die Zukunft Falls trotzdem mal wieder Arbeit verloren geht: ```bash # Alle dangling commits auflisten: git fsck --no-reflogs --lost-found # Inhalt eines Commits inspizieren: git show --stat # Files aus einem dangling commit zurückholen (ohne git history zu touchen): git checkout -- # Volles Reflog mit Datum: git reflog --date=format:"%Y-%m-%d %H:%M" ``` Tag `pre-revert-2217` (vom 7. Mai) bleibt als Notbremse-Anker erhalten. ### 4.3 Zwei Branches gleichzeitig sind ein Anti-Pattern Aktuell: `main` (Production) + `upgrade/sdk-54` (Dev). Cherry-Pick-Pflicht zwischen beiden ist die **eigentliche Wurzel** des Problems. **Empfehlung (User-Decision):** sobald `upgrade/sdk-54` stabil ist → `main` durch `upgrade/sdk-54` ersetzen (force-push) und nur noch *einen* Branch fahren. Die GH-Actions-Pipeline deployt von `main`, also nach Force-Push ist alles auf einem Branch konsolidiert. --- ## 5. Tier-Aware TTS-Architektur (jetzt aktiv) Damit klar ist wie das System nach dem Recovery funktioniert: ``` User auf SOS-Page (urge.tsx) → ttsQueue.endpoint = '/api/coach/speak' → POST /api/coach/speak { text, mode: 'sos' } → Backend: speak.post.ts → requireUser(event) → profile.plan aus DB → free → speakGoogle() (60s/day quota) → pro → speakCartesia() (300s/day quota) → legend → speakElevenLabs() (unlimited) → Backend liefert raw audio/mpeg stream → Client erwartet immer raw audio/mpeg (kein isGoogleCloud-Branch mehr nötig) ``` **Wichtig:** Kein User-Toggle. Der Provider hängt **ausschließlich** an `profile.plan`. Wenn ein Pro-User Cartesia-Stimme nicht mag → Plan-Tier muss geändert werden, nicht ein Toggle. `TtsProviderToggle.tsx` Component bleibt im Repo aber ohne Wiring. Falls Debug-Tab gebaut wird (`UI_MIGRATION_PLAN.md §3 Tab 5`), kommt der Toggle dort hin (`__DEV__`-only). --- ## 6. Game-Flow in SOS vs Standalone | Mode | Eintritt | Game-Over-Verhalten | |---|---|---| | **SOS-Mode** (`urge.tsx`) | aus Lyra-Chip „Spiel" | Game endet → `onComplete(score)` direkt → SOS-Session läuft weiter, Lyra antwortet auf Score. **KEIN GameOverScreen.** | | **Standalone-Mode** (`games.tsx`) | aus Header-Dropdown „Games" | Game endet → `` rendert mit StarRating + Lyra-Message + Share-to-Community-Button. Retry/Exit drinnen. | Implementation: `mode: 'sos' \| 'standalone'`-Prop auf SnakeGame/MemoryGame/TicTacToeGame/TetrisGame. Default = `'standalone'`. urge.tsx setzt explizit `mode="sos"`. --- ## 7. Keyboard-Overlap — generische Lösung App-übergreifender Bug: TextInput wird beim Tippen vom Keyboard verdeckt (Mail-Password-Edit, Auth-Forms, Profile-Edit, Demographics, ComposeCard, Chat-Input, etc.). **Aktiver Stack ab 2026-05-10:** [`react-native-keyboard-controller`](https://github.com/kirillzyusko/react-native-keyboard-controller) — de-facto Standard seit 2024 für RN-Keyboard-Avoidance. Native Synced (iOS-Curve pixel-genau), kein Driver-Mix, kein Bouncing. **Setup:** - Bereits installiert: `pnpm add react-native-keyboard-controller` ✓ - Root-Layout wrapped mit `` (`apps/rebreak-native/app/_layout.tsx`) ✓ - **Native-Build nötig nach Install:** `cd apps/rebreak-native/ios && pod install` (Autolinking macht den Rest), dann frischer Xcode-Build **Migrierte Components (Reference-Beispiele):** - `EditMailAccountSheet.tsx` — `useKeyboardAnimation()` + `Animated.subtract(slideY, height)` - `GameOverScreen.tsx` — gleiche Pattern, mit Spring-Slide-In bewahrt ### 7.1 Wann was nutzen Empfehlung in dieser Reihenfolge: | Situation | Lösung | |---|---| | **Bottom-Sheet mit Form/Input** (EditMail, ConnectMail, AddDomain, GameOver, künftig…) | **``** Composable (`components/KeyboardAwareSheet.tsx`). Kapselt Modal + Backdrop + Slide-In + Sheet-Grow + Form-an-Bottom-Spacer. **Beispiel:** `EditMailAccountSheet.tsx`. | | **Vollbild-Form** (Auth, Profile-Edit, Signup) | `` aus `react-native-keyboard-controller` (NICHT von RN!) als Outermost. Drop-in, funktioniert wie erwartet. | | **Sticky-Bottom-Bar über Tastatur** (Send-Button am Screen-Edge, etc.) | `` aus der Library — sticked automatisch über Tastatur. | | **Chat/SOS** (FlatList + Input-Bar) | Wie bisher in `PostCommentsSheet.tsx`. Funktioniert weiter. | | **Legacy** | `hooks/useSheetKeyboardLift.ts` + `hooks/useKeyboardHeight.ts` + `components/KeyboardAdjustedView.tsx` bleiben im Repo aber sollten **nicht mehr neu verwendet werden** — durch `` ersetzt. | ### 7.1.1 Auto-sized Sheets (kein leerer Platz unterhalb des Inhalts) Für kompakte Forms (1 Input + Save-Button — z.B. EditMailAccountSheet): KEINE feste `height` setzen, Sheet auto-sized via `position: 'absolute', bottom: 0`. `useSheetKeyboardLift({ offscreenY: SCREEN_HEIGHT })` für initial-off-screen + Keyboard-Lift. Resultat: Sheet sitzt eng über der Tastatur ohne weißen Leerraum darunter. Für Sheets mit variablem Listen-Inhalt (Comments, längere Forms): `height` setzen. ScrollView braucht constrained height zum scrollen. ### 7.1.2 Library-Migration-Pfad: `react-native-keyboard-controller` De-facto-Standard seit 2024 für Keyboard-Avoidance in RN. Löst alle Pain-Points (Driver-Mix, iOS-Modal-Quirks, Sheet-Lifts, smooth Animationen) systemisch über native Module — kein eigener Animated-Code mehr nötig. Kostet: - `pnpm add react-native-keyboard-controller` - `npx expo prebuild` + iOS pod install (= neuer Native-Build nötig) - Wrapper am App-Root: `` - Components ersetzen: `` von der Library statt RN's eigenes - Plus: `useKeyboardAnimation()` Hook für custom Animationen **Empfehlung:** wenn 2-3 weitere Sheets/Forms Probleme machen → migrieren. Bis dahin: `useSheetKeyboardLift()` Pattern reicht für die meisten Fälle. ### 7.2 Anti-Pattern zu vermeiden - **``** — funktioniert nur in Modals zuverlässig, bricht bei Full-Screens mit `paddingTop: insets.top`. **Nicht mehr neu nutzen.** - **Pressable mit style-Funktion** für Buttons mit kritischem Visual: `style={({pressed}) => ...}` schluckt manchmal Style-Properties (RN-Quirk). Für Buttons mit solidem BG + Border lieber `` Pattern. - **Driver-Mix auf einem ``** — z.B. `height: animatedValue` (JS-driver) zusammen mit `transform: [{ translateY: animatedValue }]` (native driver). Crashed mit `"Style property 'height' is not supported by native animated module"`. **Lösung:** `useSheetKeyboardLift()` Composable nutzt nur translate (beides native). - **`marginBottom: keyboardHeight` als JS-Style** + native transform im selben View → Bouncing weil zwei Threads layouten. **Lösung:** Animated.subtract(slideY, keyboardLift), beides Animated.Values, native driver konsistent. --- ## 8. Open Issues (zukünftige Sessions) ### 8.1 Aus aktueller Session 2026-05-10 verschoben - [ ] **Game-Sharing-Post-Render** — soll genau wie in `trucko-monorepo/apps/rebreak/app/components/CommunityPostCard.vue` (category=`game_share`) aussehen. Aktuell rendert die Native-App einen generischen Post statt einer Game-Share-Card mit Score-Pill + Lyra-Caption + Challenge-CTA. Source-of-truth: Vue-Pendant. - [ ] **Mail-Page-Chart** — `MailWeeklyChart.tsx` ist bereits angelegt aber Render-Logic noch nicht 1:1 vom Nuxt-`mail-stats-chart.vue` portiert. 7-Tage-Bar-Chart mit `account_*`-Stats. - [ ] **iron.png-Warning vollständig fixen** — `dm.tsx` ist gefixt, aber `room.tsx` (3 Stellen: 308, 537, 598) und `components/chat/RoomCard.tsx:52` nutzen noch raw `room.avatarUrl` / `m.avatar`. Wenn das vom Backend ein Avatar-ID statt URL liefert, gleicher Bug. `resolveAvatar()` darüber wickeln. - [ ] **TetrisActionBtn / DPadBtn rollout** — falls noch andere Stellen Pressable-mit-style-funktion nutzen für Game-relevante Buttons, gleichen TouchableWithoutFeedback-Pattern anwenden. ### 8.2 Aus voriger Session - [ ] `KeyboardAdjustedView` rollout über alle TextInput-Stellen (siehe Liste in §9) - [ ] TtsProviderToggle in `__DEV__`-Debug-Tab einbauen - [ ] Single-Branch-Konsolidierung: `upgrade/sdk-54` → `main` Force-Push - [ ] Andere 8 dangling stashes inspizieren ob noch was Wertvolles drin ist - [ ] expo-av Deprecation Warning — Migration zu `expo-audio` + `expo-video` (SDK 54 Pflicht). Tracker. ### 8.3 Snake-Sounds — Audio-Files droppen `hooks/useSnakeSounds.ts` läuft aktuell im Haptic-only-Mode. Für echten 8-Bit-Retro-Sound: 1. `apps/rebreak-native/assets/sounds/` Dir anlegen 2. 4 kurze Audio-Files reinlegen (Free-Quellen: freesound.org, opengameart.org/content/8-bit-sound-pack, sfxr.me): - `snake-eat.mp3` ~80ms tonale "blip" - `snake-move.mp3` ~30ms Tick (optional) - `snake-gameover.mp3` ~400ms abfallende Töne - `snake-record.mp3` ~600ms aufsteigender Chime 3. `useSnakeSounds.ts` öffnen, `require()` und `Audio.Sound.createAsync()` Lines unkommentieren (in der Datei-Doku exakt beschrieben) Nach Drop fallen die Haptics nicht weg — Audio + Haptic feuern dann beide. ### 8.4 Cache-Invalidierung — neuer Pattern in `useMe.ts` Profile-Avatar-/Nickname-Änderungen sind jetzt **app-weit live**: nach jedem `PATCH /api/auth/me` muss `invalidateMe()` aus `hooks/useMe.ts` aufgerufen werden (oder `reload()` einer useMe-Instanz, was intern gleichbedeutend ist). Alle anderen useMe-Konsumenten (AppHeader, ComposeCard, PostCard, NotificationsDropdown, …) re-fetchen via Listener-Subscribe automatisch — kein App-Reload mehr nötig. **Dasselbe Pattern für andere User-Daten** (Streak, Demographics, Devices) wenn das gleiche Bug-Symptom auftritt. ## 9. Files mit TextInput (für KeyboardAdjustedView-Rollout) ``` app/room.tsx app/lyra.tsx app/urge.tsx app/(auth)/signup.tsx app/(auth)/signin.tsx app/(auth)/forgot-password.tsx app/(auth)/confirm-otp.tsx app/profile/edit.tsx components/PostCommentsSheet.tsx ← bereits korrekt (Vorbild-Pattern) components/ComposeCard.tsx components/chat/CreateRoomSheet.tsx components/chat/ChatInput.tsx components/mail/ConnectMailSheet.tsx components/mail/EditMailAccountSheet.tsx ← User-explizit gemeldet components/blocker/AddDomainSheet.tsx components/urge/InlineRatingDrawer.tsx components/urge/SosFeedbackModal.tsx components/urge/ShareSuccessDrawer.tsx components/games/GameOverScreen.tsx ```