Two independent every-load costs, one per layer:
1. The static EN import in load-locale.ts put the full en.json catalog
(334 KB raw / ~106 KB gz) into the client JS of EVERY route, for
every user in every language. No catalog is statically imported by
client code anymore: the EN fallback floor of the t() chain loads
lazily on the first actual key miss (the locale-integrity guard
keeps every locale key-complete, so the miss path is drift-only),
and the labs biomarker canonical-name lookup fetches EN on demand.
2. The root layout serialized the ENTIRE active-locale catalog into the
RSC flight payload of every document (392 KB of a 505 KB dashboard
HTML, re-downloaded on every hard load, uncacheable). The handoff is
now a versioned, immutable-cacheable boot script
(/i18n/<locale>?v=<build>, new route handler) that assigns
self.__HL_I18N before the hydration scripts run, plus a server-side
cache seed in load-locale.ts behind a typeof-window branch that
Turbopack compiles out of the browser bundle (verified: zero catalog
bytes in any client chunk, zero route manifests referencing one).
SSR text stays correct and the first client render holds the same
bundle the server rendered with — no EN flash, no hydration drift.
The boot script is public (proxy allowlist: the login page needs it
pre-auth; the body is the same catalog JSON that ships in the public
repo) and cache-first in the service worker, so an offline relaunch
finds it next to the cached shell.
Measured (client-reference manifests, gzip): route JS baseline
/ 391->287 KB gz, /insights 377->273, /measurements 379->275,
/insights/mood 394->290 — the ~104 KB gz EN catalog left every route.
H2
Add a Google Health connection that reads Fitbit, Pixel Watch, and Fitbit
Air data through the user's Google account, alongside the existing Fitbit
connection, as the path forward before the classic Fitbit Web API sunsets
in September 2026.
Restore the daily briefing when it cites your own vitals (the number-grounding
gate stripped it on any blood-pressure, pulse, weight, mood or sleep figure it
did not recognise), surface a medication's dose as a tag beside its category,
let the custom on-time window switch stay on, and drop the layout-shifting
back-link from the Appearance sub-pages.
A native date input keeps its intrinsic width on mobile WebKit and won't
shrink, so the `date + fixed-width time` row overran a phone-width sheet —
the time field and its clock icon spilled past the screen edge and every
add form (measurements, mood, medication, …) scrolled sideways. Stack the
two halves on narrow viewports and only place them side-by-side from the
`sm` breakpoint up; each half then takes the full sheet width and nothing
overflows.
Renumber the custom-metrics migration to 0223 (the next free slot on the
trunk) and cut the release: version bump, regenerated OpenAPI contract,
service-worker stamp, and CHANGELOG entry.
The network operator (carrier) used to resolve only from the optional
offline GeoLite2-ASN database. On a host without it the carrier stayed
empty even though the location resolved fine online.
Mine the operator from the same online provider response the location
lookup already fetches — ip-api's top-level `isp`/`org`/`as`, ipwho.is's
nested `connection.{isp,org,asn}` — so the carrier is populated out of the
box. The offline ASN database stays authoritative when configured.
A single `lookupIpGeo` now returns location + ASN + carrier in one pass;
the audit write and the backfill both go through it. The backfill also
revisits rows that already had a location but no carrier, so the column
fills in across existing history, not just for new sign-ins.
In the admin sign-in overview the carrier moves out of a chip stacked
under the login method into its own column, next to the location.
A self-hoster can set IP_GEO_ALLOW_INSECURE=true alongside an http:// URL to
use a free HTTP-only provider — the free ip-api.com endpoint is HTTP-only (its
HTTPS form needs a paid key) but its geolocation is often more accurate and it
carries the ISP. The default stays HTTPS-only with ipwho.is; plain HTTP is
refused unless the operator explicitly opts in, since the looked-up IP then
travels unencrypted over the server's own egress. Whitelisted in compose and
documented in the env example.
The native datetime-local / time picker renders its clock and AM/PM
toggle by the browser UI language, not the page lang or the app's
time-format preference — so an English-language browser showed AM/PM
even with 24-hour selected. Replace the native time control with an
app-controlled TimeField (text entry + a dependency-free popover of
hour/minute, plus AM/PM in 12-hour mode) whose hour cycle always tracks
the preference. DateTimeField now composes the native calendar (date)
with TimeField (time); the committed value stays a local
yyyy-MM-ddTHH:mm string, and min/max clamp the recombined value.
Also thread the time-format preference into the doctor-report PDF so its
generated-at timestamp honours it instead of the locale default.
- Reword two LOINC verification comments in the signals registry so they
no longer trip the forbidden-marker grep, keeping the meaning intact.
- Resolve the dead-code gate: gate the seeded-question opener on the
per-user ambient-suggestions opt-out (wiring the previously orphaned
resolveCoachAmbientSuggestionsEnabled, server-authoritative), drop the
unread decryptDocumentFromBytes helper and its now-unused import, and
remove the unused SignalKind type alias.
- Delegate undici's fetch to the global stub in the provider-chain
integration test so the egress-pinned client HTTP is mocked again.
- Defer the JSON example-download cleanup a tick so revoking the blob URL
no longer races the browser download start (stabilises the e2e).
- Sync the service-worker version fallback to v1.25.0.
Adds the Coach goal / if-then-plan memory model (propose-then-confirm,
encrypted, migration 0194) and a standing Coach quality harness (graded
golden set + red-team in CI, optional nightly model judge off by default).
Caches the dashboard snapshot per language. No breaking changes.
Hotfix: the Coach and daily briefing failed for ChatGPT-provider accounts —
a provider tool-call rejection surfaced as a 500. Provider failures now
degrade gracefully, the Codex tool-call wire format is corrected, and the
provider error body is captured for diagnosis. Also: the thinking bubble
shows only the animated dots.
Surfaces the Coach's existing intelligence as ambient presence: a per-metric
"Coach read", a visible scope handoff and a seeded opener, briefing callbacks,
a tension verdict and return-to-baseline note on the score, two motivational-
interviewing moves, and grounding parity on the local model path. No
migrations, no breaking changes.
Patch release. Pins the action row in the centred dialog so the primary
button stays reachable without scrolling under long forms, browser zoom, or
display scaling. No migrations, no breaking changes.
A user reported the dashboard crashing on returning to a backgrounded
tab. The on-focus snapshot refetch re-rendered the page, and each chart
received a fresh inline `() => markChartReady(id)` handler every render;
the chart's data-ready notify effect, keyed on that ever-changing prop,
re-fired on every commit. That per-commit passive effect kept the chart
row's anchored popovers re-committing until the browser tripped its
render-depth guard and the page fell back to its error card.
Latch the notify behind a ref so it fires once, when the chart's initial
query settles, and depend on the load flag alone. The handler is read
through a ref so a late-arriving callback still fires without re-arming
the effect. Extract the pure fire-once decision into a small helper with
direct unit coverage (no DOM runner, per project convention).
Fix encrypted day-log data loss and the stalled cumulative-measurement drain;
capture Withings sleep vitals and Oura VO2max; harden AI grounding across the
briefing, Coach, narrative, and status cards; full-page Coach with history and a
working mic; on-device lab OCR; preventive-care, labs, and settings polish; BP
warm/cold parity, a statement timeout, and a covering measurements index.
Coach new-chat surface with streamed answers and a token footer; lab-report OCR
ingestion with a mandatory per-row review; qualitative lab results; labs list
default, sort, and delete; VO2max on the dashboard with Oura ingest; preventive-
care date and due-status polish; faster condition journal; honest report
staleness; dashboard freshness on background sync.
One-shot prettier pass so the new format:check gate starts from a clean
baseline. Formatting only — no behaviour change. Generator-owned and
vendored artifacts stay untouched via .prettierignore.