"use client"; /** * The daily reminder's text, composed **in the browser**. * * The server never sees the notes — it only knows which push endpoints exist — * so the personal part of the nudge has to be written here and stored with the * subscription (see lib/push-store.ts). Personal means: how many of the user's * cards are due and which deck they belong to; the notification then opens the * review screen. When there is nothing to name — no decks yet, or nothing due — * the message is the plain nudge in the user's own language (English or Czech, * like every other string in the app). * * Always resolves; a failure to read the local stores only costs the personal * words, never the reminder. */ import type { SrsRecord } from "./history"; import { ensureLearningHydrated, learningStore } from "./history"; import { plural, t } from "./i18n"; import { dueItems } from "./learning"; import { deckStore } from "./storage"; export type ReminderMessage = { title: string; body: string; url: string }; /** The generic nudge, used whenever the notes cannot carry the message. */ export function genericReminder(): ReminderMessage { return { title: t("push.title"), body: t("push.generic"), url: "/" }; } /** Which deck has the most due cards (ties keep the first, i.e. the oldest). */ function busiestDeck(schedules: SrsRecord[]): { title: string; count: number } | null { const byDeck = new Map(); for (const item of dueItems(schedules)) { const entry = byDeck.get(item.deck.id) ?? { title: item.deck.title, count: 0 }; entry.count += 1; byDeck.set(item.deck.id, entry); } let top: { title: string; count: number } | null = null; for (const entry of byDeck.values()) { if (!top || entry.count > top.count) top = entry; } return top; } export async function buildReminderMessage(): Promise { try { await ensureLearningHydrated(); const top = busiestDeck(learningStore.getSrsSnapshot()); if (!top || deckStore.getSnapshot().length === 0) return genericReminder(); return { title: t("push.title"), body: plural(top.count, "push.due", { deck: top.title }), /* The review screen holds the due queue the nudge talks about. */ url: "/review", }; } catch { return genericReminder(); } }