diff --git a/src/__tests__/inline-action-guard.test.ts b/src/__tests__/inline-action-guard.test.ts
new file mode 100644
index 000000000..82b6fccba
--- /dev/null
+++ b/src/__tests__/inline-action-guard.test.ts
@@ -0,0 +1,121 @@
+/**
+ * A small text action inside a line of content goes through `InlineAction`
+ * (`src/components/ui/inline-action.tsx`).
+ *
+ * Thirty-odd hand-rolled copies of the same "underlined small text link"
+ * carried three different focus rings, none at all on half of them, and a
+ * 16 px touch target on a phone. The primitive owns the 44 px floor on a
+ * coarse pointer and the ring; this guard keeps a new copy from appearing
+ * beside it.
+ *
+ * Matched: a `
{t("dashboard.keyBackupBanner.body")}
-
- {t("dashboard.keyBackupBanner.action")}
-
+
+
+ {t("dashboard.keyBackupBanner.action")}
+
+
);
}
diff --git a/src/components/documents/sources/document-source-picker.tsx b/src/components/documents/sources/document-source-picker.tsx
index ba50192d0..236c430e9 100644
--- a/src/components/documents/sources/document-source-picker.tsx
+++ b/src/components/documents/sources/document-source-picker.tsx
@@ -83,6 +83,7 @@ import {
} from "./source-errors";
import { SegmentedChoice } from "./segmented-choice";
import { systemName } from "./use-document-sources";
+import { InlineAction } from "@/components/ui/inline-action";
export interface DocumentPickerLink {
kind: DocumentPickerLinkKind;
@@ -485,19 +486,19 @@ export function DocumentSourcePicker({
{hasFilters ? (
- {
startOver();
setTag("");
setFrom("");
setTo("");
}}
- className="text-muted-foreground hover:text-foreground focus-visible:ring-ring/50 inline-flex min-h-8 items-center gap-1 rounded-sm text-xs underline-offset-2 hover:underline focus-visible:ring-[3px] focus-visible:outline-none"
+ className="min-h-8"
>
{t("documents.sourcePicker.clearFilters")}
-
+
) : null}
{finished ? (
diff --git a/src/components/insights/coach-panel/chat-bubble.tsx b/src/components/insights/coach-panel/chat-bubble.tsx
index 0c0cec5b3..81c6345a3 100644
--- a/src/components/insights/coach-panel/chat-bubble.tsx
+++ b/src/components/insights/coach-panel/chat-bubble.tsx
@@ -39,6 +39,7 @@ import type {
CoachProvenanceMetric,
} from "@/lib/ai/coach/types";
import { translateOrNull } from "@/lib/i18n/translate-code";
+import { InlineAction } from "@/components/ui/inline-action";
/**
* Per-message bubble renderer for the Coach thread, split out of
@@ -775,13 +776,11 @@ function ChatBubbleImpl({
setup instead of inviting an endless retry. */}
{(errorCode === "coach.provider.none" ||
errorCode === "ai.provider.none") && (
-
- {t("insights.coach.errorNoProviderAction")}
-
+
+
+ {t("insights.coach.errorNoProviderAction")}
+
+
)}
{/* v1.18.1 — one-tap cadence-suggestion action
card. Live from the streaming hook, or restored from the
diff --git a/src/components/insights/health-score-card.tsx b/src/components/insights/health-score-card.tsx
index ffd2f3c5b..eaf1715bf 100644
--- a/src/components/insights/health-score-card.tsx
+++ b/src/components/insights/health-score-card.tsx
@@ -41,6 +41,7 @@ import type {
import { useDateFormatPreference, useTranslations } from "@/lib/i18n/context";
import { SCORE_PILLAR_LABEL_KEYS } from "@/lib/score-config/labels";
import { cn } from "@/lib/utils";
+import { InlineAction } from "@/components/ui/inline-action";
/**
* The Health Score panel: the hero band's right-hand column.
@@ -441,14 +442,15 @@ export function HealthScoreCard({
) : null}
{/* v1.42 — the way to the score's course over time. */}
-
-
- {t("insights.healthScore.historyLink")}
-
+
+
+
+ {t("insights.healthScore.historyLink")}
+
+
diff --git a/src/components/insights/medication-efficacy-summary.tsx b/src/components/insights/medication-efficacy-summary.tsx
index a68d5ae54..5be57af72 100644
--- a/src/components/insights/medication-efficacy-summary.tsx
+++ b/src/components/insights/medication-efficacy-summary.tsx
@@ -24,6 +24,7 @@ import type {
MedicationEfficacyDTO,
EfficacyTargetView,
} from "@/lib/medications/efficacy/build-efficacy";
+import { InlineAction } from "@/components/ui/inline-action";
function primaryWithDelta(
dto: MedicationEfficacyDTO,
@@ -108,13 +109,14 @@ export function MedicationEfficacySummary({
-
- {t("medications.efficacy.insights.link")}
-
+
+
+ {t("medications.efficacy.insights.link")}
+
+
);
})}
diff --git a/src/components/insights/metric-target-summary.tsx b/src/components/insights/metric-target-summary.tsx
index 0e7d85126..bc2b2f748 100644
--- a/src/components/insights/metric-target-summary.tsx
+++ b/src/components/insights/metric-target-summary.tsx
@@ -18,6 +18,7 @@ import { useTargetAdjust } from "@/lib/insights/target-adjust-context";
import { getTargetSourceLink } from "@/lib/targets/source-link";
import { apiGet } from "@/lib/api/api-fetch";
import { resolveGlucoseUnit, type GlucoseUnit } from "@/lib/glucose";
+import { InlineAction } from "@/components/ui/inline-action";
/**
* v1.8.0 → v1.8.5 — surface the per-metric target reference panel on each
@@ -416,19 +417,16 @@ function TargetReferencePanel({
panel registers itself as that gear's edit target above. */}
diff --git a/src/components/insights/trends-row.tsx b/src/components/insights/trends-row.tsx
index b62e8e06b..eda495290 100644
--- a/src/components/insights/trends-row.tsx
+++ b/src/components/insights/trends-row.tsx
@@ -23,6 +23,7 @@ import {
type TrendAnnotationStatus,
} from "./trend-annotation";
import { useUnitDisplay } from "@/hooks/use-unit-display";
+import { InlineAction } from "@/components/ui/inline-action";
/**
* Trends row — a small chart per metric above a one-sentence
@@ -339,15 +340,20 @@ export function TrendsRow({
is used but was unreachable from Insights. The link sits below
the caption so the chart keeps its own interactions. */}
{config.detailHref ? (
-
- {t("insights.trendsRow.viewDetail", { metric: title })}
-
-
+
+ {t("insights.trendsRow.viewDetail", { metric: title })}
+
+
+
) : null}
diff --git a/src/components/medications/card-parts/medication-compliance-bars.tsx b/src/components/medications/card-parts/medication-compliance-bars.tsx
index 2f70c9964..99bd9fe02 100644
--- a/src/components/medications/card-parts/medication-compliance-bars.tsx
+++ b/src/components/medications/card-parts/medication-compliance-bars.tsx
@@ -3,6 +3,7 @@ import { Flame, RotateCw } from "lucide-react";
import { ComplianceInfoTip } from "@/components/medications/card-parts/compliance-info-tip";
import { Progress } from "@/components/ui/progress";
import { useTranslations, useFormatters } from "@/lib/i18n/context";
+import { InlineAction } from "@/components/ui/inline-action";
interface MedicationComplianceBarsProps {
rate7: number;
@@ -286,14 +287,10 @@ export function MedicationComplianceError({
-
-
+
+
{t("common.retry")}
-
+
diff --git a/src/components/medications/detail/medication-detail-tabs.tsx b/src/components/medications/detail/medication-detail-tabs.tsx
index bb34d5411..2f0128495 100644
--- a/src/components/medications/detail/medication-detail-tabs.tsx
+++ b/src/components/medications/detail/medication-detail-tabs.tsx
@@ -104,6 +104,7 @@ import { AutoMarkTakenBody } from "@/components/medications/sections/auto-mark-t
import { IntakeTrackingBody } from "@/components/medications/sections/intake-tracking-section";
import { MedicationRelatedSection } from "@/components/medications/detail/medication-related-section";
import type { RelatedLink } from "@/lib/links/related";
+import { InlineAction } from "@/components/ui/inline-action";
// v1.15.20 — Recharts stays out of the detail page's initial bundle:
// both chart bodies load through `next/dynamic` (client-only) with the
@@ -1097,14 +1098,13 @@ function StatusRow({
{label}
{value}
-
{jumpLabel}
-
+
);
}
diff --git a/src/components/medications/sections/inventory-section.tsx b/src/components/medications/sections/inventory-section.tsx
index 7af96d638..1bab5744a 100644
--- a/src/components/medications/sections/inventory-section.tsx
+++ b/src/components/medications/sections/inventory-section.tsx
@@ -79,6 +79,7 @@ import {
type InventoryItem,
type InventoryState,
} from "./inventory-shared";
+import { InlineAction } from "@/components/ui/inline-action";
export { AddInventoryDialog, AdjustInventoryDialog } from "./inventory-dialogs";
export { invalidateSupplyQueries } from "./inventory-shared";
@@ -314,17 +315,18 @@ export function InventorySection({
of what was shared" panel. */}
{!inSharedRecord && (