diff --git a/src/app/page.tsx b/src/app/page.tsx index 95396dc6c..ddc44bf67 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useMemo, useState } from "react"; +import React, { Suspense, useMemo, useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { useAuth } from "@/hooks/use-auth"; import { @@ -48,6 +48,16 @@ import { TourLauncher } from "@/components/onboarding/tour-launcher"; import { RecentAchievementsCard } from "@/components/gamification/recent-achievements-card"; import { RecentWorkoutsTile } from "@/components/dashboard/recent-workouts-tile"; +// v1.4.40 W-RSC — module-scope so the option object is stable across +// renders (audit-M2). Pre-fix the same literal was declared inside the +// component body, so every render created a fresh `{}` reference; +// TanStack does a shallow compare and shrugs at the equivalent values, +// but the slot is one future-callback-field away from cache poisoning. +const DASHBOARD_QUERY_OPTS = { + staleTime: 60_000, + refetchOnWindowFocus: false, +} as const; + const MoodChart = dynamic( () => import("@/components/charts/mood-chart").then((mod) => ({ @@ -218,10 +228,9 @@ export default function DashboardPage() { // storm. None of these are real-time data; minute-scale staleness // is fine and the dashboard's chart queries already match this // cadence. - const DASHBOARD_QUERY_OPTS = { - staleTime: 60_000, - refetchOnWindowFocus: false, - } as const; + // + // v1.4.40 W-RSC — `DASHBOARD_QUERY_OPTS` hoisted to module scope + // (audit-M2 — stable reference across renders). // v1.4.39.2 — split the dashboard's analytics consumption so the // tile-strip paints from the slim slice and the BD-Zielbereich + @@ -559,7 +568,15 @@ export default function DashboardPage() { upperBound: 55, }, ); - const hour = user?.timezone ? getHourForTimeZone(user.timezone) : null; + // v1.4.40 W-RSC — memoise the per-render `Intl.DateTimeFormat` + // instantiation (audit-H4). The greeting derived from the hour only + // changes when the user's timezone changes; recomputing it every + // render churned the parts-array and walked the format ICU twice per + // render across a 1 400-line component body. + const hour = useMemo( + () => (user?.timezone ? getHourForTimeZone(user.timezone) : null), + [user?.timezone], + ); const timeGreeting = hour == null ? t("dashboard.greeting.day") @@ -1421,14 +1438,51 @@ export default function DashboardPage() { > {trendCards.map((entry) => (