/** * Neurodivergent-friendly notification messaging system. * * Encouraging tone, adaptive frequency, forbidden phrases. * Pure client-side TS — no backend dependency. */ import type { GentleMessage, GentleNotificationConfig, GentleNotificationEngine } from './types.js'; export const FORBIDDEN_PHRASES: readonly string[] = [ "You haven't", 'You forgot', "Don't forget", 'You should have', "Why didn't you", 'You missed', 'You failed', 'You need to', ] as const; const DEFAULT_MESSAGES: Record = { reminder: [ { title: 'Gentle Reminder', body: 'Whenever you are ready, there is something waiting for you.', tone: 'encouraging', }, { title: 'Quick Note', body: 'No rush — just a friendly nudge when the time feels right.', tone: 'encouraging', }, { title: 'Hey there', body: 'Take your time. We will be here when you are ready.', tone: 'neutral', }, ], progress: [ { title: 'Nice Progress', body: 'Look at what you have accomplished — every step matters!', tone: 'encouraging', }, { title: 'Moving Forward', body: 'You are making progress at your own pace. That is perfect.', tone: 'encouraging', }, ], check_in: [ { title: 'Check In', body: 'How are you feeling? Remember, there is no wrong answer.', tone: 'encouraging', }, { title: 'Quick Check', body: 'Just checking in — hope you are doing well!', tone: 'neutral' }, ], streak: [ { title: 'Streak Update', body: 'Your consistency is impressive — keep it going if it feels right!', tone: 'encouraging', }, ], idle: [ { title: 'Welcome Back', body: 'Great to see you again — no judgment, just glad you are here!', tone: 'encouraging', }, { title: 'Hi Again', body: 'Whenever you are ready to jump back in, we are here.', tone: 'neutral', }, ], }; export function createGentleNotificationEngine( initialConfig?: Partial ): GentleNotificationEngine { const messagePools: Record = { ...DEFAULT_MESSAGES }; function getDefaultConfig(): GentleNotificationConfig { return { maxPerHour: 3, tone: 'encouraging', adaptiveFrequency: true, dismissCount: 0, suppressThreshold: 5, ...initialConfig, }; } function getMessage(type: string, config?: GentleNotificationConfig): GentleMessage { const tone = config?.tone ?? 'encouraging'; const pool = messagePools[type]; if (!pool || pool.length === 0) { return { title: 'Hey', body: 'Hope you are having a good day!', tone, }; } // Filter by tone if possible, fallback to any const toneFiltered = pool.filter(m => m.tone === tone); const candidates = toneFiltered.length > 0 ? toneFiltered : pool; const index = Math.floor(Math.random() * candidates.length); return candidates[index]; } function shouldSuppress(config: GentleNotificationConfig): boolean { if (config.adaptiveFrequency && config.dismissCount >= config.suppressThreshold) { return true; } return false; } function recordDismissal(config: GentleNotificationConfig): GentleNotificationConfig { const newConfig = { ...config, dismissCount: config.dismissCount + 1 }; if (newConfig.adaptiveFrequency && newConfig.dismissCount >= newConfig.suppressThreshold) { newConfig.maxPerHour = Math.max(1, Math.floor(newConfig.maxPerHour / 2)); } return newConfig; } function resetDismissals(config: GentleNotificationConfig): GentleNotificationConfig { return { ...config, dismissCount: 0 }; } function registerMessages(type: string, messages: GentleMessage[]): void { messagePools[type] = [...(messagePools[type] ?? []), ...messages]; } function getForbiddenPhrases(): readonly string[] { return FORBIDDEN_PHRASES; } function containsForbiddenPhrase(text: string): boolean { const lower = text.toLowerCase(); return FORBIDDEN_PHRASES.some(phrase => lower.includes(phrase.toLowerCase())); } return { getDefaultConfig, getMessage, shouldSuppress, recordDismissal, resetDismissals, registerMessages, getForbiddenPhrases, containsForbiddenPhrase, }; }