From 85240e96b1a1b56eaff278862e9001b2aebf3d26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 7 Aug 2026 16:14:28 +0200 Subject: [PATCH] fix(cycle): route the new save signals through the shared affordances Two repo-wide guards caught the additions rather than a reviewer, which is what they are for. The success toasts reached for sonner directly. The success affordance belongs to the outcome module, which binds it to the success key and nothing else, so a surface cannot raise a green toast for a write that wrote nothing. They go through toastWrittenOutcome now. The sheet's date header formatted a day key in the browser's zone. A cycle day key is a calendar day, not an instant, so it is anchored at noon UTC and rendered in UTC, the same anchor the domain's day arithmetic uses. Otherwise a reader east or west of the browser zone could see the sheet name a different day than the one it writes to. --- src/components/cycle/log-day-sheet.tsx | 15 ++++++++++++--- src/components/cycle/use-cycle.ts | 11 ++++++----- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/components/cycle/log-day-sheet.tsx b/src/components/cycle/log-day-sheet.tsx index ebb633514..815b77df7 100644 --- a/src/components/cycle/log-day-sheet.tsx +++ b/src/components/cycle/log-day-sheet.tsx @@ -479,10 +479,19 @@ function CervixRow({ * deliberately: it is how people recognise a date they are recalling. */ export function formatSheetDate(date: string, locale: Locale): string { - const [year, month, day] = date.split("-").map(Number); - return new Date(year, month - 1, day).toLocaleDateString( + // A cycle day key is a calendar day, not an instant, so it is anchored at + // noon UTC and rendered in UTC — the same anchor the domain's day arithmetic + // uses. Formatting it in the browser's zone would let a reader east or west + // of it see the sheet name a different day than the one it writes to. + return new Date(`${date}T12:00:00Z`).toLocaleDateString( resolveIntlLocale(locale), - { weekday: "long", day: "numeric", month: "long", year: "numeric" }, + { + weekday: "long", + day: "numeric", + month: "long", + year: "numeric", + timeZone: "UTC", + }, ); } diff --git a/src/components/cycle/use-cycle.ts b/src/components/cycle/use-cycle.ts index d21060e30..0e8b4fd22 100644 --- a/src/components/cycle/use-cycle.ts +++ b/src/components/cycle/use-cycle.ts @@ -20,6 +20,7 @@ import { apiPost, } from "@/lib/api/api-fetch"; import { useTranslations } from "@/lib/i18n/context"; +import { toastWrittenOutcome } from "@/components/outcome/outcome-toast"; import { cycleDependentKeys, @@ -247,7 +248,7 @@ export function useLogDay() { }, onSuccess: () => { void invalidateKeys(qc, cycleDependentKeys); - toast.success(t("cycle.saveSuccess")); + toastWrittenOutcome("success", t("cycle.saveSuccess")); }, }); } @@ -293,7 +294,7 @@ export function usePatchDayLog() { }, onSuccess: () => { void invalidateKeys(qc, cycleDependentKeys); - toast.success(t("cycle.saveSuccess")); + toastWrittenOutcome("success", t("cycle.saveSuccess")); }, }); } @@ -311,7 +312,7 @@ export function useStartPeriod() { }, onSuccess: () => { void invalidateKeys(qc, cycleDependentKeys); - toast.success(t("cycle.periodStartSaved")); + toastWrittenOutcome("success", t("cycle.periodStartSaved")); }, // v1.16.4 — period boundaries had no failure signal at all (the sheet // simply stayed open); a toast names the rejection. @@ -333,7 +334,7 @@ export function useEndPeriod() { }, onSuccess: () => { void invalidateKeys(qc, cycleDependentKeys); - toast.success(t("cycle.periodEndSaved")); + toastWrittenOutcome("success", t("cycle.periodEndSaved")); }, onError: () => toast.error(t("cycle.saveError")), }); @@ -349,7 +350,7 @@ export function useDeleteDayLog() { }, onSuccess: () => { void invalidateKeys(qc, cycleDependentKeys); - toast.success(t("cycle.deleteSuccess")); + toastWrittenOutcome("success", t("cycle.deleteSuccess")); }, onError: () => toast.error(t("cycle.deleteError")), });