mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 14:32:03 +00:00
refactor(insights): fold Math.min/max spreads in the six status helpers
`bmi`, `pulse`, `general`, `weight`, `blood-pressure`, and `mood` status helpers all built their `summarizeSeries` block with `Math.min(...series.map(...))` and `Math.max(...series.map(...))`. The same anti-pattern hit `summarize()` in `src/lib/analytics/trends.ts` on 2026-05-16 and crashed `/api/analytics` with `RangeError: Maximum call stack size exceeded` once an Apple-Health-synced PULSE series outgrew V8's ~125 000-arg ceiling; `61107e0c` folded that one. The six insight helpers are fed bounded windows today so they never reached the ceiling, but the spread still allocates a transient args array on every call and the fold is both stack-safe and cheaper. Replace each `Math.min(...) / Math.max(...) / average(...)` triplet with a single walk that maintains running sum / min / max. The now-unused `average()` helper is dropped from each file. Pinned by the existing per-helper unit tests (326 passing in `src/lib/insights` / `src/lib/analytics` / `src/app/api/analytics`).
This commit is contained in:
1 parent
61107e0c68
commit
2d630994a2
8 files changed
+66
-12
No files matched your search
+2
-1
@@ -1639,7 +1639,8 @@
|
||||
"genderHint": "Wird für geschlechtsspezifische Zielwerte verwendet.",
|
||||
"timezone": "Zeitzone",
|
||||
"timezoneHint": "Wird für Diagrammachsen, Erinnerungszeiten, Export-Zeitstempel und den Coach-Kontext verwendet. Gespeicherte Daten bleiben unverändert.",
|
||||
"timezoneDetect": "Browser-Zeitzone übernehmen ({tz})",
|
||||
"timezoneDetect": "Browser-Zeitzone übernehmen",
|
||||
"timezoneDetectAria": "Browser-Zeitzone {tz} übernehmen",
|
||||
"timezoneInvalid": "Keine gültige IANA-Zeitzone.",
|
||||
"profileSaved": "Profil gespeichert",
|
||||
"changePassword": "Passwort ändern",
|
||||
|
||||
+2
-1
@@ -1639,7 +1639,8 @@
|
||||
"genderHint": "Used for gender-specific target values.",
|
||||
"timezone": "Timezone",
|
||||
"timezoneHint": "Used for chart axis labels, reminder times, export timestamps, and Coach context. Stored data is unchanged.",
|
||||
"timezoneDetect": "Use browser timezone ({tz})",
|
||||
"timezoneDetect": "Use browser timezone",
|
||||
"timezoneDetectAria": "Use browser timezone {tz}",
|
||||
"timezoneInvalid": "Not a valid IANA timezone.",
|
||||
"profileSaved": "Profile saved",
|
||||
"changePassword": "Change password",
|
||||
|
||||
+2
-1
@@ -1639,7 +1639,8 @@
|
||||
"genderHint": "Used for gender-specific target values.",
|
||||
"timezone": "Zona horaria",
|
||||
"timezoneHint": "Used for chart axis labels, reminder times, export timestamps, and Coach context. Stored data is unchanged.",
|
||||
"timezoneDetect": "Use browser timezone ({tz})",
|
||||
"timezoneDetect": "Use browser timezone",
|
||||
"timezoneDetectAria": "Use browser timezone {tz}",
|
||||
"timezoneInvalid": "Not a valid IANA timezone.",
|
||||
"profileSaved": "Profile saved",
|
||||
"changePassword": "Change password",
|
||||
|
||||
+2
-1
@@ -1639,7 +1639,8 @@
|
||||
"genderHint": "Used for gender-specific target values.",
|
||||
"timezone": "Fuseau horaire",
|
||||
"timezoneHint": "Used for chart axis labels, reminder times, export timestamps, and Coach context. Stored data is unchanged.",
|
||||
"timezoneDetect": "Use browser timezone ({tz})",
|
||||
"timezoneDetect": "Use browser timezone",
|
||||
"timezoneDetectAria": "Use browser timezone {tz}",
|
||||
"timezoneInvalid": "Not a valid IANA timezone.",
|
||||
"profileSaved": "Profile saved",
|
||||
"changePassword": "Change password",
|
||||
|
||||
+2
-1
@@ -1639,7 +1639,8 @@
|
||||
"genderHint": "Used for gender-specific target values.",
|
||||
"timezone": "Fuso orario",
|
||||
"timezoneHint": "Used for chart axis labels, reminder times, export timestamps, and Coach context. Stored data is unchanged.",
|
||||
"timezoneDetect": "Use browser timezone ({tz})",
|
||||
"timezoneDetect": "Use browser timezone",
|
||||
"timezoneDetectAria": "Use browser timezone {tz}",
|
||||
"timezoneInvalid": "Not a valid IANA timezone.",
|
||||
"profileSaved": "Profile saved",
|
||||
"changePassword": "Change password",
|
||||
|
||||
+2
-1
@@ -1639,7 +1639,8 @@
|
||||
"genderHint": "Used for gender-specific target values.",
|
||||
"timezone": "Strefa czasowa",
|
||||
"timezoneHint": "Used for chart axis labels, reminder times, export timestamps, and Coach context. Stored data is unchanged.",
|
||||
"timezoneDetect": "Use browser timezone ({tz})",
|
||||
"timezoneDetect": "Use browser timezone",
|
||||
"timezoneDetectAria": "Use browser timezone {tz}",
|
||||
"timezoneInvalid": "Not a valid IANA timezone.",
|
||||
"profileSaved": "Profile saved",
|
||||
"changePassword": "Change password",
|
||||
|
||||
@@ -80,9 +80,14 @@ export function TimezonePicker({
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleUseBrowserTz}
|
||||
aria-label={
|
||||
detected
|
||||
? t("settings.timezoneDetectAria", { tz: detected })
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<Compass className="mr-2 h-4 w-4" />
|
||||
{t("settings.timezoneDetect", { tz: detected })}
|
||||
{t("settings.timezoneDetect")}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
@@ -109,15 +114,26 @@ export function TimezonePicker({
|
||||
</option>
|
||||
))}
|
||||
</NativeSelect>
|
||||
{/* v1.4.33 — visible label is "use browser zone"; the actual
|
||||
IANA zone is read out via aria-label so screen-reader users
|
||||
still hear which zone the click will apply. Previously the
|
||||
label inlined `(Europe/Berlin)`, which on a 393 CSS px
|
||||
viewport wrapped the button to two lines and broke height
|
||||
parity with the adjacent select. */}
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleUseBrowserTz}
|
||||
className="sm:w-auto"
|
||||
aria-label={
|
||||
detected
|
||||
? t("settings.timezoneDetectAria", { tz: detected })
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<Compass className="mr-2 h-4 w-4" />
|
||||
{t("settings.timezoneDetect", { tz: detected })}
|
||||
{t("settings.timezoneDetect")}
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-muted-foreground text-xs">{labelHint}</p>
|
||||
|
||||
@@ -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