fix(ux): close medium UX-audit findings across flows, IA, and onboarding

Coach deep-links now carry launch context in the URL instead of dropping it
at the route boundary: /coach reads ?scope= (validated against the closed
CoachScopeSource enum) and ?ask= to seed the drawer's own prefill/scope
props, the Today check-in's "Adjust" action seeds ?ask= with the plan's own
words instead of opening a blank chat, and the workout detail page scopes
its coach button to workouts.

IA: give the document vault a "View N lab values" link on LAB_RESULT-shaped
documents (the auto-stage/OCR-commit linkage existed server-side with no UI
surface); disambiguate the two "Recovery" pages (RECOVERY_SCORE anatomy vs
the composite wearable page) with a distinct label plus a two-way
cross-link; move the opt-in nutrients sync card from Source priority to
Integrations, where every other sync surface lives; rename the user-menu
theme switcher off "Appearance" (collides with the Settings hub) and the
Environment settings section to "Location & weather"; cross-link mood,
mental-wellbeing check-ins, and mood insights, which were three
unconnected islands.

Onboarding: the getting-started checklist's data-source/notifications rows
now gate their fetch on the same checklistRelevant condition the other rows
use, instead of only-while-onboarding — they used to be stuck undone
forever for anyone who finished the wizard with under 5 measurements; the
tour wrap-up no longer promises a replay affordance that was removed;
the wizard's Withings card shows a connected badge once the OAuth
round-trip lands instead of leaving the button looking untouched in the
tab it was opened from; the done screen and Modules hub get a quiet
cross-link; the sleep empty-state CTA reads "Connect a source" instead of
being pinned to one vendor.

All copy changes land across every locale; de.json reviewed for register.
This commit is contained in:
Marc-André Bombeck committed 2026-07-18 05:06:24 +02:00
1 parent e9e186dca5
commit 8dd04e0170
23 files changed
+431 -79

No files matched your search

@@ -18,8 +18,10 @@
*/
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import {
ArrowRight,
Check,
Download,
FlaskConical,
Pencil,
Plus,
Share2,
@@ -32,6 +34,7 @@ import { useEffect, useRef, useState } from "react";
import { toast } from "sonner";
import { useIllnessEpisodes } from "@/components/illness/use-illness";
import { useAuth } from "@/hooks/use-auth";
import { Button } from "@/components/ui/button";
import { DateField } from "@/components/ui/date-field";
import { Input } from "@/components/ui/input";
@@ -230,6 +233,21 @@ export function DocumentDetailSheet({
});
const doc = detail.data;
// 2026-07-17 UX/IA audit M5 / F5-5 — the document → lab-values direction
// had no UI link at all: an auto-staged (PENDING) or OCR-confirmed
// (APPROVED, see `linkOcrLabsToVaultDocument`) OBSERVATION fact already
// ties this document to specific lab rows, but nothing on the sheet said
// so. There is no per-value route yet (`/labs` has no `?analyte=`
// deep-link — see the labs page's own "no per-value relation" comment),
// so this links to the labs list rather than a single biomarker; still a
// real jump where there was none before.
const labFactCount =
doc?.facts.filter(
(fact) => fact.factType === "OBSERVATION" && fact.status !== "REJECTED",
).length ?? 0;
const { user } = useAuth();
const labsModuleEnabled = user?.modules?.labs !== false;
const episodes = useIllnessEpisodes(true);
// AI layer — capability drives the vision/text transport; the mutations run
@@ -792,6 +810,18 @@ export function DocumentDetailSheet({
</div>
</div>
{labsModuleEnabled && labFactCount > 0 ? (
<Link
href="/labs"
data-slot="document-detail-lab-values-link"
className="text-muted-foreground hover:text-foreground focus-visible:ring-ring/50 inline-flex items-center gap-1.5 self-start rounded-md text-sm transition-colors focus-visible:ring-[3px] focus-visible:outline-none"
>
<FlaskConical className="size-4" aria-hidden="true" />
{t("documents.detail.labValuesLink", { count: labFactCount })}
<ArrowRight className="size-3.5" aria-hidden="true" />
</Link>
) : null}
<p className="text-muted-foreground text-xs">
{t("documents.detail.uploadedMeta", {
date: format.date(doc.createdAt),