rebreak-monorepo/backend/server/lib/lyraPostCatalog.ts
chahinebrini 2e409efaf0 feat(onboarding/android + backend/lyra-i18n): platform-dispatch + post-catalog scaffold
Android-Onboarding (Platform.OS dispatch in ProtectionSlide):
- Neue Phasen für Android: preexplain_vpn → preexplain_a11y → a11y_pending
- AppState-Listener: nach Settings-Rückkehr auto-poll isAccessibilityEnabled
  → wenn live, armTamperLock + finish (kein Fokus-Klick nötig)
- onboardingAssets: 8 neue Mappings (android_vpn + android_a11y × 4 Locales)
- Screenshots: vpn-permission + a11y-rebreak-row pro Locale
- Locale-Keys: protection_url_android, protection_lock_android, cta_open_a11y,
  cta_check_a11y, dialog_button_vpn_ok, dialog_button_a11y_toggle, tap_marker_hint_*

Lyra-Post i18n Phase 1 (Scaffold, feature-flag OFF by default):
- schema.prisma: CommunityPost.i18nKey String? (nullable)
- migration 20260517_add_lyra_post_i18n_key: ALTER TABLE ADD COLUMN i18n_key
  (NICHT auto-deployed — `prisma migrate deploy` als separater Step)
- server/lib/lyraPostCatalog.ts: 15 Templates skelettiert + pickRandomTemplate
- cron/lyra-post: USE_TEMPLATE_CATALOG=true Branch → speichert i18nKey;
  default false → LLM-Path unverändert (zero-risk-deployment)
- community.createPost: optionaler i18nKey-Parameter
- posts.get: i18nKey in API-Response
- PostCard: 3-Zeilen-Branch — i18nKey ? t('lyra_posts.'+id) : content
- stores/community: i18nKey?: string|null im Interface
- de.json: lyra_posts-Block mit 15 IDs + DE-Texten

Single-Banner-Verhalten auf Android verifiziert:
lockedIn=urlFilter && appDeletionLock funktioniert weiter — auf Android
alias appDeletionLock ← tamperLock; onboarding arms tamperLock, also
nach onboarding-done direkt ProtectionLockedCard sichtbar.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 23:48:25 +02:00

61 lines
3.1 KiB
TypeScript

/**
* Lyra Community Post Template Catalog
*
* Each entry has a stable ID that maps to a locale key in:
* apps/rebreak-native/locales/{de,en,fr,ar}.json → lyra_posts.<id>
*
* The `topic` field mirrors the existing TOPICS enum in lyra-post.ts and is
* used for throttle-logic and potential future category filtering.
*
* Texts live in locale files — this file is locale-agnostic.
* EN/FR/AR texts are curated by lyra-persona agent (pending).
*
* Target: ~60 templates (15 per topic). Currently: 15 scaffolded.
*/
export type LyraPostTopic = 'motivation' | 'tipp' | 'zitat' | 'witzig' | 'news' | 'feature';
export interface LyraPostTemplate {
id: string;
topic: LyraPostTopic;
}
export const LYRA_POST_CATALOG: LyraPostTemplate[] = [
// ── motivation (5) ──────────────────────────────────────────────────────
{ id: 'motivation_quiet_01', topic: 'motivation' },
{ id: 'motivation_quiet_02', topic: 'motivation' },
{ id: 'motivation_quiet_03', topic: 'motivation' },
{ id: 'motivation_distance_01', topic: 'motivation' },
{ id: 'motivation_distance_02', topic: 'motivation' },
// ── tipp (4) ─────────────────────────────────────────────────────────────
{ id: 'tipp_breath_01', topic: 'tipp' },
{ id: 'tipp_urge_surf_01', topic: 'tipp' },
{ id: 'tipp_habit_replace_01', topic: 'tipp' },
{ id: 'tipp_sos_reminder_01', topic: 'tipp' },
// ── zitat (2) ────────────────────────────────────────────────────────────
{ id: 'zitat_stoic_01', topic: 'zitat' },
{ id: 'zitat_psychology_01', topic: 'zitat' },
// ── witzig (2) ───────────────────────────────────────────────────────────
{ id: 'witzig_impulse_01', topic: 'witzig' },
{ id: 'witzig_distraction_01', topic: 'witzig' },
// ── news (1) ─────────────────────────────────────────────────────────────
{ id: 'news_push_tactics_01', topic: 'news' },
// ── feature (1) ──────────────────────────────────────────────────────────
{ id: 'feature_sos_01', topic: 'feature' },
];
/**
* Returns a random template from the catalog.
* Optional: pass `excludeIds` (e.g. recently used) to avoid repeats.
*/
export function pickRandomTemplate(excludeIds: string[] = []): LyraPostTemplate {
const pool = LYRA_POST_CATALOG.filter((t) => !excludeIds.includes(t.id));
const source = pool.length > 0 ? pool : LYRA_POST_CATALOG;
return source[Math.floor(Math.random() * source.length)];
}