freezmo/app
Martin Dvorak 77819b5961 Push reminders: personal to the notes, generic without them
The daily reminder said one generic line to everyone, because the server
never sees a deck. Now the notification text is written in the browser
(lib/reminder.ts) and travels with the subscription:

- Personal: "3 cards are due in Sky deck", in the app's language, tapping
  through to /review. Without notes (or with nothing due) it is the plain
  nudge, translated in both languages — push.title / push.due.* / push.generic.
- POST /api/push accepts an optional message {title, body, url}; url must be
  a path on this app, so a stolen subscription cannot make the notification
  link elsewhere. The store keeps endpoint, keys, the message and createdAt
  (a refresh no longer resets the timestamp).
- components/PushReminderSync.tsx refreshes the stored text when the app
  opens and whenever the tab becomes visible (throttled to twice a day), so a
  count is only ever sent while it is true.
- scripts/send-reminders.mjs sends each device its own text and falls back to
  PUSH_REMINDER_TITLE/_BODY for a subscription that has none
  (scripts/reminder-payload.mjs, unit-checked).
- /api/push/test previews the real reminder with a "Test:" prefix.
- deploy/freezmo-reminders.{service,timer}: the daily send as a systemd user
  timer (18:00 local, Persistent, randomised), documented in the README.
- Settings copy, privacy page (en+cs) and .env.example say exactly what the
  server now holds.
- phase2-test grew the whole path with a faked browser subscription: personal
  text in en and cs, the generic fallback in both, the URL guard, and the
  sender's preference order (81 checks).

Version 0.8.2, SW v104.
2026-10-18 14:34:15 +00:00
..
api Push reminders: personal to the notes, generic without them 2026-10-18 14:34:15 +00:00
chat Chat mode, phase 1: grounded streaming chat over your notes 2026-10-11 18:32:11 +00:00
cookies Privacy policy, terms of service, cookie policy + a cookie notice 2026-10-12 21:26:55 +00:00
create App versioning + the create page's missing top padding 2026-10-12 20:04:26 +00:00
deck/[id] Accessibility pass, report control for homework exercises, clearer rail actions 2026-10-12 21:15:56 +00:00
exam Smart Review: instant loading feedback, no more dead-feeling tap 2026-10-11 14:35:16 +00:00
homework Navigation restructure: five-tab bottom bar, no phone drawer 2026-10-10 16:53:55 +00:00
offline Add an English/Czech interface language switch 2026-10-08 13:56:32 +00:00
podcast Podcast mode: notes read aloud with a lyric-style word highlight 2026-10-16 11:42:15 +00:00
privacy Privacy policy, terms of service, cookie policy + a cookie notice 2026-10-12 21:26:55 +00:00
review Smart Review: instant loading feedback, no more dead-feeling tap 2026-10-11 14:35:16 +00:00
share Navigation restructure: five-tab bottom bar, no phone drawer 2026-10-10 16:53:55 +00:00
share-target Native-feel pass: PWA integration, system back, offline, haptics, undo 2026-10-10 12:16:03 +00:00
stats Smart Review: instant loading feedback, no more dead-feeling tap 2026-10-11 14:35:16 +00:00
teach Teach it back: the AI becomes the curious student 2026-10-16 23:05:24 +00:00
terms Privacy policy, terms of service, cookie policy + a cookie notice 2026-10-12 21:26:55 +00:00
favicon.ico Use the new app icon everywhere 2026-10-08 00:38:54 +00:00
globals.css Aero music: the button works on the podcast player 2026-10-16 21:04:39 +00:00
layout.tsx Push reminders: personal to the notes, generic without them 2026-10-18 14:34:15 +00:00
manifest.ts Accessibility pass, report control for homework exercises, clearer rail actions 2026-10-12 21:15:56 +00:00
not-found.tsx Custom Frutiger Aero 404 2026-10-14 15:27:47 +00:00
page.tsx Podcast mode: notes read aloud with a lyric-style word highlight 2026-10-16 11:42:15 +00:00