386 Commits

Author SHA1 Message Date
chahinebrini
c776570106 fix(demographics): align Frontend enum/prefix values with Backend zod schema
Backend-Agent identified 2 mismatches that caused 422 on save:

1. MARITAL_OPTIONS values:
   - 'partnership' → 'partnered' (Backend expects this)
   - 'none' → 'no_answer'

2. BUNDESLAND_OPTIONS values:
   - 'BW' → 'DE-BW' (alle 16 Bundesländer mit DE-prefix)
   - Backend zod-regex: ^DE-(BW|BY|...)$

3. germanCities.ts getCitiesForBundesland:
   - Akzeptiert jetzt sowohl 'BY' als auch 'DE-BY' (strip prefix on lookup)

User-visible labels unverändert. Nur internal values aligned mit Backend-API.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 20:47:43 +02:00
chahinebrini
2f5d0382f0 feat(profile,devices): real DB wiring + Devices-Settings migration
Profile (rebreak-native-ui):
- New hook hooks/useProfileData.ts (143 LOC, 4 hooks):
  useSocialStats, useApprovedDomains, useCooldownHistory, useSosInsights
- app/profile/index.tsx: alle DUMMY_* constants entfernt → live data via hooks
- PATCH /api/profile/me/demographics nun wired in onChange (war TODO-only)
- DELETE /api/profile/me/demographics für revoke-consent
- POST /api/profile/me/diga-banner-dismiss

Devices (rebreak-native-ui):
- New app/devices.tsx push-page: slot-counter, progress-bar, device-list mit
  trash-button (gesperrt für isCurrent)
- New lib/deviceId.ts: persistent device-ID via expo-application
  (getIosIdForVendorAsync / getAndroidId) mit AsyncStorage-UUID-fallback
- New stores/devices.ts: Zustand store (loadDevices, removeDevice, ensureRegistered)
- lib/api.ts: x-device-id + x-platform headers bei jedem Backend-Call
  (skipDeviceHeader option für Bootstrap-register)
- app/settings.tsx: Geräte-Row aktiv (push to /devices) statt soon-flagged
- locales: 14 neue settings.devices_* keys DE+EN

Backend-Status: alle Devices-Endpoints existieren (GET /api/devices, POST /register,
DELETE /:id). Pending: GET /api/profile/me/demographics für reload-state-fetch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 20:47:30 +02:00
chahinebrini
aa609de46f feat(ui): Settings + Demographics native UIMenu + clean Wheel backdrop
Settings:
- Theme/Sprache UIMenu: chevron-forward anchor (statt chevron-down — chevron-down
  reserviert für Collapsibles, siehe feedback_chevron_icon_convention memory)
- Theme menu: SF-Symbol images entfernt → Theme/Sprache haben gleiches Padding

Demographics (Profile):
- Geschlecht (3 options) → UIMenu (anchored Pull-Down) statt Wheel
- ≤3 → UIMenu, >3 → Wheel (Apple HIG-konform)

WheelPickerModal:
- Backdrop transparent (kein darkening overlay)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 20:37:50 +02:00
chahinebrini
c24ab64c9d chore(deps): expo-router 6 + register expo-font/expo-web-browser plugins
- expo-router 6 (per Phase 1 upgrade): all our imports remain valid
  (Stack, useLocalSearchParams, useRouter, useFocusEffect, withLayoutContext,
  RelativePathString). No `Href<T>` generic usage in codebase. expo-splash-screen
  already explicit in deps. Zero code changes needed for router-6 migration.
- Register expo-font + expo-web-browser as plugins per `expo install --fix`
  recommendation. Both already in deps but plugins block missing.

TS: 0 errors. Bundle still works.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 19:47:21 +02:00
chahinebrini
59a80627d8 chore(deps): Expo SDK 54 / RN 0.81 — Phase 1 core upgrade (JS-side)
Versions:
- expo: 53.0.0 → 54.0.34
- react-native: 0.79.6 → 0.81.5
- react: 19.0.0 → 19.1.0
- expo-router: 5.1.11 → 6.0.23 (major)
- react-native-reanimated: 4.0.0 → 4.1.7
- react-native-worklets: 0.4.0 → 0.5.1
- react-native-screens: 4.11.1 → 4.16.0
- react-native-gesture-handler: 2.24.0 → 2.28.0
- @expo/metro-runtime: 5.0.5 → 6.1.2
- @types/react: → 19.2.14
- expo-av: 15.1.7 → 16.0.8 (still deprecated, last shipping in SDK 54)

expo-file-system breaking change quick-fix:
- New SDK 54 API is class-based (File/Directory/Paths). Legacy API `cacheDirectory`
  + `EncodingType` moved to `expo-file-system/legacy` sub-export.
