release/v1.11.3 — WHOOP body data, quality-of-life polish, fuller translations#254
Merged
Conversation
…the connection A 403 on a single WHOOP collection endpoint was classified reauth_required, which parked the whole connection at error_reauth and short-circuited every future sync. A lower-tier user missing one gated data class would brick the entire integration. Treat a per-resource collection 403 as a soft skip of that data class: log, return 0, and continue so sibling resources still sync. Reserve connection-wide reauth for a 401 (token rejected) and for a 403 on the token-refresh / profile path, which run outside the per-resource catch blocks and remain unchanged. Add isCollectionForbidden as the shared gate and cover both branches in tests.
The body-measurement endpoint was fetched by a function nobody called, so WHOOP weight, max heart rate, and height never reached the database despite being documented in the mapping table. Add sync-body.ts and wire it into the per-user sync loop (and, through it, the backfill): - weight_kilogram → a WEIGHT Measurement (source = WHOOP) on the stable externalId whoop:body:weight with overwrite semantics. A body measurement is a single self-reported profile value, not a time series, so a re-sync updates the same row in place rather than accumulating one row per poll. measuredAt is the fetch time; the source-priority picker ranks a real scale above WHOOP. - max_heart_rate → WhoopConnection.maxHeartRate (a profile constant). - height_meter → User.heightCm (m→cm), written ONLY when the user has no height yet — a user-set height is never overwritten and height is never minted as a Measurement. Every write is field-by-field and idempotent across reruns. Document the body mapping and the watch-only blood-pressure stance in mapping.md.
The insights.rhythmEvents event labels and verdicts shipped verbatim English in the four non-reference locales. Translate the 11 affected keys so the device-health-notifications surface reads in the user's language.
Large stretches of the Settings surface — account, passkeys, notification channels, AI provider configuration, Withings, API tokens, export, and the danger zone — rendered verbatim English in the four non-reference locales. Translate the genuine prose and labels, leaving brand names, units, and protocol tokens as-is.
Schedule, wizard, intake, and detail-page copy in the medication surface rendered verbatim English for the four non-reference locales. Translate the wizard steps, cadence options, intake history, reminder controls, and danger-zone prose, leaving units, ATC/RxNorm codes, and GLP-1 INN drug names in their language-appropriate form.
The achievements namespace — badge titles, descriptions, category labels, and dashboard card copy — rendered verbatim English outside German. Translate the surface for the four remaining locales, keeping the format-token strings and the Engagement category as-is.
A failed take/skip POST cleared the spinner without any feedback, so the user believed the dose was logged when it was not. Show an error toast on a non-ok response or a thrown request, and never show the success confirmation in that case. On a successful take/skip, attach an Undo action to the toast that soft-deletes the just-recorded intake via the per-event delete route, so a misclick no longer needs a trip through the intake history to correct.
The intake-edit dialog already tracks a busy state and sets aria-busy, but the Save button gave no visual feedback while the PUT was in flight, unlike the wizard and quick-add. Render the same inline spinner during the mutation.
The bare centred spinner reserved a 32-unit-tall box that the resolved card grid then displaced, jumping the layout. Render a grid of card skeletons built on the real Card shell + shared Skeleton primitive so the loading state matches the loaded footprint.
…arget The first / last jump buttons rendered at 32px, below the minimum touch target. Extend the hit area with an invisible inset pseudo-element so the tappable region is 44px while the visual button stays 32px; the existing aria-labels are unchanged.
While a reply streams the composer now swaps the send button for a visible Stop control bound to the abort handler, so a long or off-track reply can be interrupted instead of waited out. Closing the drawer mid-stream also calls the cancel handler, so the SSE request no longer keeps running in the background after the drawer is gone. Add a localised "Stop" label across all six bundles.
The snapshot builder ran its independent reads sequentially on a cache miss: feature extraction, the shared measurement query, the mood, compliance, sleep and workout table reads, and the GLP-1, derived, trajectory and memory helper blocks each awaited in turn. None consumes another's result, so they now fire concurrently and resolve in batched hops — feature extraction and the measurement read first (both gate the per-metric blocks), then the remaining reads in one Promise.all. The source guards are unchanged, so a disabled source still issues no query, and the synchronous block assembly keeps its original order, so the provenance envelope is byte-for-byte the same. Cuts several round-trips off the cold build; the warm path is cached and unaffected.
…y state Lift the disclaimer and feedback-thanks lines off the off-scale 10px size onto the standard extra-small step. Unify the user and assistant row gap, and budget the avatar column out of the bubble's 80% cap so a bubble plus its avatar never overflow a comfortable width on a narrow phone. Announce the empty-thread hero as a polite live region so screen readers read the hint when the thread first mounts.
…motion The recommendation cards carry `transition-all md:hover:-translate-y-0.5` for a subtle lift on pointer hover. Motion-sensitive users got the full transform with no opt-out. Pair the hover with `motion-reduce:transition-none motion-reduce:hover:translate-y-0` so the card stays put when the user asks for less motion.
The 400 ms `.animate-insight-in` entrance keyframe is applied across five insight surfaces and is collapsed to `animation: none` once, centrally, in a `@media (prefers-reduced-motion: reduce)` block in globals.css. Extend the motion-reduce coverage test to assert that central guard so a future edit cannot silently drop it and replay the entrance motion for motion-sensitive users.
…anchor the warm-assessments control The six `next/dynamic` loaders on the insights overview each rendered a bespoke `<div className="… h-[Xrem] animate-pulse …" />` with a guessed fixed height. Those heights pinned each placeholder taller or shorter than the resolved block, so the page shifted as each chunk landed. Route every loader through one shared `BlockSkeleton` built on the `Skeleton` primitive (which already honours reduced motion) and hold the row open with a `min-h` floor rather than a hard-coded pixel guess, removing the resolve-time layout shift. Decorative placeholders for the cards that can un-mount stay `aria-hidden`. Anchor the warm-assessments control too: it floated right-aligned with no label, reading as a context-less affordance. Pair it with a left-aligned caption that explains the nightly-refresh model so the button has a home.
The thumb-up / thumb-down POST used to fail silently — on error the buttons just reappeared with no signal that the submission had not gone through. Add a lightweight localized error toast on failure while keeping the success path quiet (the inline confirmation row is signal enough there).
…d a larger target The revoke icon button announced only "button" to screen readers and sat at a 32px target. Add a localized aria-label and bump it to 36px.
…urfaces The mobile passkey list rows used a tighter p-3 while every other card surface in the account section uses the standard padding step. Bump to p-4 so nested card surfaces read consistently; radius stays rounded-lg.
The export cards rendered the raw "Download failed (500)" error string to the user. Map the failure to a localized message across all six locales.
… sheet An accidental overlay tap, Escape, or mobile swipe-down on a quick-entry sheet silently dropped any typed measurement, mood, or intake. Detect unsaved input at dismiss time and ask before closing; an explicit Cancel or a successful save still closes straight away.
The tile-strip skeleton and per-tile Suspense fallback reserved only 6rem, shorter than a populated TrendCard (~8rem), so the strip grew when data landed. Bump both to 8rem and mirror the card's overflow-hidden chrome on the fallback to keep first-paint layout shift at zero.
…check A freshly-opened quick-entry sheet prefills its datetime-local field with the current time, so the dismiss-time dirty check read every pristine sheet as unsaved and always raised the discard confirm. Exclude date/time picker types from the scan; typed text and notes still count.
Extract handleCollectionFetchError so the five resource syncs share one soft-skip-vs-rethrow path instead of five copies. Behaviour is unchanged: a per-resource 403 returns 0 and keeps the connection connected, anything else records a classified failure and rethrows.
WHOOP body fields are self-reported profile data; guard mapBody against absent, NaN/Infinity, or non-positive values so a garbage reading never seeds a WEIGHT measurement or a profile height of 0.
…ller translations
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Patch release. Two WHOOP integration fixes plus a broad quality-of-life, accessibility, and translation pass across Dashboard, Insights, Coach, Medications, and Settings.
Highlights
fetchBodyMeasurementwas defined but never called.Verification
pnpm typecheckclean,pnpm lintclean (one pre-existing allowed warning),pnpm test666 files / 6932 passed / 1 skipped.pnpm openapi:checkin sync (version 1.11.3).No schema migration. Additive over v1.11.2.