mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 22:32:04 +00:00
fix(ux): honest deletes, stable hydration and tidy numbers after release review
Server and first client render now agree everywhere query state used to leak into the chrome, checkboxes no longer nest inside buttons, delete confirmations admit the undo they offer, raw step counts drop their decimals, the truncated unit label wraps instead, streaks know their singular, the medication header speaks the user's date format, a failed coach turn keeps the user's message and says what went wrong, settings status notes follow a language switch, the locale only flips once its bundle actually loaded, and the last raw api fetches in hooks move onto the shared client.
This commit is contained in:
1 parent
25f41f88fe
commit
a8149e0bdf
35 files changed
+347
-191
No files matched your search
@@ -104,8 +104,11 @@ describe("v1.4.43 W11 — dashboard tile-strip skeleton (wiring)", () => {
|
||||
|
||||
it("derives `primaryLoading` from the snapshot vs slim query", () => {
|
||||
const src = readFileSync(PAGE_PATH, "utf8");
|
||||
// v1.16.4 — `!mounted` pins SSR + the hydration render to the
|
||||
// skeleton branch so a late-hydrating boundary can't disagree with
|
||||
// the server HTML (React #418); see `useMounted`.
|
||||
expect(src).toMatch(
|
||||
/const\s+primaryLoading\s*=\s*snapshotEnabled\s*\?\s*snapshotQuery\.isLoading\s*:\s*analyticsSlimQuery\.isLoading;/,
|
||||
/const\s+primaryLoading\s*=\s*!mounted\s*\|\|\s*\(snapshotEnabled\s*\?\s*snapshotQuery\.isLoading\s*:\s*analyticsSlimQuery\.isLoading\);/,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -218,7 +218,9 @@ export default function InsightsMedikamentePage() {
|
||||
<span>{categoryLabel}</span>
|
||||
{med.streak > 0 ? (
|
||||
<Badge variant="outline" className="ml-auto shrink-0 text-xs">
|
||||
{t("insights.dayStreak", { count: med.streak })}
|
||||
{med.streak === 1
|
||||
? t("insights.dayStreakOne", { count: med.streak })
|
||||
: t("insights.dayStreak", { count: med.streak })}
|
||||
</Badge>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { useMounted } from "@/hooks/use-mounted";
|
||||
import { usePullToRefresh } from "@/hooks/use-pull-to-refresh";
|
||||
import {
|
||||
MeasurementForm,
|
||||
@@ -18,6 +19,7 @@ import { useTranslations } from "@/lib/i18n/context";
|
||||
|
||||
export default function MeasurementsPage() {
|
||||
const { isAuthenticated, isLoading } = useAuth();
|
||||
const mounted = useMounted();
|
||||
const router = useRouter();
|
||||
const queryClient = useQueryClient();
|
||||
const searchParams = useSearchParams();
|
||||
@@ -73,7 +75,10 @@ export default function MeasurementsPage() {
|
||||
}
|
||||
}, [isLoading, isAuthenticated, router]);
|
||||
|
||||
if (isLoading) {
|
||||
// v1.16.4 — `!mounted` keeps the hydration render identical to the
|
||||
// SSR HTML when this boundary hydrates after `/api/auth/me` settled
|
||||
// (React #418 family); see `useMounted`.
|
||||
if (!mounted || isLoading) {
|
||||
return (
|
||||
<div className="flex h-64 items-center justify-center">
|
||||
<Loader2 className="text-primary h-8 w-8 animate-spin motion-reduce:animate-none" />
|
||||
|
||||
+13
-3
@@ -3,6 +3,7 @@
|
||||
import React, { Suspense, useMemo, useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { useMounted } from "@/hooks/use-mounted";
|
||||
import {
|
||||
Activity,
|
||||
Droplet,
|
||||
@@ -128,6 +129,7 @@ export function resolveDashboardFirstPaintGate(input: {
|
||||
|
||||
export default function DashboardPage() {
|
||||
const { isAuthenticated, user } = useAuth();
|
||||
const mounted = useMounted();
|
||||
const { t } = useTranslations();
|
||||
const fmt = useFormatters();
|
||||
const [quickEntryDialog, setQuickEntryDialog] =
|
||||
@@ -1310,9 +1312,17 @@ export default function DashboardPage() {
|
||||
// never fires under snapshot mode, so the empty-state branch
|
||||
// below flashes for the whole snapshot fetch. Key the loading
|
||||
// flag off whichever query is actually driving the tiles.
|
||||
const primaryLoading = snapshotEnabled
|
||||
? snapshotQuery.isLoading
|
||||
: analyticsSlimQuery.isLoading;
|
||||
// v1.16.4 — `!mounted` pins the SSR pass AND the hydration
|
||||
// render to the skeleton branch. Server-side the data queries
|
||||
// are `enabled: false` (no auth) → `isLoading: false`, which
|
||||
// used to select the empty state; a late-hydrating boundary
|
||||
// then saw `isAuthenticated` already resolved and rendered the
|
||||
// skeleton instead — React #418. See `useMounted`.
|
||||
const primaryLoading =
|
||||
!mounted ||
|
||||
(snapshotEnabled
|
||||
? snapshotQuery.isLoading
|
||||
: analyticsSlimQuery.isLoading);
|
||||
const { showTileStripSkeleton, showEmptyState } =
|
||||
resolveDashboardFirstPaintGate({
|
||||
trendCardCount: trendCards.length,
|
||||
|
||||
Reference in new issue
Block a user