- 6 files updated to import from `expo-file-system/legacy` with TODO(sdk54)
  marker. Proper migration tracked as Task #14.

Smoke-test: 0 TS errors, Metro bundles 2185 modules in 5.9s.

Native binary still SDK 53 — Phase 5 prebuild --clean pending.
Branch: upgrade/sdk-54, rollback tag: pre-sdk54-upgrade

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 19:46:09 +02:00
chahinebrini
3c52d8869e feat(native): WIP checkpoint — Profile/Settings/Demographics + WheelPicker + Maestro
Rollback-Punkt vor Expo SDK 54 / RN 0.81 Upgrade.

UI/UX:
- Profile: ProfileHeader redesign (sign-in chip + member-since), StatsBar 3 pill cards,
  Demographics accordion completed (Geburtsjahr, Geschlecht, Familienstand, Beruf-split,
  Wohnort), Pro-Trial-Banner, Approved-Domains list, DigaMissionBanner
- Settings: section-based layout, neutral icons (matched Header dropdown style)
- Header dropdown: extended with logout + games-page link
- Notifications page: skeleton dummy data
- Locales: i18n keys for new screens

New components:
- WheelPickerModal: native iOS UIPickerView wheel for long lists (Geburtsjahr 91 items,
  Bundesland 16, Stadt 30+/Bundesland)
- OptionsBottomSheet: iOS-style options sheet (used briefly for Geschlecht, currently
  unused — kept for potential future use)
- germanCities.ts: Top-cities per Bundesland (DSGVO-clean static data)

New libs (NewArch-codegen verified):
- @react-native-menu/menu 2.0.0 (UIMenu wrapper, Apple HIG-konform)
- @lodev09/react-native-true-sheet 3.10.1 (UISheetPresentationController wrapper —
  ABER incompatible mit RN 0.79.6, Build-Error → Trigger für SDK-54-Upgrade)

Maestro E2E:
- Initial setup mit auth/community/profile/urge flows

Scripts:
- build-ios-clean.sh: Xcode DerivedData + ios/build cleanup vor expo run:ios

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 19:32:27 +02:00
chahinebrini
d7efd627f5 feat(profile): Demographics employment-split + Pro-Trial-Reward + tests
- New Prisma migration 20260508_demographics_employment_split:
  ADD COLUMNS employment_status / shift_work / industry / job_tenure
  (legacy `profession` kept untouched)
- PATCH /api/profile/me/demographics:
  Zod-enums updated to match Frontend values (employed/self_employed/in_training/
  unemployed/retired/homemaking/other; jobTenure: less_1y/1_3y/3_5y/5_10y/more_10y)
- profile.ts db-layer: tryAwardProTrial covers new + legacy fields,
  withdrawDemographics nulls all (incl. legacy profession)
- Vitest: 8-line trial happy-path + guard rails (free+pro+legend+used) +
  zod-validation tests covering new enum boundaries

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 19:31:28 +02:00
chahinebrini
57dfc51d97 fix(layout): restore Profile root flex:1, harden Settings + ProfileHeader rows
ROOT CAUSE Profile-Page leer: View+ScrollView verloren beide flex:1
(experimentelle Edit). Ohne flex:1 hat root null Höhe → ScrollView
kollabiert → nur ProfileHeader sichtbar, Rest abgeschnitten.

Profile/index.tsx:
- View style={{ flex: 1, backgroundColor: '#ffffff' }} restored
- ScrollView style={{ flex: 1 }} restored
- Debug-Title "Profil DEBUG-23s00" → "Profil"
- Magenta debug-marker entfernt

Settings row (def. flex-row hardening gegen icon-stack-Bug):
- width: '100%' auf Pressable
- flexShrink:0, flexGrow:0 auf icon-Box
- minWidth:0, flexShrink:1 auf text-Container (Pflicht für RN-Wrap)
- numberOfLines={1} auf label + sublabel (verhindert column-illusion bei
  langem text)
- paddingVertical: 12 fuer breathing room

ProfileHeader hint (gleicher Pattern fix):
- width: '100%' + flexShrink:0 auf beide icons
- minWidth:0, flexShrink:1, numberOfLines={2} auf hint-text
- paddingVertical 10→12

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 23:27:21 +02:00
chahinebrini
d940cb7f0f debug: magenta marker nach ProfileHeader — Render-Reach-Test
Wenn marker sichtbar nach Rebuild: ProfileHeader fully rendered,
StatsBar+below werden danach suppressed (background-overlay,
fix-height container, oder ähnlich). Wenn marker nicht sichtbar:
ProfileHeader-Render aborts mid-tree. TEMP, wird wieder entfernt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 23:16:15 +02:00
chahinebrini
fb97cda63d fix(ui): i18n device-locale + share-pill rounded-full + Profile debug-marker
stores/language.ts:
- init() override AsyncStorage-Wert nicht — wenn nichts gespeichert,
  i18n bleibt bei deviceLocale (von lib/i18n.ts via Localization.getLocales).
  Vorher: forced 'en' default obwohl App auf DE.

