diff --git a/src/components/ClinicalDashboard.tsx b/src/components/ClinicalDashboard.tsx
index 05fb92a664..1ec7998a20 100644
--- a/src/components/ClinicalDashboard.tsx
+++ b/src/components/ClinicalDashboard.tsx
@@ -3366,6 +3366,7 @@ export function ClinicalDashboard({
composerFollowUpSuggestions={searchMode === "answer" ? answerFollowUpSuggestions : undefined}
onPickComposerFollowUpSuggestion={handlePickFollowUpSuggestion}
composerFollowUpSuggestionsDisabled={loading}
+ showPhoneSuggestionTickerOnHome={heroOwnsPhoneComposer}
sharedHomeIdentity={showSharedHome}
composerPlaceholder={searchMode === "answer" && latestAnswerQuery ? "Ask a follow-up..." : undefined}
mobileSearchPlacement={hasMobileBottomSearch ? "bottom" : "default"}
diff --git a/src/components/clinical-dashboard/global-search-shell.tsx b/src/components/clinical-dashboard/global-search-shell.tsx
index 651a4cb0c3..84247b396a 100644
--- a/src/components/clinical-dashboard/global-search-shell.tsx
+++ b/src/components/clinical-dashboard/global-search-shell.tsx
@@ -877,6 +877,7 @@ function GlobalStandaloneSearchShellBody({
differentialsCompareAddonActive ? differentialsMobileCompareAddonSlotId : undefined
}
desktopSearchPlacement={desktopSearchPlacement === "hero" && isStandaloneModeHome ? "hero" : "default"}
+ showPhoneSuggestionTickerOnHome={isStandaloneModeHome || pathname === "/"}
searchComposerVisible={shouldShowSearchComposer}
desktopHomeComposerSlotId={isStandaloneModeHome ? modeHomeDesktopComposerSlotId : undefined}
desktopPageComposerSlotId={
diff --git a/src/components/clinical-dashboard/master-search-header.tsx b/src/components/clinical-dashboard/master-search-header.tsx
index 1cc6aa23e3..e87b6c29a8 100644
--- a/src/components/clinical-dashboard/master-search-header.tsx
+++ b/src/components/clinical-dashboard/master-search-header.tsx
@@ -195,6 +195,7 @@ export function MasterSearchHeader({
mobileBottomSearchVariant = "default",
desktopSearchPlacement = "default",
searchComposerVisible = true,
+ showPhoneSuggestionTickerOnHome = false,
desktopHomeComposerSlotId,
desktopPageComposerSlotId,
heroComposerBreakpoint = "all",
@@ -250,6 +251,8 @@ export function MasterSearchHeader({
* content keeps maximum screen space. Every phone dock uses it now; the
* "default" value remains for hosts that need the taller legacy dock. */
mobileBottomSearchVariant?: "default" | "compact";
+ /** Show the compact phone suggestion ticker only for standalone-mode homes. */
+ showPhoneSuggestionTickerOnHome?: boolean;
desktopSearchPlacement?: "default" | "hero";
searchComposerVisible?: boolean;
/** Mode-home slot the composer portals into so the search pill sits in the
@@ -1816,6 +1819,7 @@ export function MasterSearchHeader({
onListboxIdReady={setCommandListboxId}
onActiveItemIdChange={setCommandActiveItemId}
onFocusSearchInput={handleFocusSearchInput}
+ showPhoneSuggestionTicker={showPhoneSuggestionTickerOnHome}
>
void;
+}) {
const [activeExampleIndex, setActiveExampleIndex] = useState(0);
+ const [heldTickerExample, setHeldTickerExample] = useState
(null);
+ const [isTickerHeld, setIsTickerHeld] = useState(false);
const activeExample = examples[activeExampleIndex % examples.length];
useEffect(() => {
+ if (isTickerHeld) {
+ return;
+ }
+
if (examples.length <= 1) return;
const intervalId = window.setInterval(() => {
setActiveExampleIndex((current) => (current + 1) % examples.length);
}, SMART_HINT_ROTATION_MS);
return () => window.clearInterval(intervalId);
- }, [examples]);
+ }, [examples, isTickerHeld]);
+
+ const freezeTicker = useCallback(() => {
+ setHeldTickerExample(activeExample);
+ setIsTickerHeld(true);
+ }, [activeExample]);
+
+ const releaseTicker = useCallback(() => {
+ setIsTickerHeld(false);
+ }, []);
+
+ const resolvedTickerExample = isTickerHeld ? (heldTickerExample ?? activeExample) : activeExample;
if (!activeExample) return null;
return (
-
- Smart search
- ·
-
- Try “{activeExample}” in {modeLabel}.
-
-
+ <>
+
+ Smart search
+ ·
+
+ Try “{activeExample}” in {modeLabel}.
+
+
+ {showPhoneTicker ? (
+
+ ) : null}
+ >
);
}
@@ -364,6 +430,7 @@ export function UniversalSearchCommandSurface({
onFocusSearchInput,
onListboxIdReady,
onActiveItemIdChange,
+ showPhoneSuggestionTicker = false,
placement = "inline",
children,
}: {
@@ -384,6 +451,8 @@ export function UniversalSearchCommandSurface({
onFocusSearchInput?: () => void;
onListboxIdReady?: (listboxId: string) => void;
onActiveItemIdChange?: (activeItemId: string | null) => void;
+ /** Show the compact, tappable suggestion ticker below an in-flow phone home composer. */
+ showPhoneSuggestionTicker?: boolean;
placement?: CommandSurfacePlacement;
children: ReactNode;
}) {
@@ -992,7 +1061,15 @@ export function UniversalSearchCommandSurface({
placement === "bottom-dock" ? "gap-1" : "gap-2",
)}
>
-
+ {
+ onQueryChange(example);
+ onFocusSearchInput?.();
+ }}
+ />
{
diff --git a/tests/ui-overlap.spec.ts b/tests/ui-overlap.spec.ts
index 79923b3f6a..f04fb4bb7c 100644
--- a/tests/ui-overlap.spec.ts
+++ b/tests/ui-overlap.spec.ts
@@ -266,12 +266,43 @@ test.describe("Header element overlap coverage", () => {
);
});
- test("phone smart search does not show the desktop rotating text or prompt row", async ({ page }) => {
+ test("phone smart search replaces desktop rows with one tappable ticker", async ({ page }) => {
await page.setViewportSize({ width: 390, height: 820 });
await mockDemoDashboard(page);
await gotoHome(page);
await expect(page.getByTestId("smart-search-rotating-text")).toBeHidden();
await expect(page.getByTestId("smart-search-prompt-row")).toBeHidden();
+
+ const ticker = page.getByTestId("smart-search-phone-ticker");
+ await expect(ticker).toBeVisible();
+ await expect(ticker).toContainText("Try this");
+ await expect(ticker).toContainText("Tap to search");
+
+ const tickerBox = await ticker.boundingBox();
+ expect(tickerBox, "phone suggestion ticker must render").not.toBeNull();
+ expect(tickerBox!.height, "phone ticker must meet the tap-target floor").toBeGreaterThanOrEqual(48);
+
+ const suggestion = (await ticker.getAttribute("aria-label"))?.replace("Try suggested search: ", "");
+ expect(suggestion).toBeTruthy();
+ await ticker.click();
+ await expect(page.locator('[data-testid="global-search-input"]:visible').first()).toHaveValue(suggestion ?? "");
+ });
+
+ test("phone suggestion ticker renders on /documents mode home", async ({ page }) => {
+ await page.setViewportSize({ width: 390, height: 820 });
+ await mockDemoDashboard(page);
+ await page.goto("/documents", { waitUntil: "domcontentloaded" });
+ await expect(async () => {
+ const header = page.locator("header#search");
+ await expect(header).toHaveCount(1);
+ await expect(header).toBeVisible();
+ }).toPass({ timeout: 30_000 });
+
+ const ticker = page.getByTestId("smart-search-phone-ticker");
+ await expect(ticker).toBeVisible();
+ const tickerBox = await ticker.boundingBox();
+ expect(tickerBox, "phone suggestion ticker must render on /documents home").not.toBeNull();
+ expect(tickerBox!.height, "phone ticker must meet the tap-target floor on /documents").toBeGreaterThanOrEqual(48);
});
});