From a4774783a9db0151b2e7c930ef149f930ddbdc6f Mon Sep 17 00:00:00 2001 From: Dave Date: Thu, 17 Sep 2026 12:06:20 +0200 Subject: [PATCH] fix(dashboard): single template-literal child for to stop SVG hydration mismatch React 19's HTML serializer drops mixed text/expression children inside <title> elements when emitting SVG markup, so the server rendered <title> while the client rendered 2026-05-24: 0 commits. That mismatch was one of the documented triggers of the chunk.reason.enqueueModel flight-client crash in vercel/next.js#92362. Pre-formatting the tooltip into a single string before passing it as the 's only child keeps server and client output identical. --- src/components/dashboard/activity-calendar.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/components/dashboard/activity-calendar.tsx b/src/components/dashboard/activity-calendar.tsx index 0e06259..059e1de 100644 --- a/src/components/dashboard/activity-calendar.tsx +++ b/src/components/dashboard/activity-calendar.tsx @@ -93,6 +93,12 @@ export function ActivityCalendar({ {grid.weeks.map((week, weekIndex) => week.map((day, dayIndex) => { if (!day.inRange) return null; + // Single template-literal child: React 19's HTML serializer drops + // mixed text/expression children inside <title> elements when + // emitted as SSR markup, causing a hydration mismatch where the + // client re-adds the text. One pre-formatted string keeps the + // server and client output identical. + const tooltipText = `${day.date}: ${day.count} ${day.count === 1 ? "commit" : "commits"}`; return ( <rect key={day.date} @@ -103,10 +109,7 @@ export function ActivityCalendar({ rx={2} fill={fill(day.level)} > - <title> - {day.date}: {day.count}{" "} - {day.count === 1 ? "commit" : "commits"} - + {tooltipText} ); })