ComposeCard share-button:
- borderRadius:12 + height:50 → rounded-full px-5 h-11 (44pt)
- text-base → text-sm. Pill-Pattern wie Pre-Session.

app/profile/index.tsx:
- AppHeader title "Profil" → "Profil DEBUG-2300" — TEMPORARY marker
  zur Verifikation ob File geladen wird (user-suspect: routing zu altem
  File). Wird nach Test wieder entfernt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 23:10:20 +02:00
chahinebrini
922d5dc876 fix(stores): lazy-init theme/language — kein native API-Call beim Module-Load
Hermes wirft "property is not writable" + "Cannot read 'default' of undefined"
wenn beim Module-Load eines Stores native APIs aufgerufen werden bevor
die Bridge bereit ist. Die initialen Werte:

- theme: colorScheme war resolveColorScheme('system') → ruft Appearance.getColorScheme()
  zur Module-Load-Zeit. Jetzt: 'light' als default; korrekter Wert kommt
  asynchron via init().
- language: language war (i18n.language === 'de' ? 'de' : 'en') → liest
  i18n.language zur Module-Load-Zeit (Risiko falls i18n noch nicht init).
  Jetzt: 'en' default; korrekter Wert kommt via init() das i18n.changeLanguage
  callt.

Init-Calls passieren in _layout.tsx useEffect — nach Bridge-bereit, nach
Hermes-Eval-Phase. Sicher.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 22:03:21 +02:00
chahinebrini
6b4ea19521 fix(compose): Share-Button onPressIn fixes keyboard-tap, native iOS-Style
Diagnose: ComposeCard ist ListHeaderComponent der FlatList die
keyboardShouldPersistTaps="handled" hat. Aber das Prop propagiert nicht
rekursiv durch tiefere View-Ketten — der Share-Button-Pressable liegt
zu tief, der erste Tap dismissed nur die Tastatur.

Fix: onPress → onPressIn (fired beim Touch-Down vor Keyboard-Dismiss-
Logic). Identisch zum Pattern beim Bild-Icon. Guard if(!content.trim()
|| posting) repliziert die disabled-Logik die onPress hatte.

Style: native iOS Primary-Action-Button-Look:
- height 44→50, px-5→px-6
- rounded-full → borderRadius 12 (Rectangle statt Pill)
- Nunito_700Bold → 600SemiBold
- Plain text "Teilen", kein Icon

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 21:39:07 +02:00
chahinebrini
692abc07e1 fix(ui): Teilen-Button ohne Icon h:44, Dropdown-Width 170→210pt
ComposeCard:
- paper-plane-Icon entfernt, h:52→44, px-6→5, layout flex-row gap-2 →
  items-center justify-center, only Text. Saubere Pill ohne Icon-Gewicht.
- i18n-Key community.share war schon da — kein neuer Key.

HeaderDropdownMenu:
- minWidth 170→210, numberOfLines={1} auf Item-Labels entfernt (Breite
  reicht jetzt fuer alle Labels). numberOfLines bei SOS-Block-Labels
  bleibt (zwei separate Text-Nodes, sinnvoll).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 21:36:37 +02:00
chahinebrini
0d52cb1846 fix(ui): keyboard-first-tap, Teilen-Button bigger, Header-Icon hitSlop
(app)/index.tsx:
- FlatList keyboardShouldPersistTaps="handled" — Bild-Icon im ComposeCard
  reagiert ab erstem Tap auch wenn Tastatur offen. Vorher dismisste der
  Tap nur die Tastatur (RN-Default "never").

ComposeCard.tsx Teilen-Button:
- height 44→52, px-5→px-6, paper-plane-outline-Icon size 18 + text-base
  Nunito_700Bold. Standard-iOS-Filled-Primary-Button-Style.

AppHeader.tsx Bell + Avatar:
- hitSlop 4pt allseitig auf beiden Pressables — effective tap-area
  36→44pt ohne Layout-Verschiebung
- Bell-Icon size 18→22 (konsistent mit Avatar-36pt-Kreis)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 21:23:46 +02:00
chahinebrini
192f67cd07 feat(coach): tier-based LLM-Routing + COACH_CASUAL_SYSTEM_PROMPT
Coach-Page ist NICHT SOS — User ist nicht in Krise, will small-talk,
Reflexion, Feature-Wuensche, Philosophie. „Lockere Lyra".

