rebreak-monorepo/docs/internal/RECOVERY_LOG_2026-05-10.md
chahinebrini 5d6c322129 wip: KeyboardAwareSheet migrations + Snake/Tetris UI + iron.png + useMe live-update
Sheets via neuer KeyboardAwareSheet-Composable (in Modal pattern, auto-grow
mit Tastatur, paddingBottom-Lift): EditMail, AddDomain, CreateRoom, ConnectMail.
GameOverScreen behält Spring-Slide-In, nutzt RN Keyboard.addListener für Lift.

- KeyboardAwareSheet.tsx — universal modal with sheet-grow + keyboard-padding
- react-native-keyboard-controller installiert + KeyboardProvider in Root
- Snake: time + ScoreProgressBar + useSnakeSounds (haptic, audio TODO)
- Tetris: title weg, Buttons zentriert, kein Pressable mit style-fn
- DPad-Buttons 60→48, more bg, no scale
- useMe: pub-sub listener pattern für app-weite avatar/nickname-Updates
- dm.tsx: resolveAvatar wrap (iron.png-Warning)
- Mail-error-humanizer + locales

Recovery-Doc-Update in docs/internal/RECOVERY_LOG_2026-05-10.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 23:59:25 +02:00

284 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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:5118: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 -- <pfad>` 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 <sha>` 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 <sha>
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 <sha> --stat
# Files aus einem dangling commit zurückholen (ohne git history zu touchen):
git checkout <sha> -- <pfad/zur/datei>
# 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 → `<GameOverScreen />` 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 `<KeyboardProvider>` (`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…) | **`<KeyboardAwareSheet>`** Composable (`components/KeyboardAwareSheet.tsx`). Kapselt Modal + Backdrop + Slide-In + Sheet-Grow + Form-an-Bottom-Spacer. **Beispiel:** `EditMailAccountSheet.tsx`. |
| **Vollbild-Form** (Auth, Profile-Edit, Signup) | `<KeyboardAvoidingView />` 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.) | `<KeyboardStickyView />` 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 `<KeyboardAwareSheet>` 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: `<KeyboardProvider>`
- Components ersetzen: `<KeyboardAvoidingView />` 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
- **`<KeyboardAvoidingView behavior="padding">`** — 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 `<TouchableWithoutFeedback><View style={...}>` Pattern.
- **Driver-Mix auf einem `<Animated.View>`** — 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
```