From 771608d3d4f4cdcfadc1b5c0d0563bc989d72a18 Mon Sep 17 00:00:00 2001 From: Lucas Berger Date: Sat, 13 Jun 2026 20:49:25 -0400 Subject: [PATCH] docs(11): capture phase context for per-event reminders --- .planning/STATE.md | 16 +-- .../11-per-event-reminders/11-CONTEXT.md | 130 ++++++++++++++++++ .../11-DISCUSSION-LOG.md | 72 ++++++++++ 3 files changed, 210 insertions(+), 8 deletions(-) create mode 100644 .planning/phases/11-per-event-reminders/11-CONTEXT.md create mode 100644 .planning/phases/11-per-event-reminders/11-DISCUSSION-LOG.md diff --git a/.planning/STATE.md b/.planning/STATE.md index 7105e07..7467121 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -3,15 +3,15 @@ gsd_state_version: 1.0 milestone: v1.1 milestone_name: Operability & Polish status: verifying -stopped_at: "Completed Phase 10 Plan 02 (admin role primitives: requireAdmin, upsertUser is_admin, /api/me isAdmin+needsProviderSetup)" -last_updated: "2026-06-13T19:45:55.920Z" -last_activity: 2026-06-13 +stopped_at: Phase 11 context gathered +last_updated: "2026-06-14T00:49:25.513Z" +last_activity: "2026-06-13 - Completed quick task 260613-ndv: isolated local apps/api tests to familysync_test (dev DB no longer polluted)" progress: - total_phases: 20 + total_phases: 21 completed_phases: 8 total_plans: 27 completed_plans: 27 - percent: 40 + percent: 38 --- # Project State @@ -246,9 +246,9 @@ Recent decisions affecting current work: ## Session Continuity -Last session: 2026-06-13T19:27:40.437Z -Stopped at: Completed Phase 10 Plan 02 (admin role primitives: requireAdmin, upsertUser is_admin, /api/me isAdmin+needsProviderSetup) -Resume file: None +Last session: 2026-06-14T00:49:25.504Z +Stopped at: Phase 11 context gathered +Resume file: .planning/phases/11-per-event-reminders/11-CONTEXT.md ## Operator Next Steps diff --git a/.planning/phases/11-per-event-reminders/11-CONTEXT.md b/.planning/phases/11-per-event-reminders/11-CONTEXT.md new file mode 100644 index 0000000..17a5a63 --- /dev/null +++ b/.planning/phases/11-per-event-reminders/11-CONTEXT.md @@ -0,0 +1,130 @@ +# Phase 11: Per-Event Reminders - Context + +**Gathered:** 2026-06-13 +**Status:** Ready for planning + + +## Phase Boundary + +A per-event reminder lead picker on the event form, round-tripped to Fastmail as a +VALARM, with a variable-lead push scheduler that honors each event's choice. The +preset list, default-None, VALARM DURATION-trigger format, preserve-on-edit +mechanism, `uid:dtstartMs` dedup, variable scan window, dropping the `isShared`-only +reminder restriction, and the scheduler reading `reminder_lead_minutes` from the DB +as ground truth are all **locked by ROADMAP Phase 11 + research/PITFALLS.md** — this +discussion only resolved the product/UX gray areas on top of that. + + + + +## Implementation Decisions + +### Reminder picker — timed vs all-day presets +- **D-01:** Timed events keep the locked preset list: None / 5m / 10m / 15m / 30m / 1h / 2h / 1d / 2d, default **None**. +- **D-02:** All-day events get a **context-swapped, day-granularity** preset list: **None / Same day / 1 day before / 2 days before / 1 week before**, default **None**. The picker is NOT hidden or disabled for all-day events. +- **D-03:** The picker is shown for both event types — it swaps its option set based on the All-day toggle, rather than disappearing. + +### All-day reminder semantics +- **D-04:** All-day reminders fire at **9 AM local** on the computed alert day (retains the roadmap's sensible-morning decision; "Same day" = 9 AM on the event's own date, "1 day before" = 9 AM the prior day, etc.). +- **D-05:** All-day leads are stored in the existing `reminder_lead_minutes` column as minutes: Same day = `0`, 1d = `1440`, 2d = `2880`, 1wk = `10080`. The scheduler applies the 9 AM-local rule whenever the event is all-day. +- **D-06:** **`reminder_lead_minutes` must distinguish "no reminder" from "same-day".** No reminder = `NULL` (no VALARM, no push). Same-day all-day = `0` (fire 9 AM on the event date). For timed events `0`/absent still means None. The scheduler and `outboxPayloadSchema` must treat NULL-vs-0 as semantically distinct. + +### Existing / non-preset alarms on edit (preserve — CAL-14) +- **D-07:** **Show exact if single.** When an event already carries a reminder that isn't a preset: if it is a *single simple relative alarm*, render its real value in the picker (e.g. "45 min before") even though it's off-list. For *absolute-time triggers or multiple alarms*, show a read-only **"Custom (kept)"** entry. +- **D-08:** In both off-list cases the original VALARM(s) are **preserved verbatim** on save unless the user explicitly selects a preset or None — never silently rewritten or dropped (mirrors the WR-01 RRULE-preserve pattern; `outboxPayloadSchema` distinguishes "no change" from explicit "no reminder"). + +### Notification copy +- **D-09:** Reminder push body uses **humanized relative** phrasing, largest sensible unit: "Starts in 2 days" / "Starts in 1 hour" / "Starts in 30 min". Replaces the current hardcoded `Starts in ${minutes} min` which breaks for long leads. Title and deep-link `navigate` behavior are unchanged. + +### Recurring events +- **D-10:** **Series-level only.** One VALARM on the master event; every occurrence inherits the same lead, and the scheduler fires per occurrence naturally. No per-occurrence (RECURRENCE-ID) reminder override in this phase — see Deferred. + +### Claude's Discretion +- Picker placement within `EventForm.tsx` (reuse the existing labeled `` (reuse Recurrence-select pattern; allDay-aware preset swap). + + + + +## Existing Code Insights + +### Reusable Assets +- `EventForm.tsx` already renders a labeled `` pattern). +- Exact humanized-unit thresholds/wording for the notification body. + +## Deferred Ideas + +- Per-occurrence (RECURRENCE-ID) reminder override — its own phase if wanted. +- Reminder snooze / notification-preferences UI — already out of scope per REQUIREMENTS.md. +- Reviewed-not-folded todos: PWA BottomTabBar overlap (Phase 17), Gitea CI regression/Docker publish (Phase 8 / backlog).