Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 14 additions & 13 deletions docs/search-chrome-behaviour.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,19 +4,20 @@ This repo uses one shared search experience across the global shell, dashboard r

## Page ownership model

| Page state | Composer placement | Reserve owner |
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| Shared home (`/`, any mode) / standalone mode homes | In-flow hero composer on phones and larger breakpoints | Page content; no fixed phone dock reserve |
| Tools directory (`/tools`) and legacy alias (`/?mode=tools`) | No shared composer or phone dock; browse and filter through page-local catalogue controls | Idle shell padding only |
| Therapy Recommend (`/therapy-compass/recommend`) | In-flow clinical-situation composer; no shared composer or phone dock | Idle shell padding only |
| Submitted/search-result views | Compact bottom dock on phones; in normal page flow on tablets and desktops | Shell/dashboard `--mobile-composer-reserve` on phones; page content on desktop |
| Answer result view | Overlaid glass header plus answer composer dock | Dashboard `#main-content` top/bottom reserves |
| Document detail/source routes | `DocumentViewer` floating composer | `DocumentViewer` content padding |
| Document section navigation | Header row disclosure (phone sheet) + rail index card at `lg` | None — adds no chrome and no reserve |
| Record page breadcrumb header | Same header row without the disclosure or track; view mode inline from `sm` | None — portals into the phone collapse row, sticky at `sm+` |
| Calculators (`/calculators`) | In-flow hero composer at home; shared compact dock on `/calculators/search` (browse or submitted) | Page content at home; shell reserve for the catalogue and submitted results |
| Info/detail pages with no composer | No fixed composer | Idle shell padding only |
| Guide Centre dialog (`GuideDialog`) | No composer — tour-action dock inside the Sheet footer; Sheet footer band from `sm` | `[data-guide-content]` bottom pad (`guide-tour-dock` reserve owner) |
| Page state | Composer placement | Reserve owner |
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| Shared home (`/`, any mode) / standalone mode homes | In-flow hero composer on phones and larger breakpoints | Page content; no fixed phone dock reserve |
| Tools directory (`/tools`) and legacy alias (`/?mode=tools`) | No shared composer or phone dock; browse and filter through page-local catalogue controls | Idle shell padding only |
| Therapy Recommend (`/therapy-compass/recommend`) | In-flow clinical-situation composer; no shared composer or phone dock | Idle shell padding only |
| Submitted/search-result views | Compact bottom dock on phones; in normal page flow on tablets and desktops | Shell/dashboard `--mobile-composer-reserve` on phones; page content on desktop |
| Answer result view | Overlaid glass header plus answer composer dock | Dashboard `#main-content` top/bottom reserves |
| Document detail/source routes | `DocumentViewer` floating composer | `DocumentViewer` content padding |
| Document section navigation | Header row disclosure (phone sheet) + rail index card at `lg` | None — adds no chrome and no reserve |
| Record page breadcrumb header | Same header row without the disclosure or track; view mode inline from `sm` | None — portals into the phone collapse row, sticky at `sm+` |
| Calculators (`/calculators`) | In-flow hero composer at home; shared compact dock on `/calculators/search` (browse or submitted) | Page content at home; shell reserve for the catalogue and submitted results |
| Dictionary catalogue (`/dictionary/search`) | Compact bottom dock on phones; in-flow shared composer from `sm` up, under mode nav and above the Filter band | Shell `--mobile-composer-reserve` on phones; page content on desktop |
| Info/detail pages with no composer | No fixed composer | Idle shell padding only |
| Guide Centre dialog (`GuideDialog`) | No composer — tour-action dock inside the Sheet footer; Sheet footer band from `sm` | `[data-guide-content]` bottom pad (`guide-tour-dock` reserve owner) |

