From 3b480b9885a719284a2f5815a5dedb14dbf4ec4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 11 Jun 2026 02:19:27 +0200 Subject: [PATCH] perf(i18n,shell): ship one locale and stop blocking the first paint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The client bundle carries only the active locale — resolved on the server and handed to the provider with no hydration flash, halving the dashboard route to under a megabyte parsed (288 KiB gzip). The auth shell renders the app chrome immediately instead of a spinner while the session resolves in parallel, the widgets request folds into the snapshot it duplicated, and insight pills prefetch their sub-pages — a first visit to a sub-page lands in about ninety milliseconds instead of nearly a second. --- docs/api/openapi.yaml | 66 + docs/ui-guidelines.md | 35 +- messages/de.json | 13 + messages/en.json | 13 + messages/es.json | 13 + messages/fr.json | 13 + messages/it.json | 13 + messages/pl.json | 13 + .../dashboard-analytics-split.test.ts | 2 +- .../admin/audit-log/__tests__/route.test.ts | 8 +- .../__tests__/route.test.ts | 2 +- .../notifications/reminder-check/route.ts | 2 +- .../assistant-flags/__tests__/route.test.ts | 2 +- .../admin/users/[id]/__tests__/route.test.ts | 6 +- .../auth/check-user/__tests__/route.test.ts | 8 +- .../auth/login/__tests__/native-token.test.ts | 6 +- .../me/devices/[id]/__tests__/route.test.ts | 2 +- .../auth/me/devices/__tests__/route.test.ts | 2 +- .../me/disable-coach/__tests__/route.test.ts | 2 +- .../__tests__/route.test.ts | 6 +- .../__tests__/route.test.ts | 2 +- .../me/research-mode/__tests__/route.test.ts | 2 +- .../source-priority/__tests__/route.test.ts | 2 +- .../auth/me/timezone/__tests__/route.test.ts | 2 +- .../unit-preference/__tests__/route.test.ts | 2 +- .../__tests__/native-token.test.ts | 4 +- .../api/consent/ai/__tests__/route.test.ts | 2 +- .../consent/ai/latest/__tests__/route.test.ts | 2 +- .../dashboard/summary/__tests__/route.test.ts | 4 +- .../api/devices/[id]/__tests__/route.test.ts | 2 +- src/app/api/devices/__tests__/route.test.ts | 2 +- .../doctor-report/__tests__/pdf-route.test.ts | 2 +- .../api/doctor-report/availability/route.ts | 2 +- .../api/feature-flags/__tests__/route.test.ts | 2 +- .../__tests__/hidden-redaction.test.ts | 2 +- .../achievements/__tests__/ios-format.test.ts | 2 +- .../[jobId]/status/__tests__/route.test.ts | 4 +- .../__tests__/route.test.ts | 2 +- .../__tests__/route.test.ts | 2 +- .../insights/cards/__tests__/route.test.ts | 2 +- .../comprehensive/__tests__/route.test.ts | 2 +- .../correlations/__tests__/route.test.ts | 2 +- src/app/api/insights/correlations/route.ts | 4 +- .../insights/derived/__tests__/route.test.ts | 2 +- .../derived/batch/__tests__/route.test.ts | 2 +- .../insights/generate/__tests__/route.test.ts | 2 +- .../metric-status/__tests__/route.test.ts | 2 +- .../pregenerate/__tests__/route.test.ts | 2 +- .../rhythm-events/__tests__/route.test.ts | 2 +- .../insights/targets/__tests__/route.test.ts | 2 +- .../healthkit/__tests__/route.test.ts | 2 +- .../__tests__/route.test.ts | 2 +- .../__tests__/put-duplicate-timestamp.test.ts | 2 +- .../__tests__/range-aggregation-route.test.ts | 2 +- .../batch/__tests__/pr-detection-hook.test.ts | 2 +- .../restore/__tests__/route.test.ts | 177 ++ src/app/api/measurements/restore/route.ts | 144 ++ src/app/api/measurements/route.ts | 4 +- .../series/__tests__/route.test.ts | 2 +- .../[id]/cadence/__tests__/route.test.ts | 2 +- .../[id]/compliance/__tests__/route.test.ts | 4 +- .../[id]/dose-history/__tests__/route.test.ts | 4 +- .../[id]/glp1/__tests__/route.test.ts | 2 +- .../intake/[eventId]/__tests__/route.test.ts | 2 +- .../[id]/intake/__tests__/route.test.ts | 2 +- .../bulk-delete/__tests__/route.test.ts | 2 +- .../[id]/inventory/__tests__/route.test.ts | 2 +- src/app/api/medications/[id]/route.ts | 2 +- .../[id]/side-effects/[logId]/route.ts | 2 +- .../[id]/side-effects/__tests__/route.test.ts | 2 +- .../api/medications/__tests__/route.test.ts | 2 +- .../intake/__tests__/route.test.ts | 2 +- src/app/api/medications/intake/route.ts | 2 +- src/app/api/medications/route.ts | 2 +- .../restore/__tests__/route.test.ts | 161 ++ src/app/api/mood-entries/restore/route.ts | 133 ++ .../mood/analytics/__tests__/route.test.ts | 2 +- .../status/__tests__/route.test.ts | 2 +- src/app/api/onboarding/__tests__/step.test.ts | 2 +- .../personal-records/__tests__/route.test.ts | 2 +- .../api/sleep/night/__tests__/route.test.ts | 2 +- .../api/user/profile/__tests__/route.test.ts | 16 +- .../workouts/__tests__/batch-create.test.ts | 2 +- .../__tests__/canonical-dedup.test.ts | 2 +- src/app/api/workouts/__tests__/detail.test.ts | 2 +- src/app/auth/login/page.tsx | 78 +- src/app/auth/register/page.tsx | 32 +- src/app/bugreport/page.tsx | 56 +- src/app/insights/medications/page.tsx | 27 +- src/app/insights/mood/page.tsx | 6 +- src/app/insights/page.tsx | 12 +- src/app/layout.tsx | 10 +- src/app/medications/[id]/page.tsx | 5 +- src/app/medications/page.tsx | 6 +- src/app/notifications/page.tsx | 14 +- src/app/onboarding/[step]/page.tsx | 2 +- src/app/page.tsx | 19 +- src/app/privacy/__tests__/page.test.tsx | 9 +- .../__tests__/ai-quality-section.test.tsx | 2 +- .../__tests__/api-token-no-collapse.test.tsx | 4 +- .../api-token-overview-responsive.test.tsx | 8 +- .../__tests__/api-token-truncate.test.tsx | 8 +- .../app-log-preview-summary-position.test.tsx | 4 +- .../__tests__/backups-section-empty.test.tsx | 2 +- .../__tests__/coach-feedback-section.test.tsx | 2 +- .../login-overview-auth-filter.test.tsx | 4 +- .../__tests__/login-overview-csv.test.ts | 24 +- .../login-overview-no-collapse.test.tsx | 4 +- ...ogin-overview-pagination-position.test.tsx | 6 +- .../login-overview-provider.test.tsx | 2 +- .../admin/__tests__/sections.test.tsx | 6 +- .../admin/__tests__/status-card-grid.test.tsx | 8 +- .../__tests__/user-management-empty.test.tsx | 4 +- .../user-management-responsive.test.tsx | 8 +- src/components/admin/_shared.tsx | 24 +- src/components/admin/ai-quality-section.tsx | 14 +- .../admin/api-token-overview-section.tsx | 7 +- .../admin/app-log-preview-section.tsx | 5 +- src/components/admin/assistant-section.tsx | 17 +- src/components/admin/backups-section.tsx | 45 +- .../admin/coach-feedback-section.tsx | 16 +- src/components/admin/danger-zone-section.tsx | 12 +- .../admin/feedback-inbox-section.tsx | 26 +- src/components/admin/glitchtip-section.tsx | 14 +- src/components/admin/host-metrics-chart.tsx | 5 +- .../admin/invite-tokens-section.tsx | 36 +- .../admin/login-overview-section.tsx | 9 +- src/components/admin/recent-audit-preview.tsx | 7 +- src/components/admin/reminders-section.tsx | 85 +- src/components/admin/umami-section.tsx | 14 +- .../admin/user-management-section.tsx | 61 +- src/components/admin/version-tile-section.tsx | 11 +- src/components/app-settings-provider.tsx | 6 +- .../health-chart-empty-state-gate.test.tsx | 6 +- .../health-chart-no-data-in-range.test.tsx | 6 +- .../health-chart-overlay-defaults.test.tsx | 2 +- ...compliance-chart-empty-state-gate.test.tsx | 8 +- .../mood-chart-empty-state-gate.test.tsx | 8 +- .../__tests__/mood-chart-polish.test.tsx | 4 +- .../trend-card-baseline-alignment.test.tsx | 2 +- .../charts/chart-overlay-controls.tsx | 2 +- src/components/charts/health-chart.tsx | 33 +- .../charts/medication-compliance-chart.tsx | 8 +- src/components/charts/mood-chart.tsx | 6 +- src/components/charts/trend-card.tsx | 2 +- src/components/comparison/compare-toggle.tsx | 14 +- src/components/cycle/log-day-sheet.tsx | 55 +- src/components/cycle/use-cycle.ts | 137 +- .../medication-intake-quick-add.test.tsx | 4 +- .../dashboard/medication-intake-quick-add.tsx | 39 +- .../doctor-report/doctor-report-dialog.tsx | 37 +- .../__tests__/health-score-card.test.tsx | 2 +- .../__tests__/coach-input.test.tsx | 2 +- .../__tests__/message-thread.test.tsx | 4 +- .../insights/coach-panel/message-thread.tsx | 31 +- .../coach-panel/self-context-adopt-offer.tsx | 9 +- .../coach-panel/self-context-chips.tsx | 19 +- .../insights/coach-panel/sources-rail.tsx | 8 +- .../insights/coach-panel/use-coach.ts | 32 +- .../insights/derived/use-derived-metric.ts | 18 +- src/components/insights/health-score-card.tsx | 2 +- .../insights/insights-edit-mode.tsx | 15 +- .../insights/insights-layout-shell.tsx | 6 +- .../insights/insights-tab-strip.tsx | 12 +- src/components/insights/layout-coach-fab.tsx | 5 +- .../insights/measurement-diversity-nudge.tsx | 17 +- .../insights/metric-target-summary.tsx | 6 +- .../insights/mood/mood-insights-sections.tsx | 6 +- .../insights/mood/mood-what-stands-out.tsx | 9 +- .../insights/period-narrative-card.tsx | 7 +- .../insights/personal-record-badge.tsx | 15 +- .../insights/recommendation-feedback.tsx | 28 +- .../insights/rhythm-events-card.tsx | 12 +- src/components/insights/sleep-hypnogram.tsx | 2 +- src/components/insights/sleep-overview.tsx | 7 +- .../insights/sleep-stage-stacked-bar.tsx | 2 +- src/components/insights/trend-annotation.tsx | 14 +- .../insights/use-insights-advisor.ts | 8 +- .../layout/__tests__/sidebar-nav.test.tsx | 4 +- src/components/layout/auth-shell.tsx | 43 +- src/components/layout/sidebar-nav.tsx | 1 - ...asurement-list-apple-health-badge.test.tsx | 2 +- .../measurement-list-bp-badge.test.tsx | 2 +- .../__tests__/measurement-list-empty.test.tsx | 2 +- .../measurement-list-filter-select.test.tsx | 2 +- .../measurement-list-locked-type.test.tsx | 2 +- .../measurement-list-step-grouping.test.tsx | 2 +- .../measurements/measurement-form.tsx | 45 +- .../measurements/measurement-list.tsx | 119 +- src/components/medications/DrugLevelChart.tsx | 25 +- .../ResearchModeAcknowledgmentDialog.tsx | 23 +- .../medications/SideEffectsSection.tsx | 29 +- .../ResearchModeAcknowledgmentDialog.test.tsx | 4 +- .../medications/__tests__/card-parts.test.tsx | 4 +- .../__tests__/intake-history-list-v2.test.tsx | 4 +- .../__tests__/use-medication-intake.test.ts | 30 +- .../card-parts/medication-card-body.tsx | 2 +- .../detail/MedicationDetailTabs.tsx | 51 +- .../medications/dose-history-add-dialog.tsx | 153 +- .../medications/dose-history-ledger.tsx | 89 +- .../medications/dose-strength-curve.tsx | 12 +- .../medications/glp1-medication-card.tsx | 49 +- .../medications/intake-edit-dialog.tsx | 147 +- .../medications/intake-history-editable.tsx | 24 +- .../medications/intake-history-list-v2.tsx | 9 +- .../medications/intake-import-dialog.tsx | 24 +- .../medications/log-intake-dialog.tsx | 14 +- .../medications/medication-card.tsx | 38 +- .../scheduling/DoseWindowEditor.tsx | 2 +- .../scheduling/ScheduleTimesEditor.tsx | 11 +- .../medications/scheduling/dose-window.ts | 2 +- .../medications/sections/api-tokens-row.tsx | 47 +- .../sections/destructive-zone-section.tsx | 39 +- .../sections/inventory-section.tsx | 38 +- .../sections/notifications-section.tsx | 20 +- .../sections/phase-config-sheet.tsx | 31 +- .../medications/sections/settings-section.tsx | 25 +- .../medications/use-medication-intake.ts | 63 +- .../medications/wizard/steps/Step2Class.tsx | 2 +- .../medications/wizard/steps/Step8Summary.tsx | 9 +- .../medications/wizard/wizard-payload.ts | 2 +- src/components/monitoring/bootstrap.tsx | 12 +- .../monitoring/glitchtip-reporter.tsx | 15 +- .../mood-list-filter-select.test.tsx | 2 +- src/components/mood/mood-form.tsx | 36 +- src/components/mood/mood-list.tsx | 73 +- src/components/mood/mood-tag-picker.tsx | 6 +- src/components/onboarding/BaselineForm.tsx | 16 +- src/components/onboarding/GoalsChipPicker.tsx | 9 +- src/components/onboarding/SourceCardGrid.tsx | 9 +- src/components/onboarding/WelcomeCarousel.tsx | 9 +- .../onboarding/getting-started-checklist.tsx | 18 +- src/components/onboarding/tour-launcher.tsx | 7 +- src/components/providers.tsx | 8 +- .../settings/__tests__/ai-section.test.tsx | 2 +- .../__tests__/api-section-responsive.test.tsx | 4 +- .../__tests__/disable-coach-card.test.tsx | 4 +- .../__tests__/integrations-section.test.tsx | 2 +- .../settings/__tests__/sections.test.tsx | 6 +- src/components/settings/about-me-section.tsx | 17 +- src/components/settings/about-section.tsx | 8 +- src/components/settings/account-section.tsx | 36 +- src/components/settings/advanced-section.tsx | 9 +- src/components/settings/ai-section.tsx | 1973 +---------------- .../ai/admin-openai-provider-form.tsx | 27 + .../settings/ai/ai-insights-card.tsx | 311 +++ .../settings/ai/anthropic-provider-form.tsx | 171 ++ .../settings/ai/codex-provider-form.tsx | 298 +++ .../settings/ai/disable-coach-card.tsx | 157 ++ .../settings/ai/fallback-chain-card.tsx | 320 +++ .../settings/ai/local-provider-form.tsx | 172 ++ .../settings/ai/openai-provider-form.tsx | 208 ++ .../settings/ai/runtime-actions-row.tsx | 274 +++ src/components/settings/ai/shared.ts | 142 ++ src/components/settings/api-section.tsx | 11 +- .../settings/coach-memory-section.tsx | 31 +- src/components/settings/coach-nudge-card.tsx | 7 +- .../settings/cycle-tracking-card.tsx | 13 +- .../settings/dashboard-layout-section.tsx | 20 +- src/components/settings/export-section.tsx | 9 +- .../settings/health-record-export-panel.tsx | 3 +- src/components/settings/import-panel.tsx | 14 +- .../settings/injection-sites-card.tsx | 10 +- .../settings/insights-pill-order-section.tsx | 11 +- .../settings/integrations-section.tsx | 1509 +------------ .../settings/integrations/fitbit-card.tsx | 483 ++++ .../settings/integrations/shared.tsx | 142 ++ .../settings/integrations/whoop-card.tsx | 455 ++++ .../settings/integrations/withings-card.tsx | 497 +++++ .../settings/mood-reminder-card.tsx | 13 +- .../settings/notification-status-card.tsx | 16 +- .../settings/notifications-section.tsx | 5 +- src/components/settings/ntfy-card.tsx | 8 +- src/components/settings/sources-section.tsx | 24 +- src/components/settings/telegram-card.tsx | 8 +- .../settings/thresholds-editor-section.tsx | 8 +- .../settings/time-format-select.tsx | 3 +- .../settings/unit-preference-select.tsx | 3 +- src/components/settings/web-push-card.tsx | 7 +- src/components/targets/consistency-strip.tsx | 2 +- src/components/targets/target-edit-sheet.tsx | 42 +- src/components/ui/__tests__/tabs.test.tsx | 2 +- src/components/version-poller.tsx | 18 +- vitest.setup.ts | 26 + 284 files changed, 6058 insertions(+), 5398 deletions(-) create mode 100644 src/app/api/measurements/restore/__tests__/route.test.ts create mode 100644 src/app/api/measurements/restore/route.ts create mode 100644 src/app/api/mood-entries/restore/__tests__/route.test.ts create mode 100644 src/app/api/mood-entries/restore/route.ts create mode 100644 src/components/settings/ai/admin-openai-provider-form.tsx create mode 100644 src/components/settings/ai/ai-insights-card.tsx create mode 100644 src/components/settings/ai/anthropic-provider-form.tsx create mode 100644 src/components/settings/ai/codex-provider-form.tsx create mode 100644 src/components/settings/ai/disable-coach-card.tsx create mode 100644 src/components/settings/ai/fallback-chain-card.tsx create mode 100644 src/components/settings/ai/local-provider-form.tsx create mode 100644 src/components/settings/ai/openai-provider-form.tsx create mode 100644 src/components/settings/ai/runtime-actions-row.tsx create mode 100644 src/components/settings/ai/shared.ts create mode 100644 src/components/settings/integrations/fitbit-card.tsx create mode 100644 src/components/settings/integrations/shared.tsx create mode 100644 src/components/settings/integrations/whoop-card.tsx create mode 100644 src/components/settings/integrations/withings-card.tsx create mode 100644 vitest.setup.ts diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml index ef09fc52e..5e97c78b7 100644 --- a/docs/api/openapi.yaml +++ b/docs/api/openapi.yaml @@ -1735,6 +1735,31 @@ paths: "401": *a1 "422": *a3 "429": *a4 + /api/measurements/restore: + post: + tags: + - Measurements + summary: Restore soft-deleted measurements + description: Un-tombstones up to 200 of the caller's soft-deleted measurement rows in one call — the delete-Undo + counterpart to `/api/measurements/bulk-delete`. Idempotent via the `Idempotency-Key` header; rate-limited + `measurements:restore:`. Forged / foreign / not-deleted ids are silently skipped — `restored` is the + count of rows actually un-tombstoned. The restored rows re-surface in `/api/sync/changes` as upserts. + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/MeasurementsRestoreRequest" + responses: + "200": + description: Restore processed. + content: + application/json: + schema: + $ref: "#/components/schemas/MeasurementsRestoreResponse" + "401": *a1 + "422": *a3 + "429": *a4 /api/sleep/night: get: tags: @@ -4229,6 +4254,20 @@ components: required: - ids description: 1..200 measurement ids, scoped to the caller. Forged / foreign ids are silently skipped (no existence leak). + MeasurementsRestoreRequest: + type: object + properties: + ids: + minItems: 1 + maxItems: 200 + type: array + items: + type: string + minLength: 1 + required: + - ids + description: 1..200 measurement ids, scoped to the caller. Forged / foreign / not-deleted ids are silently skipped (no + existence leak). DeviceRegisterRequest: type: object properties: @@ -7323,6 +7362,33 @@ components: required: - deleted additionalProperties: false + MeasurementsRestoreResponse: + type: object + properties: + data: + $ref: "#/components/schemas/MeasurementsRestoreResult" + error: + type: "null" + meta: + type: object + properties: + requestId: + type: string + additionalProperties: false + required: + - data + - error + additionalProperties: false + MeasurementsRestoreResult: + type: object + properties: + restored: + type: integer + minimum: 0 + maximum: 9007199254740991 + required: + - restored + additionalProperties: false SleepNightResponse: type: object properties: diff --git a/docs/ui-guidelines.md b/docs/ui-guidelines.md index fcb14b166..3e58b9069 100644 --- a/docs/ui-guidelines.md +++ b/docs/ui-guidelines.md @@ -149,9 +149,38 @@ Wrap inputs in `