diff --git a/data/repo-awareness-snapshot.json b/data/repo-awareness-snapshot.json
index 200c7d2e82..0e7ba7fed8 100644
--- a/data/repo-awareness-snapshot.json
+++ b/data/repo-awareness-snapshot.json
@@ -1,8 +1,8 @@
{
"version": "repo-awareness-snapshot-v1",
"captured_revision": {
- "sha": "d2c61c8cbddfb096e7b86e4dae1983dd1266a356",
- "committed_at": "2026-08-27T09:12:09+08:00"
+ "sha": "d9a2e0619cd30539b5dc4a433da0d97cc8f618ed",
+ "committed_at": "2026-08-27T10:05:30+08:00"
},
"routes": {
"modes": [
@@ -3805,6 +3805,14 @@
},
"review_state": {
"records": [
+ {
+ "date": "2026-08-27",
+ "ref": "2398",
+ "head": "4228b6f91a1d130b6751fd60bdee86e673cb6ddd",
+ "scope": "pr-babysit sweep: merge conflicts, review threads, snapshot CI fix",
+ "outcome": "FIXED",
+ "checks": "merge-main, review-replies, thread-resolve, snapshot-regen"
+ },
{
"date": "2026-08-26",
"ref": "claude/dev-hub-handoff-accuracy (PR #2382)",
@@ -24831,8 +24839,8 @@
}
],
"counts": {
- "records": 2628,
- "refs": 1605
+ "records": 2629,
+ "refs": 1606
}
}
}
diff --git a/docs/branch-review-records/ca7b9dde3719ecff0347e226a3c7902d6e2aee8a619eb187fff0c63958889a5b.record.md b/docs/branch-review-records/ca7b9dde3719ecff0347e226a3c7902d6e2aee8a619eb187fff0c63958889a5b.record.md
new file mode 100644
index 0000000000..cdb860fcf6
--- /dev/null
+++ b/docs/branch-review-records/ca7b9dde3719ecff0347e226a3c7902d6e2aee8a619eb187fff0c63958889a5b.record.md
@@ -0,0 +1 @@
+| 2026-08-27 | 2398 | 4228b6f91a1d130b6751fd60bdee86e673cb6ddd | pr-babysit sweep: merge conflicts, review threads, snapshot CI fix | FIXED | merge-main, review-replies, thread-resolve, snapshot-regen |
diff --git a/src/components/clinical-dashboard/ClinicalSidebar.tsx b/src/components/clinical-dashboard/ClinicalSidebar.tsx
index 1edfbb4dbd..7b88a3551d 100644
--- a/src/components/clinical-dashboard/ClinicalSidebar.tsx
+++ b/src/components/clinical-dashboard/ClinicalSidebar.tsx
@@ -76,8 +76,8 @@ const sidebarToolItems = [
// redirects onto it, so pointing a pinned entry at `/services` or `/factsheets`
// would spend a round trip arriving at the same place.
{ id: "services", label: "Services", icon: appModeIcons.services, href: "/?mode=services" },
- // Medication owns a real home: /medications is the prescribing workspace,
- // not a consolidated 307 onto /?mode=prescribing (the shared empty home).
+ // Medication owns a real home at /medications; it is not a consolidated-mode
+ // redirect onto /?mode=prescribing (the shared empty home).
{ id: "prescribing", label: appModeDefinition("prescribing").label, icon: Pill, href: "/medications" },
{ id: "factsheets", label: "Factsheets", icon: appModeIcons.factsheets, href: "/?mode=factsheets" },
// PT-11: standalone /tools is the canonical entry; /?mode=tools remains a dashboard-mode alias.
diff --git a/src/components/therapy-compass/record/key-facts.tsx b/src/components/therapy-compass/record/key-facts.tsx
index c15a01058a..fb64b56118 100644
--- a/src/components/therapy-compass/record/key-facts.tsx
+++ b/src/components/therapy-compass/record/key-facts.tsx
@@ -6,7 +6,7 @@ import { useState } from "react";
import { cn, textMuted } from "@/components/ui-primitives";
import { Sheet } from "@/components/ui/sheet";
-import { therapyBtn } from "../controls";
+import { interactiveRowBase } from "@/components/ui/interactive-row";
import { ProseBlock } from "../prose";
import type { Therapy } from "../data/types";
import { therapyKeyFactCards, type TherapyKeyFactCard, type TherapyKeyFactId } from "./key-fact-cards";
@@ -69,7 +69,7 @@ function FactCard({ card, onOpen }: { card: TherapyKeyFactCard; onOpen?: () => v
type="button"
onClick={onOpen}
aria-haspopup="dialog"
- className={cn(therapyBtn, "flex min-h-12 min-w-0 flex-1 flex-col rounded-md text-left")}
+ className={cn(interactiveRowBase, "flex min-h-12 min-w-0 flex-1 flex-col items-start rounded-md text-left")}
aria-label={`${card.label}: ${card.face}. Open detail.`}
>
{header}
diff --git a/src/components/ui/disclosure.tsx b/src/components/ui/disclosure.tsx
index 368cd3ed98..9e9430b085 100644
--- a/src/components/ui/disclosure.tsx
+++ b/src/components/ui/disclosure.tsx
@@ -69,9 +69,12 @@ export function Disclosure({
onOpenChange?.(next);
}
+ const dataState = open ? "expanded" : "collapsed";
+
return (
{
const trigger = within(section).getByRole("button", { name: "Does not authorise" });
expect(trigger).toHaveAccessibleName("Does not authorise");
expect(trigger).toHaveAttribute("aria-expanded", "false");
+ expect(trigger).toHaveAttribute("data-state", "collapsed");
const panelId = trigger.getAttribute("aria-controls");
expect(panelId).toEqual(expect.any(String));
@@ -43,12 +44,15 @@ describe("Form information disclosures", () => {
const panel = document.getElementById(panelId);
expect(panel).toBeTruthy();
if (!panel) return;
+ expect(panel).toHaveAttribute("data-state", "collapsed");
expect(panel).not.toHaveAttribute("hidden");
expect(panel).toHaveClass("hidden", "print:block");
await user.click(trigger);
expect(trigger).toHaveAttribute("aria-expanded", "true");
+ expect(trigger).toHaveAttribute("data-state", "expanded");
+ expect(panel).toHaveAttribute("data-state", "expanded");
expect(within(trigger).queryByText(fullText)).not.toBeInTheDocument();
expect(panel).not.toHaveClass("hidden");
expect(panel).not.toHaveClass("border-t");
diff --git a/tests/ui-caring-contacts-workspace.spec.ts b/tests/ui-caring-contacts-workspace.spec.ts
index e6e96b3238..e8fc6e44ae 100644
--- a/tests/ui-caring-contacts-workspace.spec.ts
+++ b/tests/ui-caring-contacts-workspace.spec.ts
@@ -423,19 +423,17 @@ test.describe("caring-contacts patients directory", () => {
* ------------------------------------------------------------------------- */
/**
- * The two widths the overlay matrix is proved at.
+ * The two widths the overlay matrix is proved at for pure phone and desktop modalities.
*
* 390 samples `compact` (phone modalities) and 1440 samples `wide` (desktop
* modalities), which is every branch the host's modality decision has.
*
- * Ruling 60 — deliberately NOT sampled, and it must stay that way. Between 640
- * and 767 the stamped modality and the shared Sheet's own geometry breakpoint
- * disagree: `widthStateFor` switches compact→rail at 768, while `Sheet` switches
- * to a centred dialog at Tailwind `sm:` = 640, so a `bottom-sheet` row in that
- * band stamps `bottom-sheet` and renders as a dialog. That divergence is pinned
- * by `tests/caring-contacts-overlay-host.dom.test.tsx` and left in place on
- * purpose; adding a width here to chase it would turn an owner's design-record
- * question into a red gate.
+ * Ruling 60 modal/sheet breakpoint contract testing: between 640 and 767 the stamped
+ * modality and the shared Sheet's own geometry breakpoint disagree: `widthStateFor`
+ * switches compact→rail at 768, while `Sheet` switches to a centred dialog at
+ * Tailwind `sm:` = 640, so a `bottom-sheet` row in that band stamps `bottom-sheet`
+ * and renders as a dialog. This divergence is explicitly verified across 640px,
+ * 700px, and 767px in the dedicated Ruling 60 suite below.
*/
const OVERLAY_MATRIX_WIDTHS = [390, 1440] as const;
@@ -713,6 +711,123 @@ test.describe("caring-contacts workspace overlays", () => {
}
});
+/**
+ * Ruling 60 — modal vs bottom-sheet behavior across the 640px–767px band.
+ *
+ * `widthStateFor` returns "compact" up to 767px (rail breakpoint is 768px), so OverlayHost
+ * stamps `data-overlay-modality="bottom-sheet"`. The shared Sheet component switches from
+ * bottom-sheet to centred dialog modal at Tailwind `sm:` = 640px.
+ *
+ * The assertions below verify the exact contract across 640px, 700px, and 767px:
+ * 1. The stamped modality remains "bottom-sheet" as specified in the frozen definitions.
+ * 2. The rendered geometry is a centred modal dialog (constrained width <= 640px,
+ * not anchored to the bottom edge, drag grip hidden).
+ * 3. Full-screen stages remain full screen across the band (they transition at `lg:` = 1024px).
+ * 4. Decision control stays fully visible in the viewport and Escape dismissal is respected.
+ */
+const RULING_60_BAND_WIDTHS = [640, 700, 767] as const;
+
+test.describe("caring-contacts overlay Ruling 60 modal vs bottom-sheet breakpoint contract", () => {
+ for (const width of RULING_60_BAND_WIDTHS) {
+ test(`verifies bottom-sheet rows render as centred dialog modals at ${width}px`, async ({ page }) => {
+ test.setTimeout(120_000);
+
+ const bottomSheetRows = WORKSPACE_OVERLAY_DEFINITIONS.filter(
+ (definition) => definition.phoneModality === "bottom-sheet",
+ );
+
+ for (const definition of bottomSheetRows) {
+ const label = `${definition.id} at ${width}px`;
+ const content = await deepLinkOverlay(page, width, definition.id);
+
+ expect(overlayParamOf(page), `${label}: URL query parameter`).toBe(definition.id);
+ expect(widthStateFor(width), `${label}: widthStateFor must be compact`).toBe("compact");
+
+ await expect(content, `${label}: stamped modality`).toHaveAttribute("data-overlay-modality", "bottom-sheet");
+ await expect(content, `${label}: stamped dismissal`).toHaveAttribute(
+ "data-overlay-dismissal",
+ definition.dismissal,
+ );
+
+ const sheetSurface = page.getByTestId("workspace-overlay-sheet");
+ const box = await sheetSurface.boundingBox();
+ expect(box, `${label}: the overlay surface has no box`).not.toBeNull();
+ expectFullyOnScreen(box!, width, label);
+
+ // Modal geometry assertions:
+ // Width is constrained to dialog max width and narrower than viewport width
+ expect(box!.width, `${label}: modal width should not exceed dialog max width`).toBeLessThanOrEqual(
+ DIALOG_MAX_WIDTH + EDGE_TOLERANCE,
+ );
+ expect(box!.width, `${label}: modal should be narrower than viewport width`).toBeLessThan(width);
+
+ // Centered vertically, not bottom-anchored
+ expect(
+ box!.y + box!.height,
+ `${label}: modal should be vertically centred, not anchored to the bottom edge`,
+ ).toBeLessThan(VIEWPORT_HEIGHT - 4);
+ expect(box!.y, `${label}: modal should not touch the top edge`).toBeGreaterThan(4);
+
+ // Mobile drag grip is hidden on dialog modal
+ const dragGrip = sheetSurface.locator(".cursor-grab");
+ await expect(dragGrip, `${label}: drag handle should be hidden on modal`).toBeHidden();
+
+ // Decision action is fully in viewport
+ await expect(
+ content.getByTestId("workspace-overlay-action"),
+ `${label}: decision control is not fully in the viewport`,
+ ).toBeInViewport({ ratio: 1 });
+
+ await page.keyboard.press("Escape");
+ if (dismissesOnEscape(definition.dismissal)) {
+ await expect(content, `${label}: Escape did not close it`).toHaveCount(0);
+ await expect
+ .poll(() => overlayParamOf(page), { message: `${label}: Escape left the id in the URL` })
+ .toBeNull();
+ } else {
+ await page.waitForTimeout(300);
+ await expect(content, `${label}: Escape dismissed a recovery-only overlay`).toHaveCount(1);
+ expect(overlayParamOf(page), `${label}: Escape cleared recovery-only id`).toBe(definition.id);
+ }
+ }
+ });
+
+ test(`verifies full-screen-stage rows remain fullscreen across the ${width}px band`, async ({ page }) => {
+ test.setTimeout(120_000);
+
+ const stageRows = WORKSPACE_OVERLAY_DEFINITIONS.filter(
+ (definition) => definition.phoneModality === "full-screen-stage",
+ );
+
+ for (const definition of stageRows) {
+ const label = `${definition.id} at ${width}px`;
+ const content = await deepLinkOverlay(page, width, definition.id);
+
+ await expect(content, `${label}: stamped modality`).toHaveAttribute(
+ "data-overlay-modality",
+ "full-screen-stage",
+ );
+ const sheetSurface = page.getByTestId("workspace-overlay-sheet");
+ const box = await sheetSurface.boundingBox();
+ expect(box, `${label}: stage surface has no box`).not.toBeNull();
+ expectFullyOnScreen(box!, width, label);
+
+ expect(box!.width, `${label}: stage does not fill viewport width`).toBeGreaterThanOrEqual(
+ width - EDGE_TOLERANCE,
+ );
+ expect(box!.height, `${label}: stage does not fill viewport height`).toBeGreaterThanOrEqual(
+ VIEWPORT_HEIGHT - EDGE_TOLERANCE,
+ );
+
+ await expect(
+ content.getByTestId("workspace-overlay-action"),
+ `${label}: decision control is not fully in the viewport`,
+ ).toBeInViewport({ ratio: 1 });
+ }
+ });
+ }
+});
+
/**
* WCAG 2.1 1.4.10 reflow, and the focus ring that has to survive it.
*
diff --git a/tests/ui-forms-section-nav.spec.ts b/tests/ui-forms-section-nav.spec.ts
index 71c12a4ad3..f9237a014c 100644
--- a/tests/ui-forms-section-nav.spec.ts
+++ b/tests/ui-forms-section-nav.spec.ts
@@ -60,13 +60,17 @@ test.describe("Forms section navigation", () => {
await expect(trigger).toBeVisible({ timeout: 20_000 });
await expect(trigger).toHaveAttribute("aria-expanded", "false");
+ await expect(trigger).toHaveAttribute("data-state", "collapsed");
await expect(trigger.getByText(preview)).toBeVisible();
await trigger.click();
await expect(trigger).toHaveAttribute("aria-expanded", "true");
+ await expect(trigger).toHaveAttribute("data-state", "expanded");
const panelId = await trigger.getAttribute("aria-controls");
if (!panelId) throw new Error("Disclosure trigger is missing aria-controls");
- const panel = page.locator(`#${panelId}`);
+ const panel = page.locator(`[id="${panelId}"]`);
+ await expect(panel).toHaveAttribute("data-state", "expanded");
+ await expect(section.locator('[data-testid="disclosure"][data-state="expanded"]')).toBeVisible();
await expect(trigger.getByText(preview)).toHaveCount(0);
await expect(panel.getByText(preview)).toBeVisible();
await expect(panel).not.toHaveClass(/border-t/);
@@ -84,7 +88,7 @@ test.describe("Forms section navigation", () => {
const panelId = await trigger.getAttribute("aria-controls");
if (!panelId) throw new Error("Disclosure trigger is missing aria-controls");
- const panel = page.locator(`#${panelId}`);
+ const panel = page.locator(`[id="${panelId}"]`);
const disclosure = panel.locator("xpath=..");
const triggerPreview = disclosure.locator('button span[aria-hidden="true"]').filter({ hasText: preview });
diff --git a/tests/ui-smoke.spec.ts b/tests/ui-smoke.spec.ts
index 2afab285ef..ecc9c90d50 100644
--- a/tests/ui-smoke.spec.ts
+++ b/tests/ui-smoke.spec.ts
@@ -1176,7 +1176,7 @@ test.describe("Clinical KB UI smoke coverage", () => {
await expect(page.locator('[data-testid="global-search-input"]:visible').first()).toBeEnabled();
});
- test("Medication shortcut opens the prescribing workspace", async ({ page }) => {
+ test("Medication shortcut opens the standalone Medication home", async ({ page }) => {
await page.setViewportSize({ width: 390, height: 820 });
await mockPrivateUnauthenticatedApi(page);
await gotoApp(page, "/");
@@ -1185,8 +1185,8 @@ test.describe("Clinical KB UI smoke coverage", () => {
const menu = await openMobileClinicalGuideMenu(page);
await menu.getByRole("link", { name: "Medication" }).click();
- await expect(page).toHaveURL(/\/medications$/);
- await expect(page.getByTestId("medication-home")).toBeVisible();
+ await expect.poll(() => new URL(page.url()).pathname, { timeout: 30_000 }).toBe("/medications");
+ await expect(page.getByTestId("medication-home").first()).toBeVisible();
});
test("mobile search focus is singular, visible, and contained at clipped edges", async ({ page }) => {
diff --git a/tests/ui-style-contract.spec.ts b/tests/ui-style-contract.spec.ts
index d61dc30345..b956d453b3 100644
--- a/tests/ui-style-contract.spec.ts
+++ b/tests/ui-style-contract.spec.ts
@@ -374,3 +374,86 @@ test.describe("ckb-v2 forced-colours computed tokens", () => {
});
}
});
+
+/**
+ * #2TAQDC static style contract: prevents unconstrained descendant `:has()` queries
+ * on root hydration nodes (such as `body:has(#main-content...)`).
+ *
+ * Established during the mobile-root CLS investigation (PR #2253): on streaming hydration,
+ * `#main-content` may temporarily not exist in the DOM, causing `body:has(#main-content...)`
+ * rules to evaluate to false during initial paint and restyle when hydration settles.
+ * Only post-hydration components (such as `.pwa-notice-stack` which is held unmounted until
+ * shell mount) are safely permitted to consume these selectors.
+ */
+test.describe("root hydration :has() selector safety contract", () => {
+ const HERO_MAIN_CONTENT_SELECTOR = 'body:has(#main-content[data-phone-footer-owner="hero"])';
+ const ALLOWED_HERO_MAIN_CONTENT_SELECTORS = new Set([
+ `${HERO_MAIN_CONTENT_SELECTOR} .pwa-notice-stack`,
+ `${HERO_MAIN_CONTENT_SELECTOR} .pwa-install-native-sheet .pwa-install-grip`,
+ `${HERO_MAIN_CONTENT_SELECTOR} .pwa-install-native-sheet .pwa-install-tagline`,
+ `${HERO_MAIN_CONTENT_SELECTOR} .pwa-install-native-sheet .pwa-install-copy`,
+ `${HERO_MAIN_CONTENT_SELECTOR} .pwa-install-native-sheet .pwa-install-support`,
+ `${HERO_MAIN_CONTENT_SELECTOR} .pwa-install-native-sheet .pwa-install-benefits`,
+ `${HERO_MAIN_CONTENT_SELECTOR} .pwa-install-native-sheet .pwa-install-header`,
+ `${HERO_MAIN_CONTENT_SELECTOR} .pwa-install-native-sheet .pwa-install-body`,
+ `${HERO_MAIN_CONTENT_SELECTOR} .pwa-install-native-sheet .pwa-install-compact-copy`,
+ `${HERO_MAIN_CONTENT_SELECTOR} .pwa-install-native-sheet .pwa-install-actions`,
+ ]);
+
+ function splitCssSelectors(prelude: string) {
+ const parts: string[] = [];
+ let current = "";
+ let depth = 0;
+ for (const char of prelude) {
+ if (char === "(") depth += 1;
+ else if (char === ")") depth = Math.max(0, depth - 1);
+ if (char === "," && depth === 0) {
+ parts.push(current.replace(/\s+/g, " ").trim());
+ current = "";
+ } else {
+ current += char;
+ }
+ }
+ const last = current.replace(/\s+/g, " ").trim();
+ if (last) parts.push(last);
+ return parts.filter(Boolean);
+ }
+
+ function normalizeCssSelector(selector: string) {
+ return selector
+ .replace(/\s+/g, " ")
+ .trim()
+ .replace(/:has\(\s+/g, ":has(")
+ .replace(/\s+\)/g, ")");
+ }
+
+ test("rejects unconstrained descendant :has(#main-content...) selectors in globals.css", async () => {
+ const fs = await import("node:fs");
+ const path = await import("node:path");
+ const styles = fs.readFileSync(path.join(process.cwd(), "src", "app", "globals.css"), "utf8");
+ const withoutComments = styles.replace(/\/\*[\s\S]*?\*\//g, "");
+ const disallowed: string[] = [];
+ let token = "";
+ for (const char of withoutComments) {
+ if (char === "{") {
+ const prelude = token.replace(/\s+/g, " ").trim();
+ if (!prelude.startsWith("@")) {
+ for (const selector of splitCssSelectors(prelude).map(normalizeCssSelector)) {
+ if (selector.includes("body:has(#main-content") && !ALLOWED_HERO_MAIN_CONTENT_SELECTORS.has(selector)) {
+ disallowed.push(selector);
+ }
+ }
+ }
+ token = "";
+ continue;
+ }
+ if (char === "}") {
+ token = "";
+ continue;
+ }
+ token += char;
+ }
+
+ expect(disallowed).toEqual([]);
+ });
+});