chahinebrini
3c5c9ebfba
feat(onboarding): polish bundle — nickname validation, diga format, confetti, FAQ accordion, lyra-voice tuned
...
## Nickname-Validation + Duplicate-Check
Bug-Prevention: User konnte einen bereits vergebenen Nickname setzen, was
zu Verwirrung führte (zwei User mit selbem Alias). + Profanity-Filter.
Backend:
- GET /api/profile/check-nickname?nickname=X — returns {available, reason?}
reasons: 'too_short' | 'too_long' | 'profanity' | 'taken'
- Min 3, max 32 chars
- Profanity-Set (hardcoded, ~20 Wörter DE/EN — slurs + bot-impersonation
wie "admin", "lyra", etc.)
- Case-insensitive lookup, ignoriert eigenen Nickname (= behalten ok)
- Soft-deleted Profile sind ausgeschlossen
Frontend:
- NicknameSlide refactored mit Live-Debounce (450ms)
- Race-guard via checkSeqRef damit veraltete Antworten verworfen werden
- Visueller Feedback: Border-Color (success/error/transparent), Status-
Icon im Input (hourglass/checkmark/X), inline Error-Text statt Alert
- Save-Button disabled wenn invalid
- Network-Error: fail-soft, lass Server-Side bei Save validieren
## DiGA-Code Auto-Format
Live-Format-Mask: User tippt "REBREAKTEST001" → wird zu "REBREAK-TEST-001"
beim Tippen. Strip-then-segment Logik:
1. Alles außer A-Z0-9 entfernen
2. Erste 7 chars = "REBREAK", Rest in 4+restliche Blöcke
Liberal — erlaubt User dashes händisch zu setzen (wird neu segmentiert).
## DoneSlide Confetti + FAQ
- Confetti-Overlay mit 22 Partikeln, gestaffelt 40ms, native-driver Animation
(translateY + drift + rotate + opacity fade). One-shot beim Mount.
- Inline Top-5-FAQ Accordion unter dem Checkmark-Hero. Tap auf row → expand
+ zeige Antwort. Nutzt existing help.faq_q1..q5 + .faq_a1..a5 locale keys.
## Lyra Voice-Review (Agent)
lyra-persona Agent hat alle Lyra-Speech-Texte in 4 Sprachen reviewed:
- Welcome entstigmatisiert (kein "Glücksspiel"-Trigger im First-Touch)
- Plan vermenschlicht (Erklärungs- statt Verkaufs-Ton)
- DiGA-Choice sanfter (Geschenk-Frame statt Zugangs-Frame)
- protection_lock parallelisiert mit "blaue Falle"-Warnung
- FR/AR Stilglättung (Lyra-Femininum konsistent, AR Frage-Forms)
## Locale-Additions
- onboarding.nickname.error_{too_short, too_long, profanity, taken} × 4 langs
- onboarding.done.faq_section_title × 4 langs
- Lyra-bodies × 4 langs (vom Agent getuned)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 20:09:53 +02:00
chahinebrini
38a8517259
feat(onboarding): interactive welcome + nickname spotlight tour
...
Stage 1+2 des post-signup Onboarding-Flows:
- Welcome-Screen: dark-slate Full-Screen mit Pulse-Hero, 3 Mission-Bullets,
DSGVO-Box, CTA "Los geht's"
- Nickname-Spotlight via react-native-copilot ums TextInput in /profile/edit,
auto-start wenn step='nickname', nach Save → step='block' + back to /(app)
- Backend: Profile.onboardingStep enum (welcome/nickname/block/done),
Migration mit Backfill (existing → done), PATCH /api/profile/me/onboarding-step,
/api/auth/me erweitert
- Frontend: CopilotProvider in root, Routing-Gate in (app)/_layout, useMe um
onboardingStep ergänzt
- i18n (de/en/fr) für onboarding.welcome.* + onboarding.nickname_spotlight.*
Stage 3 (Block-Aktivierung-Spotlight) folgt in nächster Session — der bestehende
ProtectionOnboardingSheet auf Android wird daran angebunden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 21:00:20 +02:00
chahinebrini
76f8595a4f
feat(backend): lyra voice picker for legend (mvp)
...
- profile.ts: Whitelist (null | iFSsEDGbm0FiEd2IVH4w | Gt7OshJCH7MuzX96wFHi) + setLyraVoiceId()
- profile/me/lyra-voice.patch.ts: neuer Endpoint, Legend-Gate (403 legend_only),
Validation gegen Whitelist (400 invalid_voice_id). DB-Wert bleibt bei Plan-Downgrade.
- coach/speak.post.ts: ElevenLabs-Voice-Prioritätskette nimmt userLyraVoiceId
zuerst (nur wenn plan === legend), sonst voiceCfg / config / env / FALLBACK.
- auth/me.get.ts: lyraVoiceId in der Profile-Response damit Frontend hydriert.
Schema-Feld lyraVoiceId existiert bereits aus migration
20260507_profile_demographics_and_trial — keine neue Migration nötig.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 22:15:42 +02:00
chahinebrini
0e94ddb68a
feat(api): GET /api/profile/me/demographics endpoint
...
Read-counterpart zum existierenden PATCH/DELETE. Frontend braucht den endpoint
um nach Page-Reload die schon-gespeicherten Werte zu fetchen — sonst sieht User
leere Felder und denkt save funktioniert nicht.
- backend/server/db/profile.ts: getDemographics(userId) — SELECT der 9 fields +
demographics_consent_at + demographics_withdrawn_at
- backend/server/api/profile/me/demographics.get.ts: requireUser + getDemographics
+ ISO-string conversion. 404 wenn Profile-row fehlt.
- backend/tests/profile/demographics.get.test.ts: 5 vitest cases
(null fields, 404, populated, withdrawn, 401)
Response shape kompatibel mit PATCH-input (gleiche field names, camelCase) plus
metadata consentAt/withdrawnAt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 21:40:42 +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
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