{t("insights.derived.trajectory.insufficient")}
+ {daysNeeded > 0 ? ( ++ {t("insights.derived.trajectory.daysNeeded", { + count: daysNeeded, + })} +
+ ) : null}+
{data?.revalidating
? t("insights.narrativeUpdating")
: t("insights.narrativeUpdated", {
diff --git a/src/components/insights/sleep-hypnogram.tsx b/src/components/insights/sleep-hypnogram.tsx
index f18bea157..2d222e0f7 100644
--- a/src/components/insights/sleep-hypnogram.tsx
+++ b/src/components/insights/sleep-hypnogram.tsx
@@ -125,10 +125,40 @@ export function SleepHypnogram({ session }: SleepHypnogramProps) {
});
}, [session.segments]);
- const domain: [number, number] = [
- new Date(session.start).getTime(),
- new Date(session.end).getTime(),
- ];
+ const domain = useMemo<[number, number]>(
+ () => [
+ new Date(session.start).getTime(),
+ new Date(session.end).getTime(),
+ ],
+ [session.start, session.end],
+ );
+
+ // v1.12.0 — explicit clock-aligned X-axis ticks. With `data={[]}` Recharts
+ // has no points to derive ticks from and would otherwise label the domain
+ // endpoints with raw bedtime/wake stamps (e.g. 23:14 / 06:02). Snap to whole
+ // clock hours and pick a 1–3 h step from the night's span so the axis reads
+ // 23:00 / 01:00 / 03:00 / 05:00 and stays mobile-legible (≈4–6 ticks).
+ const xTicks = useMemo(() => {
+ const [from, to] = domain;
+ const spanHours = (to - from) / 3_600_000;
+ if (!Number.isFinite(spanHours) || spanHours <= 0) return [];
+ // Aim for ~5 ticks: 1 h up to 5 h span, 2 h up to 10 h, else 3 h.
+ const stepHours = spanHours <= 5 ? 1 : spanHours <= 10 ? 2 : 3;
+ const stepMs = stepHours * 3_600_000;
+ // First whole hour at or after `from`, aligned to the step grid.
+ const firstHour = new Date(from);
+ firstHour.setMinutes(0, 0, 0);
+ if (firstHour.getTime() < from) firstHour.setTime(firstHour.getTime() + 3_600_000);
+ // Align the first tick to a multiple of the step (relative to midnight).
+ while ((firstHour.getHours() % stepHours) !== 0) {
+ firstHour.setTime(firstHour.getTime() + 3_600_000);
+ }
+ const ticks: number[] = [];
+ for (let t = firstHour.getTime(); t <= to; t += stepMs) {
+ ticks.push(t);
+ }
+ return ticks;
+ }, [domain]);
const timeFmt = useMemo(
() =>
@@ -167,8 +197,11 @@ export function SleepHypnogram({ session }: SleepHypnogramProps) {
const hasBreakdown = breakdown.length > 0;
return (
- timeFmt.format(new Date(v))}
diff --git a/src/components/insights/sleep-overview.tsx b/src/components/insights/sleep-overview.tsx
index b5fb129c7..4e41a5117 100644
--- a/src/components/insights/sleep-overview.tsx
+++ b/src/components/insights/sleep-overview.tsx
@@ -130,8 +130,12 @@ export function SleepOverview() {
return (
{primary}
{sleepSummary?.count