diff --git a/src/__tests__/baselines/source-function-lines.json b/src/__tests__/baselines/source-function-lines.json index 512931154..e033dd14a 100644 --- a/src/__tests__/baselines/source-function-lines.json +++ b/src/__tests__/baselines/source-function-lines.json @@ -70,7 +70,7 @@ "components/charts/medication-compliance-chart.tsx :: MedicationComplianceChart": 543, "components/charts/mood-chart.tsx :: MoodChart": 896, "components/charts/mood-heatmap.tsx :: MoodHeatmap": 296, - "components/charts/trend-card.tsx :: TrendCard": 365, + "components/charts/trend-card.tsx :: TrendCard": 351, "components/clinician/clinician-view.tsx :: ClinicianView": 223, "components/command-palette/command-palette.tsx :: PaletteBody": 333, "components/custom-metrics/custom-metric-chart.tsx :: CustomMetricChart": 231, diff --git a/src/components/admin/__tests__/api-token-truncate.test.tsx b/src/components/admin/__tests__/api-token-truncate.test.tsx index aab52dfa4..403d45c3b 100644 --- a/src/components/admin/__tests__/api-token-truncate.test.tsx +++ b/src/components/admin/__tests__/api-token-truncate.test.tsx @@ -16,7 +16,7 @@ import { renderToStaticMarkup } from "react-dom/server"; * * The fix is aggressive: every cell that could carry a long string * gets `truncate` (overflow-hidden + text-ellipsis + whitespace-nowrap) - * AND a tooltip on hover/long-press so the full value is still + * AND a popover on a tap so the full value is still * discoverable. This is the same pattern UserManagementSection uses * — apply it consistently here. */ @@ -83,7 +83,7 @@ function render() { ); } -describe("ApiTokenOverviewSection — truncate + tooltip (4th attempt)", () => { +describe("ApiTokenOverviewSection — truncate + popover", () => { it("mobile token-name span does NOT carry the contradictory `break-all` modifier", () => { // `truncate` already sets `white-space: nowrap`, which beats // `word-break: break-all` per CSS spec — `break-all` was dead @@ -125,11 +125,11 @@ describe("ApiTokenOverviewSection — truncate + tooltip (4th attempt)", () => { ); }); - it("renders shadcn tooltip wrappers around truncated cells so the full value stays discoverable", () => { + it("makes each truncated cell a button that opens the full value on a tap", () => { const html = render(); - // Tooltip primitive uses `data-slot="tooltip-content"` for the - // hover bubble. Verify at least one tooltip-content node was - // rendered next to a truncated cell. - expect(html).toContain('data-slot="tooltip-trigger"'); + // A hover tooltip never opens on a phone; the popover trigger does. + expect(html).toMatch( + / - - -
-

{summaryLabel}

-

- {t("insights.derived.coverage.historyDays", { - count: clampInt(coverage.historyDays, 0, 99999), + {summaryLabel} + + + +

+

{summaryLabel}

+

+ {t("insights.derived.coverage.historyDays", { + count: clampInt(coverage.historyDays, 0, 99999), + })} +

+ {coverage.missing.length > 0 && ( +

+ {t("insights.derived.coverage.missing", { + list: coverage.missing.join(", "), })}

- {coverage.missing.length > 0 && ( -

- {t("insights.derived.coverage.missing", { - list: coverage.missing.join(", "), - })} -

- )} -
- - - + )} +
+ + ); } diff --git a/src/components/insights/measurement-diversity-nudge.tsx b/src/components/insights/measurement-diversity-nudge.tsx index ad4d1e775..8663287c4 100644 --- a/src/components/insights/measurement-diversity-nudge.tsx +++ b/src/components/insights/measurement-diversity-nudge.tsx @@ -3,12 +3,7 @@ import { useQuery } from "@tanstack/react-query"; import { Lightbulb } from "lucide-react"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@/components/ui/tooltip"; +import { InfoPopover } from "@/components/ui/info-popover"; import { useAuth } from "@/hooks/use-auth"; import { useTranslations } from "@/lib/i18n/context"; import { queryKeys } from "@/lib/query-keys"; @@ -29,7 +24,7 @@ import { detectMeasurementDiversity } from "@/lib/insights/measurement-diversity * inline `role="note"` block confused on the category pages (it read * like a data finding rather than a tip), so the nudge now renders as a * small `Lightbulb` glyph beside the page title. The hint text the block - * used to show inline lives in a hover / focus Tooltip; the glyph mounts + * used to show inline opens on a tap (`InfoPopover`); the glyph mounts * only when the spread is actually lopsided, so a healthy metric shows * no extra affordance at all. * @@ -95,26 +90,15 @@ export function MeasurementDiversityNudge({ const triggerLabel = t("insights.subPage.diversity.trigger"); return ( - - - - - - - {message} - - - + ); } diff --git a/src/components/insights/sleep-source-discrepancy-marker.tsx b/src/components/insights/sleep-source-discrepancy-marker.tsx index 8c972f80f..bac82012a 100644 --- a/src/components/insights/sleep-source-discrepancy-marker.tsx +++ b/src/components/insights/sleep-source-discrepancy-marker.tsx @@ -2,12 +2,7 @@ import { TriangleAlert } from "lucide-react"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@/components/ui/tooltip"; +import { InfoPopover } from "@/components/ui/info-popover"; import { useTranslations } from "@/lib/i18n/context"; import { formatDurationMinutes } from "@/lib/i18n/duration"; import { MEASUREMENT_SOURCE_SETTINGS_LABEL_KEYS } from "@/lib/i18n/source-labels"; @@ -17,7 +12,7 @@ import { MEASUREMENT_SOURCE_SETTINGS_LABEL_KEYS } from "@/lib/i18n/source-labels * `sleep-hypnogram.tsx` (which introduced it in v1.28.21) so the dashboard * sleep tile and the hypnogram render the identical hint off the identical * wire shape. Observational only: the headline number always stays the - * winning writer's total; the tooltip lists each writer's own total so a + * winning writer's total; the popover lists each writer's own total so a * multi-tracker user can see WHY the night reads surprising. */ @@ -35,7 +30,7 @@ export interface SleepSourceDiscrepancyDto { * Localised device-type label keys (`Measurement.deviceType`), reused from * the source-priority settings surface. Appended in parentheses so two * writer apps behind the same source (watch vs phone under Apple Health) - * stay distinguishable in the discrepancy tooltip. + * stay distinguishable in the discrepancy popover. */ const DEVICE_TYPE_LABEL_KEYS: Record = { watch: "settings.sections.sources.deviceLabels.watch", @@ -64,7 +59,7 @@ export function sourceDisplayName( export interface SleepSourceDiscrepancyMarkerProps { discrepancy: SleepSourceDiscrepancyDto | null | undefined; - /** Tooltip alignment relative to the trigger (default `"start"`). */ + /** Popover alignment relative to the trigger (default `"start"`). */ align?: "start" | "center" | "end"; } @@ -82,36 +77,36 @@ export function SleepSourceDiscrepancyMarker({ const { t } = useTranslations(); if (!discrepancy) return null; return ( - - - - - - -

- {t("insights.sleep.sourceDiscrepancy.tooltipTitle")} -

-

+ // On the dashboard the marker sits in the tile's link: its tap opens the + // popover, not the tile. + { + event.preventDefault(); + event.stopPropagation(); + }} + > + + + {t("insights.sleep.sourceDiscrepancy.tooltipTitle")} + {discrepancy.sources .map( (b) => `${sourceDisplayName(b.source, b.deviceType, t)} ${formatDurationMinutes(b.asleepMinutes, t)}`, ) .join(" · ")} -

-
-
-
+ + } + label={t("insights.sleep.sourceDiscrepancy.tooltipTitle")} + icon={TriangleAlert} + className="-mx-3" + align={align} + triggerDataSlot="sleep-source-discrepancy" + bodyDataSlot="sleep-source-discrepancy-body" + /> + ); } diff --git a/src/components/targets/__tests__/range-bar.test.tsx b/src/components/targets/__tests__/range-bar.test.tsx index bb44a7a84..e3edc111c 100644 --- a/src/components/targets/__tests__/range-bar.test.tsx +++ b/src/components/targets/__tests__/range-bar.test.tsx @@ -85,16 +85,17 @@ describe("", () => { expect(marker).toBeLessThan(100 - EDGE_PADDING_PERCENT); }); - it("makes the marker focusable with a text alternative for the value + range (2026-07-17 a11y audit M1)", () => { + it("makes the marker a button named with the value + range, so a tap or a key opens its numbers", () => { const html = render({ value: 72, min: 60, max: 100, unit: "bpm", }); - expect(html).toContain('tabindex="0"'); - expect(html).toContain('role="img"'); - expect(html).toContain('aria-label="Current: 72 bpm'); + expect(html).toMatch( + /