mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 06:40:50 +00:00
v1.4.33 — polish and reliability
Quality-leap release between two HealthKit milestones. The headline is a P0 hotfix for the `/api/analytics` 500 that broke the Insights mother page for any account with more than a few thousand measurements: the six per-metric status helpers were spreading the entire numeric history into `Math.min` / `Math.max`, which trips a stack overflow once a single argument list exceeds the V8 spread cap. Both the surfaced symptom and the latent risk in five sibling helpers were folded onto a reduce-based min/max path. Around that hotfix landed a deep polish pass: the Insights mother page now defers three below-the-fold blocks behind `next/dynamic` to trim the cold mount cost, the analytics endpoint gained a `?slice=summaries` slim slice that the per-metric sub-pages now ride, the Coach snapshot builder picked up a 60 s LRU keyed on `(userId, scope)`, the Settings shell consolidated three duplicate copies of the about / notifications / about-dropdown surfaces, the navigation strip dropped a redundant "Home" group label and a colliding "Notifications" sibling, every icon-only button on the medications and admin surfaces gained an accessible name, every Progress bar gained one too, three pages had their heading hierarchy repaired to a sequential h1 → h2 → h3, the mobile Coach FAB now hides while a chart tooltip is open so it cannot occlude the read-out, and the auth shell normalised every authenticated route to a single `max-w-screen-xl` container. Also lands the Apple App Site Association handler at `/.well-known/apple-app-site-association` on every host that fronts the app, advertising the iOS bundle's App ID prefix (S8WDX4W5KX.dev.healthlog.app) under `webcredentials.apps` so the passkey ceremony shares cleanly between the web origin and the iOS app. The release closes nineteen issues from a five-surface audit + a runtime bug-hunt pass, plus six follow-up tracks of polish across the affected surfaces.
This commit is contained in:
1 parent
c7a0ff214b
commit
7bef199039
186 files changed
+9358
-946
No files matched your search
@@ -1,10 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { hasMetricData, type InsightMetric } from "@/lib/insights/metric-availability";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import { useAnalyticsQuery } from "@/lib/queries/use-analytics-query";
|
||||
import type { SubPageAnalyticsData } from "@/types/analytics";
|
||||
|
||||
/**
|
||||
@@ -53,24 +51,20 @@ export function useInsightsAnalytics(
|
||||
metric: InsightMetric,
|
||||
): UseInsightsAnalyticsResult {
|
||||
const { isAuthenticated } = useAuth();
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: queryKeys.analytics(),
|
||||
queryFn: async (): Promise<SubPageAnalyticsData> => {
|
||||
const res = await fetch("/api/analytics");
|
||||
if (!res.ok) throw new Error("Failed");
|
||||
const json = (await res.json()) as { data: SubPageAnalyticsData };
|
||||
return json.data;
|
||||
},
|
||||
enabled: isAuthenticated,
|
||||
staleTime: 60 * 1000,
|
||||
});
|
||||
// v1.4.33 IW2 — sub-pages only read `summaries[METRIC].count` for the
|
||||
// gating short-circuit, so they ride on the slim slice. The mother
|
||||
// page keeps the default thick slice for `correlations` /
|
||||
// `healthScore` / `bpInTargetPct*`; both consumers live in the same
|
||||
// queryKey tree (root `["analytics"]`) but the slim slice has its own
|
||||
// cache slot under `["analytics", "summaries"]`.
|
||||
const query = useAnalyticsQuery({ slice: "summaries" });
|
||||
const data = query.data as SubPageAnalyticsData | undefined;
|
||||
|
||||
const isEmpty =
|
||||
Boolean(isAuthenticated) &&
|
||||
query.data !== undefined &&
|
||||
data !== undefined &&
|
||||
!hasMetricData(metric, {
|
||||
summaries: query.data.summaries,
|
||||
summaries: data.summaries,
|
||||
// Sub-pages that go through this hook are sensor-backed only
|
||||
// (PULSE / WEIGHT / BMI / BLOOD_PRESSURE_SYS / SLEEP_DURATION).
|
||||
// Mood + medication run their own gate.
|
||||
@@ -79,7 +73,7 @@ export function useInsightsAnalytics(
|
||||
});
|
||||
|
||||
return {
|
||||
data: query.data,
|
||||
data,
|
||||
isLoading: query.isLoading,
|
||||
isEmpty,
|
||||
error: query.error as Error | null,
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
|
||||
/**
|
||||
* v1.4.33 IW9 — single source of truth for the "scroll to top on
|
||||
* route mount" affordance.
|
||||
*
|
||||
* The pre-v1.4.33 wiring had `<SubPageShell>` AND the insights mother
|
||||
* page each firing their own `requestAnimationFrame → scrollTo({top:0})`
|
||||
* on mount. Two consumers doing the same trick is the kind of
|
||||
* duplication that drifts; the second `scrollTo` lands ~16 ms after the
|
||||
* first and re-snaps the viewport once the chart skeleton has inflated.
|
||||
* On slow hydrates the user sees a visible "jump" — exactly the
|
||||
* complaint in `.planning/round-v1433-audit-polish.md` §2.1.
|
||||
*
|
||||
* This hook is the single owner of that reset. Both the shell and the
|
||||
* mother page call into it; the effect is mount-only and uses
|
||||
* `requestAnimationFrame` so the scroll lands after the first paint
|
||||
* (chart skeleton heights have settled, no double-snap).
|
||||
*
|
||||
* `behavior: "auto"` is intentional — `prefers-reduced-motion` users
|
||||
* get the same instant reset everyone else does. A smooth scroll on
|
||||
* a route change is itself a motion that some users opt out of.
|
||||
*/
|
||||
export function useScrollResetOnRoute() {
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined") return;
|
||||
const handle = window.requestAnimationFrame(() => {
|
||||
window.scrollTo({ top: 0, behavior: "auto" });
|
||||
});
|
||||
return () => window.cancelAnimationFrame(handle);
|
||||
}, []);
|
||||
}
|
||||
Reference in new issue
Block a user