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>
284 lines
15 KiB
Markdown
284 lines
15 KiB
Markdown
# 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 -- <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
|
||
```
|