diff --git a/src/app/globals.css b/src/app/globals.css
index 0dd4d243f4..12f7efaff9 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -1927,6 +1927,7 @@ summary::-webkit-details-marker {
}
.answer-footer-search-input {
+ appearance: none;
padding-inline: 0.35rem;
font-size: 16px;
font-weight: 560;
@@ -1934,6 +1935,11 @@ summary::-webkit-details-marker {
letter-spacing: 0;
}
+.answer-footer-search-input::-webkit-search-cancel-button,
+.answer-footer-search-input::-webkit-search-decoration {
+ appearance: none;
+}
+
/* The universal composer form is the visible control and already owns its
focus-within border + halo. Suppress the nested text field's global focus
paint so the pill never renders competing or clipped concentric rings. */
diff --git a/src/components/clinical-dashboard/master-search-header.tsx b/src/components/clinical-dashboard/master-search-header.tsx
index 32cac92f3f..85e7811c75 100644
--- a/src/components/clinical-dashboard/master-search-header.tsx
+++ b/src/components/clinical-dashboard/master-search-header.tsx
@@ -2036,6 +2036,7 @@ export function MasterSearchHeader({
pr-* utility, which let text run under an overlaid button. */}
-
+
{/* Eager, not lazy: for an image-source document this *is* the document,
and it sits above the fold. Lazy-loading it puts the largest element
on the page behind the browser's own lazy threshold and delays LCP
@@ -168,7 +171,7 @@ function InlineImagePreview({
decoding="async"
fetchPriority="high"
onError={() => setFailed(true)}
- className="mx-auto max-h-[min(70vh,36rem)] w-full object-contain"
+ className="h-full w-full object-contain"
/>
{block.items.map((fact) => (
-
+
{fact.k}
-
+
{fact.v}
diff --git a/src/components/ward-management/ward-management-modes.tsx b/src/components/ward-management/ward-management-modes.tsx
index 9b0d1e6ef5..67b3b636af 100644
--- a/src/components/ward-management/ward-management-modes.tsx
+++ b/src/components/ward-management/ward-management-modes.tsx
@@ -439,13 +439,13 @@ function QueueView({ role }: { role: WardRole }) {
- Patient
- Priority
- Wait
- Need
- Health service
- Blocker
- Top candidate
+ Patient
+ Priority
+ Wait
+ Need
+ Health service
+ Blocker
+ Top candidate
@@ -515,11 +515,11 @@ function CapacityView() {
- Unit
- Health service
- Capability cue
- Five bed states
- Freshness
+ Unit
+ Health service
+ Capability cue
+ Five bed states
+ Freshness
diff --git a/tests/document-viewer-non-pdf-preview.dom.test.tsx b/tests/document-viewer-non-pdf-preview.dom.test.tsx
index 768dbd49f0..147cf82637 100644
--- a/tests/document-viewer-non-pdf-preview.dom.test.tsx
+++ b/tests/document-viewer-non-pdf-preview.dom.test.tsx
@@ -34,6 +34,11 @@ describe("DocumentViewer non-PDF image preview", () => {
);
expect(screen.queryByTestId("image-lightbox")).not.toBeInTheDocument();
+ expect(screen.getByTestId("non-pdf-image-stage")).toHaveClass(
+ "h-[clamp(16rem,60vh,36rem)]",
+ "sm:h-[clamp(18rem,60vh,36rem)]",
+ );
+ expect(screen.getByRole("img", { name: "Clinical chart" })).toHaveClass("h-full", "w-full", "object-contain");
fireEvent.click(screen.getByRole("button", { name: "Expand image: Clinical chart" }));
expect(await screen.findByTestId("image-lightbox")).toBeInTheDocument();
expect(screen.getByTestId("image-lightbox-stage")).toHaveAttribute("data-source-mode", "url");
diff --git a/tests/factsheet-detail-header.dom.test.tsx b/tests/factsheet-detail-header.dom.test.tsx
index 43fa06631e..d0e72818e3 100644
--- a/tests/factsheet-detail-header.dom.test.tsx
+++ b/tests/factsheet-detail-header.dom.test.tsx
@@ -59,6 +59,23 @@ describe("factsheet detail header", () => {
expect(within(printPortal as HTMLElement).getAllByRole("heading", { level: 2 }).length).toBeGreaterThan(0);
});
+ it("associates printable fact values with row headers", () => {
+ renderFactsheet("sertraline");
+ const printPortal = document.querySelector(".factsheet-print-portal");
+ expect(printPortal).not.toBeNull();
+
+ // Assert per row, not over the set of headers that happen to exist: a fact row
+ // rendered with no row header at all would leave its value unassociated in a
+ // screen reader, and a headers-only assertion passes straight over it.
+ const rows = within(printPortal as HTMLElement).getAllByRole("row");
+ expect(rows.length).toBeGreaterThan(0);
+ for (const row of rows) {
+ const rowHeaders = within(row).getAllByRole("rowheader");
+ expect(rowHeaders).toHaveLength(1);
+ expect(rowHeaders[0]).toHaveAttribute("scope", "row");
+ }
+ });
+
it("names the way back without spending the row on its label", () => {
renderFactsheet("sertraline");
const back = screen.getByRole("link", { name: "Back to all factsheets" });
diff --git a/tests/mode-menu-prefetch.dom.test.tsx b/tests/mode-menu-prefetch.dom.test.tsx
index 1a12cf09d1..229dfc3890 100644
--- a/tests/mode-menu-prefetch.dom.test.tsx
+++ b/tests/mode-menu-prefetch.dom.test.tsx
@@ -90,6 +90,11 @@ describe("mode menu destination prefetch", () => {
window.localStorage.clear();
});
+ it("exposes the shared composer as a semantic search input", () => {
+ render( );
+ expect(screen.getByTestId("global-search-input")).toHaveAttribute("type", "search");
+ });
+
it("keeps calculator submission enabled when document data is unavailable", async () => {
const user = userEvent.setup();
const onAsk = vi.fn();
diff --git a/tests/ui-universal-search.spec.ts b/tests/ui-universal-search.spec.ts
index 05da62d9ae..debf6acab5 100644
--- a/tests/ui-universal-search.spec.ts
+++ b/tests/ui-universal-search.spec.ts
@@ -208,6 +208,22 @@ test.describe("universal search typeahead", () => {
expect(await input.getAttribute("aria-activedescendant")).toBeNull();
});
+ test("Escape dismisses the dropdown without erasing the typed query", async ({ page }) => {
+ // The composer input is `type="search"`, whose native Chromium Escape gesture
+ // clears the field. Escape must only dismiss the dropdown; the query stays put
+ // so a reader can reopen or edit it instead of retyping from scratch.
+ await mockUniversalSearch(page);
+ const input = await openComposer(page);
+ await input.fill("acamprosate");
+
+ const listbox = page.getByRole("listbox", { name: "Documents search suggestions" });
+ await expect(listbox).toBeVisible();
+
+ await input.press("Escape");
+ await expect(listbox).toBeHidden();
+ await expect(input).toHaveValue("acamprosate");
+ });
+
test("does not count document-only hits as visible Medication rows", async ({ page }) => {
await page.route(/\/api\/search\/universal(?:\?.*)?$/, async (route) => {
await fulfillUniversalSearch(route, {
diff --git a/tests/ward-management.test.ts b/tests/ward-management.test.ts
index b81f490e8f..14f8e91805 100644
--- a/tests/ward-management.test.ts
+++ b/tests/ward-management.test.ts
@@ -7,6 +7,8 @@ import { movementById, wardMovements } from "../src/components/ward-management/w
import { NOW_ANCHOR, allUnits } from "../src/components/ward-management/ward-sites";
import { toolCatalogRecordById } from "../src/lib/tools-catalog";
+const modesSource = readFileSync("src/components/ward-management/ward-management-modes.tsx", "utf8");
+
/**
* The mode strip renders one literal ` ` per view so
* `tests/route-reachability.test.ts` can find the hrefs by static AST scan. Reading the
@@ -23,6 +25,11 @@ function routeFileFor(href: string) {
}
describe("Ward Flow synthetic prototype", () => {
+ it("declares every queue and capacity header as a column header", () => {
+ expect(modesSource.match(//g)).toHaveLength(12);
+ expect(modesSource).not.toMatch(/ /);
+ });
+
it("keeps the production route reachable from the Tools catalogue", () => {
expect(toolCatalogRecordById("ward-management").href).toBe("/ward-management");
});