Skip to content

feat(desktop): reminders notifications, snooze, overlay, and inbox view mode#1093

Merged
wpfleger96 merged 5 commits into
mainfrom
duncan/reminders-notifications-snooze
Jun 17, 2026
Merged

feat(desktop): reminders notifications, snooze, overlay, and inbox view mode#1093
wpfleger96 merged 5 commits into
mainfrom
duncan/reminders-notifications-snooze

Conversation

@wpfleger96

Copy link
Copy Markdown
Collaborator

Builds four reminder features on the desktop app, all riding a single shared react-query spine. Reminders also move out of a standalone route and into the inbox as a view mode.

Phase 0 — useRemindersQuery spine

A single query keyed by ["reminders", pubkey] wraps fetchReminders. useReminderMutations wraps create/snooze/complete/cancel and invalidates the shared key on every success, so the badge, channel overlay, panel, and fire-on-due detection all read one source and can never disagree. This also auto-resolves the prior Phase 3 staleness where onUpdate() did not propagate outside the panel.

Phase 1 — Watermark fire-on-due + notification + badge

useReminderNotifications (mounted once in AppShell) detects due reminders with a persisted single-timestamp watermark in localStorage (buzz:lastReminderCheck:<pubkey>). On launch and every 30s it fires reminders where notBefore > watermark AND notBefore <= now AND status === "pending", coalesces multiple into one toast, then advances the watermark. The watermark seeds to now on first launch so history is not replayed; a reminder already past at the seed fails the strict > and surfaces only in the panel + badge, never as a toast. The OS toast respects desktopEnabled + the needs_action alert slot, with sound + dock bounce. The pure detection predicates (isDue, countDue, dueSince, groupReminders) live in lib/reminderFilters.ts so the highest-risk logic is unit-testable without react-query.

Phase 2 — Slack-style snooze dropdown

The hardcoded "snooze 1 hour" button becomes a Clock-icon dropdown (SnoozeMenu) over a shared lib/timePresets.ts module (TIME_PRESETS), the single source of truth for both the create dialog and snooze. "Custom…" reuses the native date/time input; a shared parseCustomDateTime future-time validator rejects notBefore <= now on both surfaces.

Phase 3 — Channel blue-tint overlay

RemindMeLaterProvider derives a Set of active (pending) reminder event IDs from the Phase 0 query and exposes it via context. MessageRow applies bg-blue-500/10 when a message's id is in the set; invalidation re-renders with a fresh set, so no stale tint.

Phase 4 — Reminders relocated into the inbox

Reminders is now a ?view=reminders mode inside the inbox, not a standalone nav item or route.

  • The inbox list-pane header gains a Messages | Reminders view-mode toggle. Reminders mode renders RemindersPanel rows directly (pending + completed via groupReminders(reminders, includeDone), snooze/complete/cancel inline) and never enters the FeedItem selection model — it does not touch selectedItemId or InboxDetailPane. This dissolves the detail-pane/selection collision by construction.
  • The auto-select effect and ?item= consume/mirror are gated to Messages mode. View-mode persists in the URL via ?view=remindersvalidateHomeSearch is extended to allowlist view so the param survives navigation, reload, and the redirect target.
  • deriveShellRoute stays pathname-only; /?view=reminders keeps selectedView === "home", accepted intentionally for the notification gate. No "reminders" arm is added to the union.
  • /reminders is replaced by a redirect to /?view=reminders (TanStack throw redirect), preserving back/forward history and bookmarks. routeTree.gen.ts is unchanged (the redirect keeps the route registered). The Bell sidebar item, goReminders, and RemindersScreen are removed.
  • The reminder badge has a single render site: the inbox Reminders toggle only. Count is due/overdue pending from the Phase 0 query, not folded into homeBadgeCount.

@wpfleger96 wpfleger96 force-pushed the duncan/reminders-notifications-snooze branch 2 times, most recently from 2af5d46 to 55989a3 Compare June 17, 2026 19:48
wpfleger96 pushed a commit that referenced this pull request Jun 17, 2026
@wpfleger96

Copy link
Copy Markdown
Collaborator Author

Reminders v6 — UI screenshots

Visual walkthrough of the four UI changes in this PR, captured via the v6 Playwright screenshot spec (reminders-v6-screenshots.spec.ts).

Snooze dropdown

Snooze menu open on a fired reminder, showing the defer-time presets (In 30m / 1h / 3h / Tomorrow 9am / Next Mon 9am / Custom).

