mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 14:16:12 +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
@@ -4,6 +4,31 @@ import { Slot } from "radix-ui";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
/**
|
||||
* v1.4.33 IW9 — loading-state convention.
|
||||
*
|
||||
* Callers driving a transient loading state on a button must SWAP the
|
||||
* leading icon for `<Loader2 />` rather than APPENDING the spinner
|
||||
* next to the existing icon. The legacy `{loading && <Loader2/>}<Icon/>`
|
||||
* pattern grew the button by ~24 px during the request, which produced
|
||||
* the 0.186 CLS hit flagged on the v1.4.33 Lighthouse run. The
|
||||
* spinner and the icon are both 16 px (`h-4 w-4`); swapping keeps the
|
||||
* intrinsic button width stable.
|
||||
*
|
||||
* ```tsx
|
||||
* <Button disabled={loading} aria-busy={loading || undefined}>
|
||||
* {loading ? (
|
||||
* <Loader2 className="mr-2 h-4 w-4 animate-spin motion-reduce:animate-none" />
|
||||
* ) : (
|
||||
* <Icon className="mr-2 h-4 w-4" />
|
||||
* )}
|
||||
* {label}
|
||||
* </Button>
|
||||
* ```
|
||||
*
|
||||
* Also: pass `aria-busy={loading || undefined}` so screen readers
|
||||
* announce the in-flight state.
|
||||
*/
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||
{
|
||||
|
||||
@@ -2,12 +2,25 @@ import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
/**
|
||||
* v1.4.33 IW9 — Card padding normalised on `p-4 md:p-6`.
|
||||
*
|
||||
* Pre-v1.4.33 the shadcn defaults shipped `py-6` + `px-6` (uniform
|
||||
* 24 px on every breakpoint) while the dashboard tile-strip + chart
|
||||
* cards used `p-4 md:p-6` (16 px mobile, 24 px md+). Side-by-side on
|
||||
* `/insights` the correlation cards + daily-briefing read as "denser"
|
||||
* than the chart cards next to them; the visual reads as two card
|
||||
* languages on the same page. Matching the dashboard token here gives
|
||||
* the whole app a single card-padding contract.
|
||||
*
|
||||
* Refs `.planning/round-v1433-audit-polish.md` §4.1 + Win 4.
|
||||
*/
|
||||
function Card({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card"
|
||||
className={cn(
|
||||
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
|
||||
"bg-card text-card-foreground flex flex-col gap-4 rounded-xl border py-4 shadow-sm md:gap-6 md:py-6",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -20,7 +33,7 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
<div
|
||||
data-slot="card-header"
|
||||
className={cn(
|
||||
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
||||
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-4 has-data-[slot=card-action]:grid-cols-[1fr_auto] md:px-6 [.border-b]:pb-4 md:[.border-b]:pb-6",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -65,7 +78,7 @@ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-content"
|
||||
className={cn("px-6", className)}
|
||||
className={cn("px-4 md:px-6", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -75,7 +88,10 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-footer"
|
||||
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
|
||||
className={cn(
|
||||
"flex items-center px-4 md:px-6 [.border-t]:pt-4 md:[.border-t]:pt-6",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
|
||||
/**
|
||||
@@ -13,16 +14,47 @@ import { useTranslations } from "@/lib/i18n/context";
|
||||
* having to pull `locale` out of `useTranslations()` just to feed it
|
||||
* back into the same JSX prop, and guarantees a future date input
|
||||
* cannot silently regress by forgetting the hint.
|
||||
*
|
||||
* v1.4.33 — height parity with `<Input>` and `<NativeSelect>`.
|
||||
*
|
||||
* Mobile Safari + Android Chrome give `type="date"` (and
|
||||
* `type="datetime-local"`) an intrinsic chrome that puffs the
|
||||
* field past Tailwind's `h-10`, so the DOB cell on
|
||||
* `/settings/account` rendered ~6 px taller than its row siblings
|
||||
* and roughly 12 px wider on the Pixel-5 mobile-pass screenshots.
|
||||
* `appearance-none` switches the native widget off, then
|
||||
* `min-h-10` re-asserts the contract that every other input
|
||||
* primitive ships. The `:webkit-date-and-time-value` shadow rule
|
||||
* forces the embedded value text to occupy at least one full line
|
||||
* even when iOS shrinks its placeholder, so the field looks
|
||||
* identical on both engines.
|
||||
*/
|
||||
const DATE_INPUT_HEIGHT_CLASSES =
|
||||
"appearance-none min-h-10 h-10 [&::-webkit-date-and-time-value]:min-h-[1.5em]";
|
||||
|
||||
type InputProps = React.ComponentProps<typeof Input>;
|
||||
type DateInputProps = Omit<InputProps, "type" | "lang">;
|
||||
|
||||
export function DateInput(props: DateInputProps) {
|
||||
export function DateInput({ className, ...props }: DateInputProps) {
|
||||
const { locale } = useTranslations();
|
||||
return <Input type="date" lang={locale} {...props} />;
|
||||
return (
|
||||
<Input
|
||||
type="date"
|
||||
lang={locale}
|
||||
className={cn(DATE_INPUT_HEIGHT_CLASSES, className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function DateTimeInput(props: DateInputProps) {
|
||||
export function DateTimeInput({ className, ...props }: DateInputProps) {
|
||||
const { locale } = useTranslations();
|
||||
return <Input type="datetime-local" lang={locale} {...props} />;
|
||||
return (
|
||||
<Input
|
||||
type="datetime-local"
|
||||
lang={locale}
|
||||
className={cn(DATE_INPUT_HEIGHT_CLASSES, className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user