From 5683af87e827fb0f4bb406318e443d30b723e317 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 19 Jul 2026 14:59:13 +0000 Subject: [PATCH 1/3] Fix differentials mobile search card layout and FAB clearance Restructure phone result cards so titles no longer fight Emergent badges, show subtitles and readable chips, number the list contiguously after the featured best answer, and reserve enough bottom space that the last card clears the compare FAB. Co-authored-by: BigSimmo --- .../clinical-dashboard/differentials-home.tsx | 75 ++++++++++++------- tests/ui-tools.spec.ts | 29 +++++-- 2 files changed, 70 insertions(+), 34 deletions(-) diff --git a/src/components/clinical-dashboard/differentials-home.tsx b/src/components/clinical-dashboard/differentials-home.tsx index 40c54affd..e89a4e7db 100644 --- a/src/components/clinical-dashboard/differentials-home.tsx +++ b/src/components/clinical-dashboard/differentials-home.tsx @@ -324,9 +324,14 @@ function MatchBadge({ label }: { label: string }) { ); } -function Chip({ children }: { children: string }) { +function Chip({ children, className }: { children: string; className?: string }) { return ( - + {children} ); @@ -453,42 +458,49 @@ function MobileResultCard({ selected: boolean; onToggle?: () => void; }) { - const Icon = result.icon; - return ( -
-
- +
+
+ {index + 1} - - -
-
- - {result.title} - + + {result.title} + +
-
-
+ {result.subtitle ? ( +

+ {result.subtitle} +

+ ) : null}
{onToggle ? : }
{result.tags.slice(0, 2).map((tag) => ( - {tag} + + {tag} + ))} - {result.tags.length > 2 ? {`+${result.tags.length - 2}`} : null} + {result.tags.length > 2 ? ( + {`+${result.tags.length - 2}`} + ) : null}
); @@ -541,12 +553,12 @@ function BestAnswerCard({

Best answer

-

+

- {best.title} + {best.title}

@@ -865,7 +877,7 @@ function SearchResultsView({ // this results canvas into a nested phone scrollport, stealing scroll from // #main-content. The fixed compare FAB and shell hide-on-scroll both assume // #main-content owns vertical scroll. - className="mx-auto grid w-full max-w-[86rem] min-w-0 gap-3 overflow-x-clip px-3 pb-[calc(10rem+env(safe-area-inset-bottom))] min-[390px]:gap-4 sm:px-4 lg:px-0 lg:pb-0" + className="mx-auto grid w-full max-w-[86rem] min-w-0 gap-3 overflow-x-clip px-3.5 pb-[calc(12.5rem+env(safe-area-inset-bottom))] min-[390px]:gap-4 sm:px-4 lg:px-0 lg:pb-0" > {/* Query context lives here on every breakpoint — on phones this is the only place the submitted query is visible above the fold. */} @@ -1061,6 +1073,11 @@ function SearchResultsView({ // Best-match styling remains tied to relevance while the row // number follows the user's chosen presentation order. const isBest = result.kind === best.kind && result.id === best.id; + // Phone list hides the best-answer duplicate, so ranks must + // skip that row or the first visible card starts at 2/3. + const mobileIndex = visibleResults + .slice(0, displayIndex) + .filter((candidate) => !(candidate.kind === best.kind && candidate.id === best.id)).length; return ( // The best answer is already featured above the phone list, // so its ranked duplicate only renders from the desktop @@ -1079,7 +1096,7 @@ function SearchResultsView({
toggleSelected(result.id) : undefined} /> diff --git a/tests/ui-tools.spec.ts b/tests/ui-tools.spec.ts index c2791757d..36f8bec93 100644 --- a/tests/ui-tools.spec.ts +++ b/tests/ui-tools.spec.ts @@ -1448,6 +1448,26 @@ test.describe("Clinical KB tools launcher", () => { }); expect(badgeMetrics.height).toBeGreaterThanOrEqual(22); expect(badgeMetrics.scrollHeight).toBeLessThanOrEqual(badgeMetrics.height + 1); + + // Phone list hides the featured best answer, so ranks must start at 1. + const mobileCards = page.getByTestId("differential-mobile-result-card"); + await expect(mobileCards.first()).toBeVisible(); + await expect(mobileCards.first().getByTestId("differential-mobile-result-rank")).toHaveText("1"); + const ranks = await mobileCards.getByTestId("differential-mobile-result-rank").allTextContents(); + expect(ranks).toEqual(ranks.map((_, index) => String(index + 1))); + + // Status badge sits on its own meta row below the title, never beside it. + const titleBadgeLayout = await mobileCards.first().evaluate((card) => { + const title = card.querySelector("a span.line-clamp-2") ?? card.querySelector("a"); + const badge = card.querySelector('[data-testid="differential-status-badge"]'); + if (!title || !badge) return null; + const titleRect = title.getBoundingClientRect(); + const badgeRect = badge.getBoundingClientRect(); + return { titleBottom: titleRect.bottom, badgeTop: badgeRect.top }; + }); + expect(titleBadgeLayout).not.toBeNull(); + expect(titleBadgeLayout!.badgeTop).toBeGreaterThanOrEqual(titleBadgeLayout!.titleBottom - 1); + await expectNoPageHorizontalOverflow(page); }); @@ -1505,12 +1525,9 @@ test.describe("Clinical KB tools launcher", () => { await mainContent.evaluate((element) => element.scrollTo({ top: element.scrollHeight, behavior: "instant" })); const lastResultBottom = await page - .getByTestId("differential-status-badge") + .getByTestId("differential-mobile-result-card") .last() - .evaluate( - (element) => - element.closest("article")?.getBoundingClientRect().bottom ?? element.getBoundingClientRect().bottom, - ); + .evaluate((element) => element.getBoundingClientRect().bottom); const dockTop = await dock.evaluate((element) => element.getBoundingClientRect().top); expect(lastResultBottom).toBeLessThanOrEqual(dockTop); @@ -1530,6 +1547,8 @@ test.describe("Clinical KB tools launcher", () => { expect(compareGeometry.bottom).toBeLessThanOrEqual(compareGeometry.viewportHeight); expect(compareGeometry.receivesPointer).toBe(true); expect(compareGeometry.bottom).toBeLessThan(dockTop); + // Last card must clear the floating compare CTA, not only the composer dock. + expect(lastResultBottom).toBeLessThanOrEqual(compareGeometry.top); await expectNoPageHorizontalOverflow(page); // The result cards and compare bar remain in their non-desktop layout up From 4bdc1aaab217ecd8f4b593025d709557d9d427c7 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 19 Jul 2026 15:19:17 +0000 Subject: [PATCH 2/3] Prevent mobile differential chips from overflowing cards Clamp chip flex items with min-w-0, hide card overflow, widen page padding, and assert mobile result cards do not scroll horizontally. Co-authored-by: BigSimmo --- .../clinical-dashboard/differentials-home.tsx | 14 +++++++------- tests/ui-tools.spec.ts | 10 ++++++++++ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/components/clinical-dashboard/differentials-home.tsx b/src/components/clinical-dashboard/differentials-home.tsx index e89a4e7db..ade2e7f2d 100644 --- a/src/components/clinical-dashboard/differentials-home.tsx +++ b/src/components/clinical-dashboard/differentials-home.tsx @@ -328,11 +328,11 @@ function Chip({ children, className }: { children: string; className?: string }) return ( - {children} + {children} ); } @@ -461,7 +461,7 @@ function MobileResultCard({ return (
{onToggle ? : }
-
+
{result.tags.slice(0, 2).map((tag) => ( {tag} ))} {result.tags.length > 2 ? ( - {`+${result.tags.length - 2}`} + {`+${result.tags.length - 2}`} ) : null}
@@ -877,7 +877,7 @@ function SearchResultsView({ // this results canvas into a nested phone scrollport, stealing scroll from // #main-content. The fixed compare FAB and shell hide-on-scroll both assume // #main-content owns vertical scroll. - className="mx-auto grid w-full max-w-[86rem] min-w-0 gap-3 overflow-x-clip px-3.5 pb-[calc(12.5rem+env(safe-area-inset-bottom))] min-[390px]:gap-4 sm:px-4 lg:px-0 lg:pb-0" + className="mx-auto grid w-full max-w-[86rem] min-w-0 gap-3 overflow-x-clip px-4 pb-[calc(12.5rem+env(safe-area-inset-bottom))] min-[390px]:gap-4 sm:px-4 lg:px-0 lg:pb-0" > {/* Query context lives here on every breakpoint — on phones this is the only place the submitted query is visible above the fold. */} diff --git a/tests/ui-tools.spec.ts b/tests/ui-tools.spec.ts index 36f8bec93..bc840a752 100644 --- a/tests/ui-tools.spec.ts +++ b/tests/ui-tools.spec.ts @@ -1468,6 +1468,16 @@ test.describe("Clinical KB tools launcher", () => { expect(titleBadgeLayout).not.toBeNull(); expect(titleBadgeLayout!.badgeTop).toBeGreaterThanOrEqual(titleBadgeLayout!.titleBottom - 1); + const cardOverflow = await mobileCards.evaluateAll((cards) => + cards.map((card) => ({ + overflowX: card.scrollWidth > card.clientWidth + 1, + width: card.clientWidth, + })), + ); + for (const card of cardOverflow) { + expect(card.overflowX).toBe(false); + } + await expectNoPageHorizontalOverflow(page); }); From 252ec6aafd9426c0508adc408f060d459c8f519f Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 19 Jul 2026 15:24:18 +0000 Subject: [PATCH 3/3] Format differentials-home with Prettier Static PR checks failed on format:check for the mobile card layout edits. Co-authored-by: BigSimmo --- src/components/clinical-dashboard/differentials-home.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/components/clinical-dashboard/differentials-home.tsx b/src/components/clinical-dashboard/differentials-home.tsx index ade2e7f2d..e18e1f1ff 100644 --- a/src/components/clinical-dashboard/differentials-home.tsx +++ b/src/components/clinical-dashboard/differentials-home.tsx @@ -491,10 +491,7 @@ function MobileResultCard({
{result.tags.slice(0, 2).map((tag) => ( - + {tag} ))}