Aenderungen:
- Neuer Prompt COACH_CASUAL_SYSTEM_PROMPT (exportiert): warm/neugierig/
  manchmal humorvoll, bis 4-5 Saetze, darf eigene Empfehlungen + Mini-
  Meinungen formulieren, lädt Feedback aktiv ein. Kein Crisis-Framing.
  Sprachregeln (keine Pathologisierung, kein „Sucht") gelten unveraendert.
- Tier-LLM-Routing analog zu sos-stream:
  Free/Pro = Groq llama-3.3-70b-versatile (Fallback llama-3.1-8b)
  Legend  = OpenRouter anthropic/claude-haiku-4.5 (Fallback claude-3.5-haiku)
- max_tokens 280→500 (Coach darf laenger antworten)
- Demographics-Injection (analog sos-stream): birthYear/gender/etc als
  USER-DEMOGRAPHIE-Block in Prompt (read-only, kein Extract)
- sosMode-Branch deprecated — Frontend kann den Param noch senden, wird
  ignoriert. Folge-TODO: UI-Agent entfernt sosMode aus Coach-Call.

NICHT geändert:
- TTS-Endpoints bleiben plan-agnostisch (Frontend routet by tier)
- sos-stream.get.ts/sos-stream.post.ts unberuehrt (importieren weiter
  COACH_SYSTEM_PROMPT, kein Breaking Change)

Memory:
- project_llm_per_plan.md (tier-LLM-Default-Logic)
- feedback_anonymity_nickname.md
- feedback_demographics_user_initiated.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 21:22:51 +02:00
chahinebrini
9ccd0bd334 feat(settings): Theme + Language + Lyra-Voice Picker; Dropdown-width 280→170pt
Settings-Page (alles auf einen Rutsch):
- Theme-Picker (System/Hell/Dunkel) — neuer useThemeStore (Zustand) mit
  AsyncStorage @rebreak/theme persist; init-call in app/_layout.tsx
  parallel zu auth-init
- Language-Picker (DE/EN) — neuer useLanguageStore mit i18n.changeLanguage
  + AsyncStorage @rebreak/language persist; init beim App-Start
- Lyra-Voice-Picker (Legend-only) — UI live, lokal selectable. BACKEND-GAP:
  PATCH /api/profile/me/lyra-voice fehlt (demographics.patch akzeptiert
  Voice-ID nicht via zod). UI bleibt funktional, Persist erst wenn
  Endpoint da ist.
- Logout neutral (kein style:'destructive' am Alert-Button)
- Coming-Soon-Banner entfernt (war veraltet, Settings ist live)

Dropdown:
- HeaderDropdownMenu minWidth 280→170, Item-Labels numberOfLines={1}
  ellipsize bei langen Strings

Locales:
- 11 neue Keys fuer Theme/Lang/Voice-Picker (DE+EN gepflegt)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 21:22:32 +02:00
chahinebrini
cddc4d0f26 feat(profile): DiGA-Demographics + Pro-Trial-Reward + 7 Profile-Endpoints
Schema:
- 8 neue Profile-Felder fuer DiGA-Demographics (birthYear/gender/maritalStatus/
  profession/bundesland/city + 2 consent-stamps demographicsConsentAt/
  demographicsWithdrawnAt)
- 4 Pro-Trial-Felder (proTrialStartedAt/ExpiresAt/Source/UsedAt) — Free-User
  bekommen 1 Woche Pro als Reward fuer DiGA-Daten-Pflege (siehe
  project_demographic_pro_trial_reward.md)
- lyra_voice_id (Legend-only Voice-Picker)
- diga_banner_dismissed_at (server-side persistence ueber Re-Install)
- last_install_at (Streak-Logic survives Re-Install)
- Migration 20260507_profile_demographics_and_trial: alle Felder optional,
  keine Backfill-Logik notwendig

Endpoints (alle auth-protected, scope=me):
- GET /api/profile/me/sos-insights
- GET /api/profile/me/cooldown-history
- GET /api/profile/me/approved-domains
- POST /api/profile/me/install-event (track app re-installs)
- POST /api/profile/me/diga-banner-dismiss
- PATCH /api/profile/me/demographics (consent-stamp + re-grant-after-withdrawal in tx)
- DELETE /api/profile/me/demographics (DSGVO right-to-be-forgotten)

Plugin:
- pro-trial-expiry-cron: 6h-Interval, conservative-fallback (revoke nur wenn
  kein stripeSubId), 60s initial-delay damit Server-boot nicht blockiert wird

Tests:
- vitest config + erste Test-Files (test-infrastructure setup)

Memory:
- feedback_demographics_user_initiated.md (Lyra darf NIE extrahieren)
- project_demographic_pro_trial_reward.md (Pro-Trial-Reward-Mechanik)
- project_profile_page_design.md (UI-Showpiece, eigene/fremde-Ansicht streng getrennt)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 21:14:06 +02:00
chahinebrini
2f3b19f71b fix(profile/faq): demographics-hint auto-expand + FAQ-chevron-direction
Profile:
- Hint "fuelle deine anonymen Daten aus" oeffnet das DemographicsAccordion
  jetzt automatisch via expanded-Prop + useEffect mit LayoutAnimation.
  Vorher: scrollte hin, liess es geschlossen, User musste nochmal tappen.
- DemographicsAccordion: expanded-Prop fuer external-trigger; interner
  expandedLocal-State, Toggle-Button bleibt unabhaengig functional.

ProtectionDetailsSheet FAQ:
- chevron-forward (0deg→90deg Rotation, sah aus wie Nav-Link) → chevron-down
  (0deg→180deg). Geschlossen=runter, offen=hoch. State-Toggling war schon
  korrekt, nur visuelle Affordance war falsch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 21:11:41 +02:00
chahinebrini
756947549f fix(settings): tap-targets, logout-row, AppHeader-Pattern + HIG-konsistenz
Pre-fix war die Settings-Page de-facto kaputt: Rows waren <View> ohne
Press-Handler — 0pt Tap-Target, nichts klickbar. Plus Abmelden war nur
ueber Header-Dropdown erreichbar (nicht erwartete UX).

Fixes:
- AppHeader showBack statt manueller SafeAreaView+Chevron — konsistent mit
  Profile-Page
- Pressable minHeight:56 auf jeder Row (mit pressed-state opacity+bg);
  disabled={row.soon} fuer geplante Features
- Icons 16pt/32pt-Box → 18pt/36pt-Box (App-Norm)
- Row-Label fontSize 14→15 (konsistent mit Profile/Dropdown)
- Danger-Zone-Section hinzugefuegt: Abmelden live (Alert-Confirmation),
  Konto-loeschen 'soon'
- Chevron-forward fuer tappable Rows (statt 'Soon'-Badge falls live)
- paddingBottom: insets.bottom + 40 (safe-area-aware fuer Home-Indicator)

Sektionen unveraendert (Profil, Theme/Sprache, Notifications, Geraete/Abo,
Lyra, Account, Debug) — nur Visual + Tap-Layer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 21:06:16 +02:00
chahinebrini
82c6c61a5e fix(sos-stream): profile is not defined ReferenceError
let profile vor try-block hoisten, damit es im plan-routing-Fallback
(line 210, profile?.plan) sichtbar ist. Vorher: const profile innerhalb
try-block → block-scoped → ReferenceError außerhalb.

Demographics-Block-Injection added — gracefully no-op wenn neue Felder
(birthYear/gender/etc.) noch nicht im DB-Schema sind (optional chaining).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 21:03:27 +02:00
chahinebrini
e53911e844 fix(social/compose): Bild-Upload + Teilen-Button auf Apple-HIG 44pt Tap-Target
Vorher: 30/32pt Touch-Areas mit hitSlop-Kruecken (asymmetrisch, schwer zu treffen).
Nachher: explizite height: 44 im style-Callback, Icons 22pt, Padding angepasst.

Konsistent mit PostCard-Like/Comment-Pattern. Funktionalitaet (pickImage,
submit) unveraendert.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:45:00 +02:00
chahinebrini
e9a9bd8880 feat(ui): SOS-Page LLM/TTS-Toggles raus + DemographicsAccordion FieldRow flex fix
- urge.tsx: TtsProviderToggle + LlmProviderToggle entfernt (Testing durch);
  Core-Logic (useTtsProvider, currentLlmProvider, BenchSession) bleibt für
  spätere Debug-Page intakt
- DemographicsAccordion FieldRow: flex:1 auf Label-Text, kein flexShrink-
  Wrapper mehr nötig; Label+Input wrappen nicht mehr auf schmalen Devices

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:36:10 +02:00
chahinebrini
def21a220d refactor(deploy-staging): HETZNER_HOST/USER von secrets zu vars
Hostname (staging.rebreak.org) und User (root) sind keine echten Secrets —
public DNS bzw. in Doku schon erwähnt. Variables sichtbar in Logs erleichtert
Debug (siehe DNS-Fail mit "getaddrinfo ***" — bei vars wäre der konkrete
Wert lesbar gewesen).

Aufteilung jetzt:
- HETZNER_SSH_KEY → secret (sensitive: Server-Root-Zugang)
- HETZNER_HOST    → var (public DNS)
- HETZNER_USER    → var (in Doku)

Plus echo des Host-Werts in Setup-SSH-Step für Debug-Visibility.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:32:34 +02:00
chahinebrini
072efa06e8 fix(deploy-staging): pnpm-version Konflikt mit packageManager-Field
pnpm/action-setup@v4 errored out weil sowohl `version: 10` im Workflow
als auch `packageManager: pnpm@10.23.0` in package.json gesetzt waren.
package.json bleibt source-of-truth, version aus Workflow raus.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:20:00 +02:00
chahinebrini
87438ede8e feat(deploy): GitHub-Actions Build+Deploy-Pipeline für rebreak-staging
CX23 (4GB RAM) OOM'd am 2026-05-06 während webhook-getriggertem `pnpm build`
(Heap-Limit 1.5GB überschritten). Build raus aus Server, GitHub-Runner (7GB RAM)
übernimmt — Server deployed nur noch Artifact via scp + atomic-mv + pm2 restart.

- .github/workflows/deploy-staging.yml: 2-Job (build + deploy via SSH-Artifact-Push)
- scripts/deploy-from-artifact.sh: Server-Script mit Migration-Detection + atomic-mv
- ops/GITHUB_ACTIONS_PIPELINE.md: Architektur-Doku + Cheatsheet

Coexistence: alter rebreak-webhook bleibt als Failsafe, wird nach 5+ erfolgreichen
GA-Runs deaktiviert. Erster Run: Webhook temporär gestoppt für sauberen Test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:18:49 +02:00
chahinebrini
b38bf176b9 feat(deploy): Migration-Detection in webhook-deploy.sh
Step 2.5 zwischen pnpm install und build:
- git diff vs .last-deployed-sha auf prisma/migrations + schema.prisma
- bei Änderungen: pnpm prisma migrate deploy via infisical run wrapper
  (idempotent, conservative — abort bei Fail vor pm2-restart)
- DATABASE_URL Fallback auf NUXT_DATABASE_URL analog start-staging.sh

Step 8: persistiert HEAD-SHA in .last-deployed-sha
.gitignore: .last-deployed-sha (server-only artifact)

Edge cases:
- First-deploy: .last-deployed-sha fehlt → run_migration=true (sicher dank
  Idempotenz)
- Infisical-Login-Fail: abort vor pm2-restart
- Migration-Fail: exit 1, alter Code läuft auf alter DB sauber

Old-trucko-Pattern (deploy.sh:58-65) macht migrate deploy unconditional;
rebreak-Variante macht es conditional → spart Infisical+DB-Roundtrip wenn
keine Migration-Files berührt wurden.
2026-05-07 19:09:12 +02:00
chahinebrini
e76be7ee78 feat(profile): Profile-Page komplett + Header-Dropdown + UI-Pattern-Fixes
Profile (3 Iterationen):
- app/profile/index.tsx + components/profile/* (Header, StatsBar, Approved,
  Streak, UrgeStats, Demographics, DigaMissionBanner)
- echte Live-Daten via useMe-Hook (Avatar/Nickname/Plan/Email/Provider-Pill)
- Demographics mit echten Inputs (TextInput + Bottom-Sheet-Selects),
  debounced auto-save, Pro-Trial-Reward-Banner, Mikro-Why-Texte
- Approved Domains als plain integer (KEIN Plan-Slot/Cap)
- Friendly Hint-Text statt Progress-Bar (alignSelf:'stretch' Pattern)
- StatsBar zentriert mit 3 prominenten Cards (vertikale Dividers)
- Cooldown-Timeline als Liste mit 1px-Rail
- ApprovedDomainsList: Collapse-Chevron rechts in Title-Row (Pattern-Fix)
- Eigene vs fremde Profile-Ansicht streng getrennt (DSGVO/Anonymität)

Header-Dropdown (kein 3-Punkte-Icon):
- Avatar als Trigger im AppHeader (User-Wunsch)
- Custom-Modal beide Plattformen, Card-Style
- SOS prominent oben (nur Wort 'SOS' rot, Tagline 'wir sind für dich da' klein darunter)
- Profile/Settings/Games/Debug(__DEV__)/Logout
- Logout neutral (nicht rot — Recovery-tonal)
- AppHeader: neue showBack + title Props für Sub-Routes

Routes (Stub bis Phase C):
- app/profile/[userId].tsx — anonym (nur public-Stats)
- app/settings.tsx — Coming-Soon-Skeleton
- app/games.tsx — Standalone Games-Page mit GameCard-Grid
- app/debug.tsx — __DEV__-only

Game-Picker (Migration aus Nuxt):
- components/games/{GameCard, StarRating, GameRatingStars}
- 2x2 Grid, 56pt SVG-Icons (inline aus components/urge/gameSvgs.ts)
- Live-Backend /api/games/ratings (silent-fail)
- Re-use UrgeGames.tsx ohne TTS/Cooldown-Loop

UI-Pattern-Fixes (alle aus screenshot-User-Feedback 2026-05-07):
- Snake-Bug (food-pellet React-18-StrictMode-Reducer-double-call) gefixt
- Snake-Buttons platform-native (iOS-blue / Android-ripple)
- Tetris-Margins (16px paddingHorizontal)
- PostCard-Buttons Apple-44pt-Hit-Area (Image-Select, Image-Remove,
  Cancel, Share-Pill — via hitSlop)
- ProfileHeader Demographics-Hint: alignSelf:'stretch' Pattern
- ApprovedDomainsList Collapse: Title flex:1 + Chevron rechts
- ProtectionDetailsSheet FAQ-Items: alignSelf:'stretch' defensive
- AppHeader Back-Button: neue showBack-Prop + chevron-back

Memory + Plan-Docs:
- 17 Memory-Files dokumentieren System-Wissen + Patterns
- ops/{CUTOVER, UI_MIGRATION, PROFILE_PAGE, WEBHOOK, GAMES_1V1,
  RELEASE_READINESS, TESTING_STATE, MAESTRO_HOSTING}_*.md

Backend bleibt unverändert (Tier-LLM + Nickname + sort:latency
sind seit gestern deployed).
2026-05-07 18:22:58 +02:00
chahinebrini
355166c194 feat(sos): tier-based LLM-Default — Free/Pro=Groq, Legend=Haiku
Backend (sos-stream.get.ts): wenn sessionData.llmProvider === 'auto'
oder undefined, resolved zu plan-based default via profile.plan:
- legend → openrouter-haiku (Anthropic warm + sort:latency)
- pro/free → groq-llama (sachlich + schnell, ~157ms TTFB)

Frontend (llmProvider.ts): DEFAULT_PROVIDER = 'auto', neue Pill 'Auto'
in LlmProviderToggle. Explicit-Toggles (Sonnet/Haiku/Groq) sind
debug-overrides die plan-logic bypassen.
2026-05-07 04:15:31 +02:00
chahinebrini
4e5f8e6c93 fix(sos-stream): nickname-Injektion in systemPrompt
Lyra halluzinierte Namen wie 'Max' im SOS-Stream weil systemPrompt
keinen Nicknamen-Anker hatte. message.post.ts hatte das Pattern schon,
aber beim Cutover ins backend/-Layout war's nur dort, nicht in
sos-stream.{get,post}.ts.

Fix: getProfile(user.id) + Inject 'NUTZER-NAME: ...' wie in message.post.ts.
Non-blocking (catch), Memory-Pattern preserved.
2026-05-07 04:10:27 +02:00
chahinebrini
431ae6a75d fix(sos-stream): undefined 'key' var nach cutover-refactor
Im Cutover wurde 'const key = config.openrouterApiKey' rausgenommen,
aber line 311 referenzierte 'key' weiter für extractAndStoreMemories.
ReferenceError fiel in catch → 'stream failed' SSE event → App
fiel auf non-streaming fallback (4-5s wait) statt streaming.

Fix: explizit memoryExtractKey aus config.openrouterApiKey holen.
2026-05-07 03:41:11 +02:00
chahinebrini
cc0fd8f7fa fix(backend/start-staging): Infisical→Nitro env-var mapping
- SUPABASE_KEY/SERVICE_KEY → SUPABASE_ANON_KEY/SERVICE_ROLE_KEY aliasing
- NUXT_*_API_KEY → fallback to non-prefixed
- NITRO_-Prefix mapping für runtimeConfig-Runtime-Override
  (Nitro standalone bracht NITRO_X-prefix zur runtime override des
  build-time defaults, da Build außerhalb infisical run wrapper läuft)

Verified live auf rebreak-staging: HTTP /api/auth/me mit fake bearer
gibt jetzt 401 statt 500.
2026-05-07 03:37:16 +02:00
chahinebrini
d308ea2875 fix(backend/config): Infisical env-var Namen alignen
Server's Infisical staging liefert die Secrets unter:
- SUPABASE_KEY (nicht SUPABASE_ANON_KEY)
- SUPABASE_SERVICE_KEY (nicht SUPABASE_SERVICE_ROLE_KEY)
- NUXT_OPENROUTER_API_KEY (nur prefixed)
- NUXT_GROQ_API_KEY (nur prefixed)
- NUXT_DATABASE_URL (auch prefixed)

Cutover-Fix: nitro.config.ts liest beide Varianten (mit Fallback-Chain).
Auth-middleware crashed sonst mit 'supabaseKey is required'.
2026-05-07 03:28:14 +02:00
chahinebrini
d1b71e76b2 chore(cutover): prepare backend/-Layout for Hetzner-Pipeline-Cutover
Phase-1-Vorbereitung für den Rebreak-Cutover (apps/rebreak Nuxt → backend
standalone Nitro). Alle Änderungen sind lokal verifiziert (build = 9.66 MB
gzipped 3.08 MB, node .output/server/index.mjs startet ohne ERR_MODULE_NOT_FOUND
auf :3000). Kein Push, kein Server-Eingriff in dieser Session.

Inhalt:

- backend/nitro.config.ts: 8 zusätzliche runtimeConfig-Keys (cartesia*, eleven*,
  supabaseUrl/AnonKey/ServiceKey, public.supabase.{url,key}). Schließt den
  Auth-500-Cascade vom 2026-05-06 (server/utils/auth.ts:32 liest
  config.public.supabase ?? config.supabase — beide Pfade jetzt deklariert).

- .npmrc (NEU, root-level): node-linker=hoisted für Prisma 7 transitive
  @prisma/client-runtime-utils (siehe feedback_backend_runtime_config.md).

- backend/start-staging.sh: Pfad korrigiert von /srv/rebreak-monorepo/...
  → /srv/rebreak/backend/.output-staging/server/index.mjs. infisical run
  wrapper (kein NUXT_*-Mapping mehr — runtimeConfig liest process.env.X
  direkt). IMAP-Services entfernt (sind Mo's Scope, separat in ecosystem).

- scripts/deploy.sh (NEU): adaptiert von /srv/rebreak/scripts/deploy.sh
  für backend/-Layout. APP_DIR=backend, pnpm --filter rebreak-backend build,
  .output → .output-staging atomic-move bleibt erhalten, pm2 restart
  --update-env zieht neue Infisical-Secrets.

- scripts/deploy-webhook/server.mjs (NEU): 1:1-Kopie vom Server, damit
  ecosystem.config.js auf die Repo-Version zeigen kann.

- ecosystem.config.js (NEU, root-level): rebreak-staging zeigt auf
  backend/start-staging.sh, rebreak-webhook zeigt auf scripts/deploy-webhook.
  rebreak-prod + dns-* sind kommentiert (folgen in späterer Phase).

- ops/CUTOVER_PLAN.md: Plan-Doku vom 2026-05-06 (yesterday's work).

- .gitignore: .claude/ und xgit ergänzt (lokale Agent-State, nicht versioniert).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 00:35:50 +02:00
chahinebrini
5dfbe886a4 fix(ops/nginx): sync configs aus /etc/nginx/sites-available/ vom rebreak-server
Mein vorheriger Push hatte ältere ops/nginx/-Versionen aus dem rebreak-monorepo.
Server hatte ACME-Challenge, security-Headers (HSTS, X-Frame-Options, X-XSS),
separate /webhook Location, X-Robots-Tag — alles war im Repo nicht drin.

Geänderte/neue files:
- staging.rebreak.org.conf — full version mit ACME + security
- db-staging.rebreak.org.conf — minimal diff
- default.conf — NEU
- rebreak-staging-new.conf — NEU

Live nginx selbst war nicht betroffen (nginx -t ok), Repo-Sources jetzt
auf Live-Stand synchronisiert.
2026-05-06 14:12:31 +02:00
chahinebrini
f2e822be95 feat(sos): llmProvider toggle + sort:latency + bench scaffolding
- backend/coach: routing zu Sonnet (default) / Haiku / Groq Llama je nach
  sessionData.llmProvider. sort:latency für Anthropic-Modelle (-30..58% TTFB).
- frontend: LlmProviderToggle (Sonnet/Haiku/Groq pills), llmProvider.ts
  Storage-Helper. sosStream.ts schickt llmProvider im /sos-session-Body.
- bench: SosTtsBenchmark sammelt Marker (req->session, lyra-ttfb, lyra-done,
  tts-fired/headers/body/file, audio-loaded, first-audio); Output als console.table.
- ops: backend/scripts/llm-bench.sh + Python-Variante für realistic SOS-Prompt.
- speak-cartesia + speak-elevenlabs Endpoints (waren ungetracked, jetzt mit drin).
2026-05-06 13:58:07 +02:00
RaynisDev
b58588cf3c initial commit: rebreak-monorepo (RN app + standalone Nitro backend) 2026-05-06 07:13:43 +02:00