mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
fix(desktop): show due-reminder count in the Inbox nav badge (#1191)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
co-authored by
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7
parent
45eae7c0ec
commit
c0858dac12
@@ -44,6 +44,10 @@ const overrides = new Map([
|
||||
// overage from load-bearing per-message plumbing, not generic debt growth.
|
||||
// Approved override; still queued to split with the rest of this list.
|
||||
["src/features/messages/ui/MessageThreadPanel.tsx", 1006],
|
||||
// useDueReminderBadgeCount hook call + sum to wire due-reminder count into
|
||||
// the Inbox nav badge — a small overage from load-bearing badge plumbing,
|
||||
// not generic debt growth. Approved override; still queued to split.
|
||||
["src/app/AppShell.tsx", 1007],
|
||||
]);
|
||||
|
||||
await runFileSizeCheck({
|
||||
|
||||
@@ -69,6 +69,7 @@ import {
|
||||
isSettingsSection,
|
||||
} from "@/features/settings/ui/SettingsPanels";
|
||||
import { HuddleBar, HuddleProvider } from "@/features/huddle";
|
||||
import { useDueReminderBadgeCount } from "@/features/reminders/hooks";
|
||||
import { RemindMeLaterProvider } from "@/features/reminders/ui/RemindMeLaterProvider";
|
||||
import { useReminderNotifications } from "@/features/reminders/useReminderNotifications";
|
||||
import { AppSidebar } from "@/features/sidebar/ui/AppSidebar";
|
||||
@@ -411,6 +412,12 @@ export function AppShell() {
|
||||
getThreadReadAt,
|
||||
);
|
||||
|
||||
// Raw add to the in-app nav badge, mirroring the inbox filter badge; gated by
|
||||
// homeBadgeEnabled to match every other badge contribution.
|
||||
const dueReminderBadge = useDueReminderBadgeCount(
|
||||
identityQuery.data?.pubkey,
|
||||
notificationSettings.settings.homeBadgeEnabled,
|
||||
);
|
||||
const isNotifiedForThread = React.useCallback(
|
||||
(rootId: string) =>
|
||||
!mutedRootIds.has(rootId) &&
|
||||
@@ -822,7 +829,7 @@ export function AppShell() {
|
||||
currentPubkey={identityQuery.data?.pubkey}
|
||||
errorMessage={channelsErrorMessage}
|
||||
fallbackDisplayName={identityQuery.data?.displayName}
|
||||
homeBadgeCount={homeBadgeCount}
|
||||
homeBadgeCount={homeBadgeCount + dueReminderBadge}
|
||||
isAddWorkspaceOpen={isAddWorkspaceOpen}
|
||||
isCreatingChannel={createChannelMutation.isPending}
|
||||
isCreatingForum={createForumMutation.isPending}
|
||||
|
||||
@@ -33,6 +33,27 @@ export function useRemindersQuery(pubkey: string | undefined) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The due-reminder contribution to the in-app Inbox nav badge. Reminders are a
|
||||
* separate stream from the feed badge machinery, so the count is summed in at
|
||||
* the AppShell wiring point rather than threaded through homeBadge.ts. Reads
|
||||
* the shared query above, so the useReminderNotifications poll's invalidate
|
||||
* keeps it live and countDue re-evaluates as reminders cross due. The caller
|
||||
* adds this raw (no isHomeActive suppression), mirroring the inbox filter
|
||||
* badge, which persists while the Inbox is open.
|
||||
*
|
||||
* `enabled` mirrors the homeBadgeEnabled contract: when the home badge toggle is
|
||||
* off, the feed contribution returns 0, so the reminder add must too — otherwise
|
||||
* a disabled badge would still show a reminder `(1)`.
|
||||
*/
|
||||
export function useDueReminderBadgeCount(
|
||||
pubkey: string | undefined,
|
||||
enabled: boolean,
|
||||
): number {
|
||||
const remindersQuery = useRemindersQuery(pubkey);
|
||||
return enabled ? countDueReminders(remindersQuery.data ?? []) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps every reminder write so the shared query is invalidated on success —
|
||||
* the consistency spine the panel/badge/overlay all depend on. A mutation that
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import * as React from "react";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { useRemindersQuery } from "@/features/reminders/hooks";
|
||||
import {
|
||||
remindersQueryKey,
|
||||
useRemindersQuery,
|
||||
} from "@/features/reminders/hooks";
|
||||
import { dueSince } from "@/features/reminders/lib/reminderFilters";
|
||||
import type { Reminder } from "@/features/reminders/lib/reminderTypes";
|
||||
import {
|
||||
@@ -51,6 +55,7 @@ export function useReminderNotifications(
|
||||
settings: NotificationSettings,
|
||||
): void {
|
||||
const reminders = useRemindersQuery(pubkey).data;
|
||||
const queryClient = useQueryClient();
|
||||
const remindersRef = React.useRef<Reminder[]>([]);
|
||||
remindersRef.current = reminders ?? [];
|
||||
const settingsRef = React.useRef(settings);
|
||||
@@ -109,10 +114,18 @@ export function useReminderNotifications(
|
||||
// not backlog-replay reminders that came due while muted — same no-replay
|
||||
// rationale as seed-to-now. Suppressed reminders still show in panel/badge.
|
||||
window.localStorage.setItem(watermarkStorageKey(pubkey), String(now));
|
||||
// Liveness tick: re-render every countDue consumer (inbox nav badge,
|
||||
// HomeView filter, panel) so a reminder that crossed notBefore while the
|
||||
// app sat idle surfaces within the poll interval. Safe to run after the
|
||||
// watermark advance — the toast check() fires on this hook's own
|
||||
// setInterval, not on query-data change, so the refetch cannot re-fire it.
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: remindersQueryKey(pubkey),
|
||||
});
|
||||
};
|
||||
|
||||
check();
|
||||
const interval = window.setInterval(check, POLL_INTERVAL_MS);
|
||||
return () => window.clearInterval(interval);
|
||||
}, [pubkey]);
|
||||
}, [pubkey, queryClient]);
|
||||
}
|
||||
|
||||
@@ -270,6 +270,50 @@ function aliceReminderContent() {
|
||||
});
|
||||
}
|
||||
|
||||
// Nav badge — the surface unit tests can't prove: a due reminder driving the
|
||||
// Inbox nav item's `(1)` count. The count is gated behind `homeBadgeEnabled`,
|
||||
// so seed that setting on before installMockBridge (addInitScript runs at
|
||||
// document start, ahead of the app reading localStorage).
|
||||
const NAVBADGE_SHOTS = "test-results/reminders-navbadge";
|
||||
const NOTIFICATION_SETTINGS_KEY = `buzz-notification-settings.v2:${MOCK_PUBKEY}`;
|
||||
|
||||
test.describe("reminders nav badge", () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.addInitScript(
|
||||
({ key }) => {
|
||||
localStorage.setItem(key, JSON.stringify({ homeBadgeEnabled: true }));
|
||||
},
|
||||
{ key: NOTIFICATION_SETTINGS_KEY },
|
||||
);
|
||||
await installMockBridge(page);
|
||||
});
|
||||
|
||||
test("09 — Inbox nav badge counts a due reminder", async ({ page }) => {
|
||||
await gotoInboxHome(page);
|
||||
|
||||
// One overdue pending reminder (notBefore in the past) is the sole badge
|
||||
// contributor; the feed baseline is 0, so an exact "1" proves the reminder
|
||||
// — not feed noise — drives the nav count.
|
||||
const pastTimestamp = Math.floor(Date.now() / 1000) - 7200;
|
||||
await seedReminders(page, [
|
||||
mockReminderEvent({
|
||||
id: "reminder-navbadge-01",
|
||||
dTag: "rem-navbadge-01",
|
||||
content: aliceReminderContent(),
|
||||
notBefore: pastTimestamp,
|
||||
}),
|
||||
]);
|
||||
|
||||
await expect(page.getByTestId("sidebar-home-count")).toHaveText("1");
|
||||
await waitForAnimations(page);
|
||||
|
||||
await page.screenshot({
|
||||
path: `${NAVBADGE_SHOTS}/01-inbox-nav-badge-due-reminder.png`,
|
||||
clip: { x: 0, y: 0, width: 900, height: 720 },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("reminders phase 2 — author, source, navigation", () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await installMockBridge(page);
|
||||
|
||||
Reference in New Issue
Block a user