The Tools row is scoped to the **mounted Tools directory**, not to `resultKind: "tools"`. Factsheets,
Dictionary and Therapy Compass borrow that result kind purely as a benign search kind, and on the
Expand Down
22 changes: 17 additions & 5 deletions src/components/clinical-dashboard/global-search-shell.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -113,6 +113,7 @@ import { readSearchNavigationContext, type SearchNavigationOptions } from "@/lib
import {
isAlwaysStandaloneShellPath,
isDashboardOwnedModeHomePath,
isDictionaryCataloguePath,
isStandaloneModeHomePath,
shouldRenderClinicalDashboard,
shouldRenderDashboardSearch,
Expand DownExpand Up@@ -468,6 +469,7 @@ function GlobalStandaloneSearchShellBody({
// searchMode before router.push landed, which made isStandaloneModeHome false
// for one frame (dock reserve + 200ms padding transition = choppy resize).
const isStandaloneModeHome = !hasSubmittedModeSearch && !rendersDashboardSearch && isStandaloneModeHomePath(pathname);
const isDictionaryCatalogue = isDictionaryCataloguePath(pathname);
const isDifferentialPresentationWorkflow = pathname.startsWith("/differentials/presentations/");
const shouldShowDesktopSidebar = !hideDesktopSidebar;
const effectiveSidebarCollapsed = isDifferentialPresentationWorkflow ? true : sidebarCollapsed;
Expand All@@ -493,7 +495,9 @@ function GlobalStandaloneSearchShellBody({
// This flag controls sm+ padding for standalone mode homes. Tools has no
// shared composer, so it cannot reserve floating-composer space. Phone
// clearance is resolved separately from heroOwnsPhoneComposer below.
const reservesFloatingComposer = shouldShowSearchComposer && !isStandaloneModeHome;
// Dictionary catalogue keeps the usual compact phone dock; sm+ still
// portals into the in-page slot under mode nav (`desktopHomeComposerSlotId`).
const reservesFloatingComposer = shouldShowSearchComposer && !isStandaloneModeHome && !isDictionaryCatalogue;
// Most standalone mode homes keep the in-flow hero pill at every width. Tools
// deliberately has no shared composer. Document viewer routes own their own
// floating composer, so
Expand DownExpand Up@@ -963,13 +967,21 @@ function GlobalStandaloneSearchShellBody({
desktopSearchPlacement={desktopSearchPlacement === "hero" && isStandaloneModeHome ? "hero" : "default"}
showPhoneSuggestionTickerOnHome={isStandaloneModeHome || (pathname === "/" && !hasSubmittedModeSearch)}
searchComposerVisible={shouldShowSearchComposer}
desktopHomeComposerSlotId={isStandaloneModeHome ? modeHomeDesktopComposerSlotId : undefined}
desktopHomeComposerSlotId={
isStandaloneModeHome || isDictionaryCatalogue ? modeHomeDesktopComposerSlotId : undefined
}
desktopPageComposerSlotId={
shouldShowSearchComposer && !isStandaloneModeHome ? desktopPageComposerSlotId : undefined
shouldShowSearchComposer && !isStandaloneModeHome && !isDictionaryCatalogue
? desktopPageComposerSlotId
: undefined
}
// Most standalone homes keep the in-flow hero pill at every width.
// Tools suppresses the shared composer at every breakpoint.
heroComposerBreakpoint={mobileHomeComposerPlacement === "footer" ? "sm-up" : "all"}
// Dictionary catalogue uses the usual compact phone dock; sm+
// still portals into the in-page slot under mode nav.
heroComposerBreakpoint={
mobileHomeComposerPlacement === "footer" || isDictionaryCatalogue ? "sm-up" : "all"
}
// Phones: #main-content owns vertical scroll, so hide-on-scroll
// collapses the top bar to hand space back to content.
// Tablet and desktop portal search into normal page flow. The outer
Expand DownExpand Up@@ -1046,7 +1058,7 @@ function GlobalStandaloneSearchShellBody({
data-testid="mobile-composer-reserve-pad"
className="max-sm:pt-[var(--phone-overlay-chrome-h)] max-sm:pb-[var(--mobile-composer-reserve)]"
>
{shouldShowSearchComposer && !isStandaloneModeHome ? (
{shouldShowSearchComposer && !isStandaloneModeHome && !isDictionaryCatalogue ? (
<DesktopComposerPortalSlot
id={desktopPageComposerSlotId}
data-testid="desktop-page-search-composer-slot"
Expand Down
41 changes: 30 additions & 11 deletions src/components/clinical-dashboard/search-results-header-band.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -147,6 +147,8 @@ export function SearchResultsHeaderBand({
filterLabel = "Filter search results",
headingLevel = 2,
resultNoun: resultNounOverride,
hideEmptyQuery = false,
emptyQueryLabel,
className,
}: {
modeId: AppModeId;
Expand DownExpand Up@@ -207,6 +209,14 @@ export function SearchResultsHeaderBand({
filterLabel?: string;
/** Use level 1 when the ribbon is the route's primary page heading. */
headingLevel?: 1 | 2;
/**
* Hide the query subject when `query` is empty instead of falling back to
* "All". Dictionary browse keeps this band as count + Filter with no
* invented query chip.
*/
hideEmptyQuery?: boolean;
/** Accessible region name used when `hideEmptyQuery` omits the subject. */
emptyQueryLabel?: string;
/**
* What this count counted, when the mode registry's noun is not specific
* enough to be true.
Expand All@@ -222,7 +232,10 @@ export function SearchResultsHeaderBand({
resultNoun?: string;
className?: string;
}) {
const displayQuery = query.trim() || "All";
const trimmedQuery = query.trim();
const displayQuery = trimmedQuery || "All";
const showQuerySubject = Boolean(trimmedQuery) || !hideEmptyQuery;
const regionLabel = showQuerySubject ? `Search results for ${displayQuery}` : (emptyQueryLabel ?? "Catalogue");
// `status` wins when both are passed; `loading` is the deprecated shim.
const resolvedStatus: SearchResultsBandStatus = status ?? (loading ? "loading" : "ready");
// The clinical invariant, expressed once: a search that failed has no count to
Expand DownExpand Up@@ -297,7 +310,7 @@ export function SearchResultsHeaderBand({

return (
<section
aria-label={`Search results for ${displayQuery}`}
aria-label={regionLabel}
aria-busy={busy}
data-status={resolvedStatus}
data-testid="search-query-ribbon"
Expand DownExpand Up@@ -437,15 +450,21 @@ export function SearchResultsHeaderBand({
always rendered rather than appearing on scroll — conditional chrome
is what this band spent its last redesign removing — and it is the
part that truncates when the line runs out, never the number. */}
<span className="search-band-rule mx-0.5 h-[1.125rem] w-px shrink-0" aria-hidden />
<QueryHeading
// `min-w-[2rem]` keeps a non-empty box at 320px, so the heading stays
// findable and visible when a wide page control squeezes the line.
className="search-band-subject min-w-[2rem] truncate text-[color:var(--text-muted)] lg:max-w-[24rem]"
title={displayQuery}
>
{displayQuery}
</QueryHeading>
{showQuerySubject ? (
<>
<span className="search-band-rule mx-0.5 h-[1.125rem] w-px shrink-0" aria-hidden />
<QueryHeading
// `min-w-[2rem]` keeps a non-empty box at 320px, so the heading stays
// findable and visible when a wide page control squeezes the line.
className="search-band-subject min-w-[2rem] truncate text-[color:var(--text-muted)] lg:max-w-[24rem]"
title={displayQuery}
>
{displayQuery}
</QueryHeading>
</>
) : (
<span className="min-w-0 flex-1" aria-hidden="true" />
)}
</div>

{hasUtilities ? (
Expand Down
Loading
Loading