01-snooze-dropdown

Blue-tint overlay

The bg-blue-500/10 tint applied to a message row with an active reminder, visibly distinct from untinted rows.

02-blue-tint-overlay

Inbox Reminders view

The inbox Reminders view — the v6 relocation target after the sidebar nav teardown — grouping reminders into OVERDUE and TODAY, each row with done / snooze / dismiss actions.

03-inbox-reminders-view

Reminder surfacing as due

The reminder surfacing as due — the watermark-guarded fire path fixed in this PR. The actual fire-on-due notification is an OS-native Notification (mocked in the harness), so the captured surface is the closest in-app evidence: the Reminders badge incrementing and the overdue row appearing the moment the reminder comes due.

04-fire-on-due-toast

@wpfleger96 wpfleger96 force-pushed the duncan/reminders-notifications-snooze branch 2 times, most recently from 7284d09 to 14ce20c Compare June 17, 2026 21:06
@wpfleger96

Copy link
Copy Markdown
Collaborator Author

Reminders desktop — slider→dropdown rework (shots after Will's correction)

Reminders is now the 6th entry in the existing inbox filter dropdown (All / Mentions / Needs Action / Activity / Agents / Reminders), not a pill-slider. Selecting it swaps the list body to the reminders view; the Complete/Snooze/Cancel row actions are fully preserved. Captured at tip 14ce20cd, rebased clean on current main.

Filter dropdown — Reminders option

01-inbox-filter-reminders-option
The existing inbox filter dropdown now carries a 6th Reminders entry alongside the five message filters, with its distinct due-reminder badge on the dropdown affordance.

Message row action — Remind me later

02-message-action-remind-later
The per-message action menu exposes Remind me later, the entry point for scheduling a reminder off any message.

Remind-me-later dialog

03-remind-me-later-dialog
Selecting Remind me later opens the time-picker dialog for choosing when the reminder fires.

Reminders view — empty

04-reminders-panel-empty
With the Reminders filter selected and no reminders scheduled, the body renders the empty reminders view.

Reminders view — active

05-reminders-panel-active
Scheduled reminders render grouped (e.g. a TODAY group) with Complete / Snooze / Cancel row actions intact on each.

Reminders view — fired / overdue

06-reminders-panel-fired
Fired and overdue reminders surface in the reminders view, row actions preserved.

wpfleger96 pushed a commit that referenced this pull request Jun 17, 2026
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 and others added 4 commits June 17, 2026 17:23
Builds four reminder surfaces on a shared react-query spine:

- A single useRemindersQuery keyed by pubkey, invalidated in all four
  mutation paths (create/snooze/complete/cancel), so the badge, channel
  overlay, panel, and fire-on-due detection can never disagree.
- App-level watermark fire-on-due: a persisted lastReminderCheck timestamp,
  firing pending reminders that newly cross not_before since the watermark,
  coalesced into one toast, seeded to now on first launch so history is not
  replayed. Pure predicates live in lib/reminderFilters.ts so the highest-risk
  detection logic is unit-testable without react-query.
- Slack-style snooze dropdown over a shared TIME_PRESETS module with a
  future-time validator guarding the custom date/time surface.
- Blue-tint overlay on channel messages that have an active reminder.
- Reminders relocated into the inbox as a ?view=reminders mode rather than a
  standalone route: it renders RemindersPanel rows directly and never enters
  the FeedItem selection model, so the detail-pane/selectedItemId collision is
  dissolved by construction. /reminders redirects to /?view=reminders to
  preserve history and bookmarks; the Bell sidebar item is removed.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
… advance

Fold two advisory review MINORs. groupReminders guarded on `!r.notBefore`,
dropping epoch-0 timestamps, while isDue/dueSince use `!== undefined` — align
all three so the lib reads consistently (epoch 0 is unreachable; no behavior
change). Document that the watermark advances even when the toast is suppressed
so a future reader does not mistake the no-backlog-replay design for a bug.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
On mount, useRemindersQuery is still loading (data === undefined) so
remindersRef.current is []. Without a guard, check() advances the
watermark past reminders that came due while the app was closed — the
"missed-while-asleep" window — permanently preventing their toast from
firing. Track query resolution via a ref and defer the watermark advance
when the array is empty AND the query has not resolved yet.

First-launch-with-no-reminders still advances correctly: the query
resolves to [] with queryResolvedRef === true, so the guard passes.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
The screenshot spec still navigated via the deleted view-mode slider, so it landed in the chat view where the filter dropdown is not mounted. Drive it through the inbox filter dropdown instead, seed reminders past the badge query's cached-empty result by invalidating the ["reminders"] query, and finalize the dropdown by dropping the "?view=reminders" URL persistence to local state. Harden waitForAnimations with a bounded race so looping animations (presence dots) and animations that restart mid-sample no longer hang the evaluate until Playwright aborts.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
@wpfleger96 wpfleger96 force-pushed the duncan/reminders-notifications-snooze branch from 14ce20c to 6f406c6 Compare June 17, 2026 21:23
The inbox filter dropdown sits in a justify-end header row inside the
list pane. In messages mode the pane is the fixed-width left grid column,
so the dropdown lands mid-window. Selecting Reminders collapses the grid
to grid-cols-1 and the pane goes full-width, flinging the same right-aligned
dropdown to the far window edge. Cap the header row to the list-column
width so the dropdown's horizontal position is identical in both modes.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
@wpfleger96 wpfleger96 merged commit 22b47db into main Jun 17, 2026
20 checks passed
@wpfleger96 wpfleger96 deleted the duncan/reminders-notifications-snooze branch June 17, 2026 21:47
tlongwell-block pushed a commit that referenced this pull request Jun 18, 2026
…te-response

* origin/main: (194 commits)
  Fold agent core memory into the session system prompt (#1112)
  feat(cli): add patches and issues commands for NIP-34 git collaboration (#1073)
  fix(desktop): stop random timeline message loss + page reconnect replay (#1105)
  Update README.md
  fix(desktop): keep thread replies from scrolling channel (#1109)
  fix(buzz-acp): accept siblings under allowlist author gate (#1108)
  feat(deploy): add production Helm chart for Buzz (#990)
  fix(desktop): keep MembersSidebar input usable while an add is in flight (#1106)
  chore(release): release version 0.3.25 (#1102)
  fix(desktop): stop dimming deferred message lists (#1104)
  Smooth channel loading: single-surface timeline state machine (#1099)
  feat: surface base + persona system prompts in observer feed (#1103)
  ci: move reminder e2e to a dedicated backend-integration job (#1098)
  fix: give agent-observer sub a replay-capable limit (#1100)
  fix: make managed-agent spawn and teardown portable to Windows (#1097)
  fix(desktop): constrain message timeline width with min-w-0 (#1092)
  feat(desktop): reminders notifications, snooze, overlay, and inbox view mode (#1093)
  feat(prompt): add memory hygiene and hoist universal engineering discipline to base prompt (#1085)
  fix(desktop): correct thread-unread badge flicker, stale clear, phantom count, mention gate, and nested count (#1080)
  Fix mention chip alignment (#1094)
  ...

# Conflicts:
#	crates/buzz-cli/src/commands/workflows.rs
tlongwell-block pushed a commit that referenced this pull request Jun 18, 2026
…te-response

* origin/main: (194 commits)
  Fold agent core memory into the session system prompt (#1112)
  feat(cli): add patches and issues commands for NIP-34 git collaboration (#1073)
  fix(desktop): stop random timeline message loss + page reconnect replay (#1105)
  Update README.md
  fix(desktop): keep thread replies from scrolling channel (#1109)
  fix(buzz-acp): accept siblings under allowlist author gate (#1108)
  feat(deploy): add production Helm chart for Buzz (#990)
  fix(desktop): keep MembersSidebar input usable while an add is in flight (#1106)
  chore(release): release version 0.3.25 (#1102)
  fix(desktop): stop dimming deferred message lists (#1104)
  Smooth channel loading: single-surface timeline state machine (#1099)
  feat: surface base + persona system prompts in observer feed (#1103)
  ci: move reminder e2e to a dedicated backend-integration job (#1098)
  fix: give agent-observer sub a replay-capable limit (#1100)
  fix: make managed-agent spawn and teardown portable to Windows (#1097)
  fix(desktop): constrain message timeline width with min-w-0 (#1092)
  feat(desktop): reminders notifications, snooze, overlay, and inbox view mode (#1093)
  feat(prompt): add memory hygiene and hoist universal engineering discipline to base prompt (#1085)
  fix(desktop): correct thread-unread badge flicker, stale clear, phantom count, mention gate, and nested count (#1080)
  Fix mention chip alignment (#1094)
  ...

Co-authored-by: Tyler Longwell <tlongwell@squareup.com>
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>

# Conflicts:
#	crates/buzz-cli/src/commands/workflows.rs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant