diff --git a/src/app/globals.css b/src/app/globals.css index 9c06a4ddb..9a4cb8040 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1799,6 +1799,17 @@ summary::-webkit-details-marker { box-shadow: 0 0 0 4px color-mix(in srgb, var(--focus) 25%, transparent) !important; } + /* Underline-tab focus: an inset rounded ring that hugs the label instead of the + default full-height box, which collides with the tablist border. */ + .focus-ring-tab { + border-radius: var(--radius-md); + } + .focus-ring-tab:focus-visible { + outline: 2px solid var(--focus) !important; + outline-offset: -4px !important; + box-shadow: none !important; + } + /* Premium hover transitions for source capsules */ .source-capsule-hover { transition: diff --git a/src/components/DocumentTagCloud.tsx b/src/components/DocumentTagCloud.tsx index 8f8069809..b3ce99a1e 100644 --- a/src/components/DocumentTagCloud.tsx +++ b/src/components/DocumentTagCloud.tsx @@ -71,8 +71,7 @@ function DocumentTagChip({ groupToneClass(tag.group), tag.queryMatched && "ring-2 ring-[color:var(--focus)]/25", selected && "ring-2 ring-[color:var(--primary)]/35", - onTagClick && - "cursor-pointer transition hover:-translate-y-0.5 hover:border-[color:var(--border-strong)] focus:outline-none focus:ring-2 focus:ring-[color:var(--focus)]/40", + onTagClick && "cursor-pointer transition hover:-translate-y-0.5 hover:border-[color:var(--border-strong)]", ); const content = ( <> diff --git a/src/components/applications-launcher-page.tsx b/src/components/applications-launcher-page.tsx index 239489f16..3af9807e4 100644 --- a/src/components/applications-launcher-page.tsx +++ b/src/components/applications-launcher-page.tsx @@ -343,7 +343,7 @@ function FilterTabs({ aria-controls="launcher-results-panel" onClick={() => onFilterChange(filter.id)} className={cn( - "inline-flex min-h-9 items-center justify-center rounded-lg border px-4 text-xs font-bold transition", + "inline-flex min-h-11 items-center justify-center whitespace-nowrap rounded-lg border px-4 text-xs font-bold transition", active ? "border-[color:var(--clinical-accent)] bg-[color:var(--clinical-accent)] text-[color:var(--clinical-accent-contrast)] shadow-[var(--shadow-tight)]" : "border-[color:var(--border)] bg-[color:var(--surface-lux)] text-[color:var(--text-muted)] hover:bg-[color:var(--surface-subtle)] hover:text-[color:var(--text-heading)]", @@ -368,7 +368,7 @@ function FilterTabs({ aria-controls="launcher-results-panel" onClick={() => onFilterChange(filter.id)} className={cn( - "inline-flex min-h-7 shrink-0 items-center justify-center gap-0.5 rounded-lg border px-2 text-4xs font-bold transition", + "inline-flex min-h-11 shrink-0 items-center justify-center gap-0.5 whitespace-nowrap rounded-lg border px-2 text-4xs font-bold transition", active ? "border-[color:var(--clinical-accent)] bg-[color:var(--clinical-accent)] text-[color:var(--clinical-accent-contrast)] shadow-[var(--shadow-tight)]" : "border-[color:var(--border)] bg-[color:var(--surface-lux)] text-[color:var(--text-muted)]", diff --git a/src/components/clinical-dashboard/favourites-command-library-page.tsx b/src/components/clinical-dashboard/favourites-command-library-page.tsx index cd68a125d..0cfb03fbe 100644 --- a/src/components/clinical-dashboard/favourites-command-library-page.tsx +++ b/src/components/clinical-dashboard/favourites-command-library-page.tsx @@ -386,7 +386,7 @@ function RowActionsMenu({ item }: { item: FavouriteItem }) { aria-label={`More actions for ${item.title}`} onClick={() => setOpen((current) => !current)} className={cn( - "grid h-9 w-9 place-items-center rounded-lg text-[color:var(--text-muted)] hover:bg-[color:var(--surface-subtle)]", + "grid h-11 w-11 place-items-center rounded-lg text-[color:var(--text-muted)] hover:bg-[color:var(--surface-subtle)]", focusRing, )} > diff --git a/src/components/clinical-dashboard/favourites-library-nav.tsx b/src/components/clinical-dashboard/favourites-library-nav.tsx index 2c62b51fb..c6ec59429 100644 --- a/src/components/clinical-dashboard/favourites-library-nav.tsx +++ b/src/components/clinical-dashboard/favourites-library-nav.tsx @@ -210,7 +210,7 @@ function SidebarRow({ entry }: { entry: SidebarEntry }) { aria-pressed={entry.active} onClick={entry.onClick} className={cn( - "flex min-h-9 w-full items-center gap-2.5 rounded-lg border px-2.5 text-left text-sm-minus font-semibold transition", + "flex min-h-11 w-full items-center gap-2.5 rounded-lg border px-2.5 text-left text-sm-minus font-semibold transition", entry.active ? "border-[color:var(--clinical-accent-border)] bg-[color:var(--clinical-accent-soft)] text-[color:var(--clinical-accent)] shadow-[inset_2px_0_0_var(--clinical-accent)]" : "border-transparent text-[color:var(--text-muted)] hover:bg-[color:var(--surface-subtle)] hover:text-[color:var(--text)]", @@ -302,7 +302,7 @@ export function FavouritesSidebar({ type="button" onClick={() => onCollapsedChange(true)} className={cn( - "grid h-9 w-9 shrink-0 place-items-center rounded-lg border border-transparent text-[color:var(--text-muted)] transition hover:border-[color:var(--border)] hover:bg-[color:var(--surface)] hover:text-[color:var(--text)]", + "grid h-11 w-11 shrink-0 place-items-center rounded-lg border border-transparent text-[color:var(--text-muted)] transition hover:border-[color:var(--border)] hover:bg-[color:var(--surface)] hover:text-[color:var(--text)]", focusRing, )} aria-label="Compact library sidebar" diff --git a/src/components/clinical-dashboard/medication-prescribing-workspace.tsx b/src/components/clinical-dashboard/medication-prescribing-workspace.tsx index 58213e866..c02e3cee1 100644 --- a/src/components/clinical-dashboard/medication-prescribing-workspace.tsx +++ b/src/components/clinical-dashboard/medication-prescribing-workspace.tsx @@ -207,7 +207,7 @@ function MedicationHome({ actionsLabel="Medication prompts" actions={medicationPrompts.map((prompt) => ({ title: prompt.label, - description: "Prescribing-focused search.", + description: prompt.description, icon: prompt.icon, onClick: () => onSuggestedSearch(prompt.label), disabled: loading, diff --git a/src/components/differentials/differential-detail-page.tsx b/src/components/differentials/differential-detail-page.tsx index cd595a6f4..f22bb6210 100644 --- a/src/components/differentials/differential-detail-page.tsx +++ b/src/components/differentials/differential-detail-page.tsx @@ -160,13 +160,13 @@ function SectionItems({ {slug ? ( {item} ) : ( - + {item} )} @@ -714,11 +714,11 @@ function TopActions({ onCompare: () => void; }) { return ( -
+
diff --git a/tests/ui-tools.spec.ts b/tests/ui-tools.spec.ts index 4e2224f4c..639f3846f 100644 --- a/tests/ui-tools.spec.ts +++ b/tests/ui-tools.spec.ts @@ -1263,6 +1263,50 @@ test.describe("Clinical KB tools launcher", () => { await expectNoPageHorizontalOverflow(page); }); + test("diagnosis detail actions stay tappable and tabs stay single-line", async ({ page }) => { + await page.setViewportSize({ width: 1024, height: 800 }); + await gotoLauncher(page, "/differentials/diagnoses/delirium"); + await expect(page.getByTestId("differential-detail-page")).toBeVisible(); + // The desktop action cluster must keep its intrinsic width (shrink-0) so the + // icon action does not get crushed below the 44px tap standard. + await expectMinTouchTarget(page.getByRole("button", { name: "Save diagnosis" })); + + // Tabs: no page overflow and single-line labels at the narrowest width. + await page.setViewportSize({ width: 320, height: 700 }); + await expect(page.getByTestId("differential-detail-page")).toBeVisible(); + await expectNoPageHorizontalOverflow(page); + const overviewTab = page.getByRole("tab", { name: "Overview" }); + await expect(overviewTab).toBeVisible(); + // Count rendered label lines from text-node rects (an icon rect would bridge + // two wrapped lines and mask a wrap); the tab label must stay on one line. + const overviewLineCount = await overviewTab.evaluate((element) => { + const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT); + const rects: DOMRect[] = []; + while (walker.nextNode()) { + const node = walker.currentNode; + if (!(node.textContent ?? "").trim()) continue; + const range = document.createRange(); + range.selectNodeContents(node); + for (const rect of range.getClientRects()) { + if (rect.height > 4 && rect.width > 1) rects.push(rect); + } + } + rects.sort((a, b) => a.top - b.top); + let lines = 0; + let lineBottom = Number.NEGATIVE_INFINITY; + for (const rect of rects) { + if (rect.top >= lineBottom - 4) { + lines += 1; + lineBottom = rect.bottom; + } else { + lineBottom = Math.max(lineBottom, rect.bottom); + } + } + return lines; + }); + expect(overviewLineCount).toBe(1); + }); + test("differentials presentation comparison page stays wired to differentials mode", async ({ page }) => { await page.setViewportSize({ width: 1440, height: 920 }); const workflow = acuteConfusionPresentationWorkflow;