From ddcea60336c0a235beb4901e56008b10390ef7bf Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Tue, 11 Aug 2026 23:30:16 +0800 Subject: [PATCH 1/5] Refine recent search presentation --- src/app/globals.css | 61 +++++++++++++++++++ .../clinical-dashboard/answer-status.tsx | 2 +- tests/helpers/style-contracts.ts | 2 + 3 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/app/globals.css b/src/app/globals.css index efded671e..f4e631822 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -2023,6 +2023,67 @@ summary::-webkit-details-marker { white-space: nowrap; } +/* Home history is a quiet convenience rather than a second set of primary + actions. Keep the shared chip behaviour and touch target, but use a compact + rectangular treatment that takes up less visual and vertical space. */ +.home-recent-searches { + gap: 0.375rem 0.625rem; + padding-inline: 0; +} + +.home-recent-searches .answer-suggestion-label { + color: color-mix(in srgb, var(--text-muted) 82%, transparent); + font-size: 0.625rem; + font-weight: 650; + letter-spacing: 0.025em; +} + +.home-recent-searches .answer-suggestion-chips { + justify-content: center; + gap: 0.375rem; +} + +.home-recent-searches .answer-suggestion-chip { + min-height: 1.75rem; + border-color: color-mix(in srgb, var(--border) 58%, transparent); + border-radius: 0.625rem; + background: color-mix(in srgb, var(--surface-inset) 42%, transparent); + box-shadow: none; + padding-inline: 0.625rem; + color: color-mix(in srgb, var(--text-muted) 90%, transparent); + font-size: 0.6875rem; + font-weight: 550; + line-height: 1rem; +} + +.home-recent-searches .answer-suggestion-chip-icon { + width: 0.6875rem; + height: 0.6875rem; + margin-right: 0.25rem; +} + +.home-recent-searches .answer-suggestion-chip:hover:not(:disabled) { + border-color: color-mix(in srgb, var(--clinical-accent-border) 68%, transparent); + background: color-mix(in srgb, var(--clinical-accent-soft) 52%, transparent); +} + +@media (pointer: coarse) { + .home-recent-searches .answer-suggestion-chip { + min-height: var(--spacing-tap); + } +} + +@media (max-width: 639px) { + .home-recent-searches { + align-items: flex-start; + flex-direction: column; + } + + .home-recent-searches .answer-suggestion-chips { + justify-content: flex-start; + } +} + .answer-footer-search-edge .answer-suggestion-row { padding-inline: 0.25rem; } diff --git a/src/components/clinical-dashboard/answer-status.tsx b/src/components/clinical-dashboard/answer-status.tsx index af64ec488..88791bd3f 100644 --- a/src/components/clinical-dashboard/answer-status.tsx +++ b/src/components/clinical-dashboard/answer-status.tsx @@ -91,7 +91,7 @@ export function SharedHomeEmptyState({ onPick={(entry) => onSelectRecent?.(entry)} label={sharedHomeEmptyState.recentLabel} layout="wrap" - className="justify-center" + className="home-recent-searches justify-center" icon={History} /> )} diff --git a/tests/helpers/style-contracts.ts b/tests/helpers/style-contracts.ts index 536b30016..00b9859f1 100644 --- a/tests/helpers/style-contracts.ts +++ b/tests/helpers/style-contracts.ts @@ -275,6 +275,8 @@ export const STYLE_CONTRACT_EXEMPTIONS: Readonly> = { // Not component effects. dark: "theme root selector, not a component class; token values are asserted by the dark-mode journeys", "touch-card": "sets outline/touch-action only; the shared focus treatment is asserted by ui-accessibility", + "home-recent-searches": + "session-seeded answer-home history variant; its desktop/phone layout and activation are covered by the focused ui-smoke recent-search journey", // Phone/answer composer chrome. Covered behaviourally by verify:phone-chrome and // the chrome-scroll/overlap journeys, but not yet by computed-effect assertions. From 06ef7ddb1db4f479b10c5480c4b19f693ead420a Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Wed, 12 Aug 2026 00:05:29 +0800 Subject: [PATCH 2/5] fix(pr-1819): avoid new raw scale literals in recent-search styles --- src/app/globals.css | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/app/globals.css b/src/app/globals.css index b5c0e77e7..a947c6c1b 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -2028,39 +2028,38 @@ summary::-webkit-details-marker { actions. Keep the shared chip behaviour and touch target, but use a compact rectangular treatment that takes up less visual and vertical space. */ .home-recent-searches { - gap: 0.375rem 0.625rem; + gap: calc(var(--spacing-icon-xs) / 2) var(--radius-md); padding-inline: 0; } .home-recent-searches .answer-suggestion-label { color: color-mix(in srgb, var(--text-muted) 82%, transparent); - font-size: 0.625rem; + font-size: var(--text-3xs); font-weight: 650; letter-spacing: 0.025em; } .home-recent-searches .answer-suggestion-chips { justify-content: center; - gap: 0.375rem; + gap: calc(var(--spacing-icon-xs) / 2); } .home-recent-searches .answer-suggestion-chip { - min-height: 1.75rem; + min-height: calc(var(--spacing-tap) * 7 / 12); border-color: color-mix(in srgb, var(--border) 58%, transparent); - border-radius: 0.625rem; + border-radius: var(--radius-md); background: color-mix(in srgb, var(--surface-inset) 42%, transparent); box-shadow: none; - padding-inline: 0.625rem; + padding-inline: var(--radius-md); color: color-mix(in srgb, var(--text-muted) 90%, transparent); - font-size: 0.6875rem; + font-size: var(--text-2xs); font-weight: 550; - line-height: 1rem; } .home-recent-searches .answer-suggestion-chip-icon { - width: 0.6875rem; - height: 0.6875rem; - margin-right: 0.25rem; + width: var(--text-2xs); + height: var(--text-2xs); + margin-right: var(--radius-xs); } .home-recent-searches .answer-suggestion-chip:hover:not(:disabled) { From 1937cc3fe24c2f1d360af2cf7d819295e1da547b Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Wed, 12 Aug 2026 06:55:16 +0800 Subject: [PATCH 3/5] fix(pr-1819): cover home recent-search contracts and assertions --- src/app/globals.css | 2 +- tests/helpers/style-contracts.ts | 44 ++++++++++++++++++++++++++++++-- tests/ui-smoke.spec.ts | 13 ++++++++++ tests/ui-style-contract.spec.ts | 11 ++++++++ 4 files changed, 67 insertions(+), 3 deletions(-) diff --git a/src/app/globals.css b/src/app/globals.css index a947c6c1b..3b4b3fd65 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -2033,7 +2033,7 @@ summary::-webkit-details-marker { } .home-recent-searches .answer-suggestion-label { - color: color-mix(in srgb, var(--text-muted) 82%, transparent); + color: var(--text-heading); font-size: var(--text-3xs); font-weight: 650; letter-spacing: 0.025em; diff --git a/tests/helpers/style-contracts.ts b/tests/helpers/style-contracts.ts index 00b9859f1..e131cafb1 100644 --- a/tests/helpers/style-contracts.ts +++ b/tests/helpers/style-contracts.ts @@ -1,3 +1,6 @@ +import { guestAnswerThreadOwnerId } from "@/lib/answer-thread-storage"; +import { demoRecentQueryOwnerId, recentQueryStorageKey } from "@/lib/recent-query-storage"; + /** * The cascade-layer inertness registry (ledger #094). * @@ -55,6 +58,13 @@ export type UnlayeredVisualClass = { readonly unmediated: boolean; }; +type StyleContractSessionBootstrap = { + readonly sessionStorage?: ReadonlyArray<{ + readonly key: string; + readonly value: readonly string[]; + }>; +}; + /** * Class rules in `css` that sit outside every `@layer` block and set a visual * property. @@ -164,6 +174,7 @@ export type StyleEffectContract = { readonly route: string; /** Playwright selector for an element carrying `className`. */ readonly selector: string; + readonly bootstrap?: StyleContractSessionBootstrap; /** Computed values that must match exactly. */ readonly computed: Readonly>; /** A computed colour that must resolve to the named CSS custom-property token. */ @@ -180,6 +191,15 @@ export type StyleEffectContract = { readonly nonInert?: readonly string[]; }; +const HOME_RECENT_QUERIES = ["clozapine monitoring schedule"] as const; +const HOME_RECENT_QUERY_STORAGE_KEYS = [ + `${recentQueryStorageKey}:${guestAnswerThreadOwnerId}`, + `${recentQueryStorageKey}:${demoRecentQueryOwnerId}`, +] as const; +const homeRecentQueriesContractBootstrap: StyleContractSessionBootstrap = { + sessionStorage: HOME_RECENT_QUERY_STORAGE_KEYS.map((key) => ({ key, value: HOME_RECENT_QUERIES })), +}; + /** * Rendered-effect contracts, asserted in Chromium by `tests/ui-style-contract.spec.ts`. * @@ -260,6 +280,28 @@ export const STYLE_EFFECT_CONTRACTS: readonly StyleEffectContract[] = [ nonInert: ["color"], colorToken: { property: "color", token: "--text-heading" }, }, + { + className: "home-recent-searches", + description: "home recent-searches surface keeps compact desktop gap and phone-first mobile column flow", + route: "/", + selector: '[data-testid="shared-home-recent-queries"]', + bootstrap: homeRecentQueriesContractBootstrap, + computed: { + rowGap: "6px", + columnGap: "10px", + display: "flex", + }, + }, + { + className: "answer-suggestion-label", + description: "home recent-search labels use readable text contrast when nested under compact rail", + route: "/", + selector: '[data-testid="shared-home-recent-queries"] .answer-suggestion-label', + bootstrap: homeRecentQueriesContractBootstrap, + computed: {}, + nonInert: ["color"], + colorToken: { property: "color", token: "--text-heading" }, + }, ]; /** @@ -275,8 +317,6 @@ export const STYLE_CONTRACT_EXEMPTIONS: Readonly> = { // Not component effects. dark: "theme root selector, not a component class; token values are asserted by the dark-mode journeys", "touch-card": "sets outline/touch-action only; the shared focus treatment is asserted by ui-accessibility", - "home-recent-searches": - "session-seeded answer-home history variant; its desktop/phone layout and activation are covered by the focused ui-smoke recent-search journey", // Phone/answer composer chrome. Covered behaviourally by verify:phone-chrome and // the chrome-scroll/overlap journeys, but not yet by computed-effect assertions. diff --git a/tests/ui-smoke.spec.ts b/tests/ui-smoke.spec.ts index 1dd080631..215ecd3c3 100644 --- a/tests/ui-smoke.spec.ts +++ b/tests/ui-smoke.spec.ts @@ -2567,6 +2567,19 @@ test.describe("Clinical KB UI smoke coverage", () => { await expect(page.getByTestId("plain-answer-response")).toBeVisible(); expect(answerRequests).toContain(recent); await expectNoPageHorizontalOverflow(page); + + await page.setViewportSize({ width: 390, height: 844 }); + await gotoApp(page, "/"); + await waitForDemoDashboardReady(page); + + const homeRecentSearches = recentChips.locator(".home-recent-searches"); + await expect(homeRecentSearches).toBeVisible(); + const homeRecentDirection = await homeRecentSearches.evaluate((node) => getComputedStyle(node).flexDirection); + expect(homeRecentDirection, "home recent-searches should stack on phone width").toBe("column"); + + const chipsGroup = recentChips.locator(".answer-suggestion-chips"); + const mobileJustify = await chipsGroup.evaluate((node) => getComputedStyle(node).justifyContent); + expect(mobileJustify, "phone home recent-search chips should align to flex-start").toBe("flex-start"); }); test("legacy unscoped recent-query storage is purged and never displayed @critical", async ({ page }) => { diff --git a/tests/ui-style-contract.spec.ts b/tests/ui-style-contract.spec.ts index 6e8dd973f..a09281802 100644 --- a/tests/ui-style-contract.spec.ts +++ b/tests/ui-style-contract.spec.ts @@ -23,6 +23,17 @@ test.describe("unlayered style rules render their effect", () => { for (const contract of STYLE_EFFECT_CONTRACTS) { test(contract.description, async ({ page }) => { + if (contract.bootstrap?.sessionStorage?.length) { + await page.addInitScript( + ({ sessionStorage }) => { + for (const item of sessionStorage ?? []) { + window.sessionStorage.setItem(item.key, JSON.stringify(item.value)); + } + }, + { sessionStorage: contract.bootstrap?.sessionStorage }, + ); + } + await page.goto(contract.route, { waitUntil: "domcontentloaded" }); const target = page.locator(contract.selector).first(); From 4390e6ef1149161b07b33f45f09be93eceaa9fe9 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Wed, 12 Aug 2026 10:27:44 +0800 Subject: [PATCH 4/5] Fix recent-search smoke locator and style-contract exemption --- tests/helpers/style-contracts.ts | 1 - tests/ui-smoke.spec.ts | 9 ++++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/helpers/style-contracts.ts b/tests/helpers/style-contracts.ts index e131cafb1..9ad340075 100644 --- a/tests/helpers/style-contracts.ts +++ b/tests/helpers/style-contracts.ts @@ -347,7 +347,6 @@ export const STYLE_CONTRACT_EXEMPTIONS: Readonly> = { // Answer suggestions and smart search. "answer-suggestion-chip": "answer suggestion rail — no effect contract yet (#094)", "answer-suggestion-chip-icon": "answer suggestion rail — no effect contract yet (#094)", - "answer-suggestion-label": "answer suggestion rail — no effect contract yet (#094)", "smart-search-rotating-query": "rotating placeholder — reduced-motion behaviour covered by ui-accessibility", "smart-search-rotating-text": "rotating placeholder — reduced-motion behaviour covered by ui-accessibility", "smart-search-phone-ticker": diff --git a/tests/ui-smoke.spec.ts b/tests/ui-smoke.spec.ts index 47844069f..d148e8ca8 100644 --- a/tests/ui-smoke.spec.ts +++ b/tests/ui-smoke.spec.ts @@ -2600,15 +2600,18 @@ test.describe("Clinical KB UI smoke coverage", () => { await expectNoPageHorizontalOverflow(page); await page.setViewportSize({ width: 390, height: 844 }); - await gotoApp(page, "/"); + const newChat = page.getByRole("button", { name: /new chat|new comparison/i }); + await expect(newChat).toBeVisible(); + await newChat.click(); await waitForDemoDashboardReady(page); - const homeRecentSearches = recentChips.locator(".home-recent-searches"); + const homeRecentSearches = page.getByTestId("shared-home-recent-queries"); + await homeRecentSearches.scrollIntoViewIfNeeded(); await expect(homeRecentSearches).toBeVisible(); const homeRecentDirection = await homeRecentSearches.evaluate((node) => getComputedStyle(node).flexDirection); expect(homeRecentDirection, "home recent-searches should stack on phone width").toBe("column"); - const chipsGroup = recentChips.locator(".answer-suggestion-chips"); + const chipsGroup = homeRecentSearches.locator(".answer-suggestion-chips"); const mobileJustify = await chipsGroup.evaluate((node) => getComputedStyle(node).justifyContent); expect(mobileJustify, "phone home recent-search chips should align to flex-start").toBe("flex-start"); }); From 0138972271bcae599b540605349f69304d3c86ef Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Wed, 12 Aug 2026 13:54:05 +0800 Subject: [PATCH 5/5] Fix differential compare launch singleton routing --- src/lib/differentials.ts | 7 +++++++ tests/differentials-navigation.test.ts | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/lib/differentials.ts b/src/lib/differentials.ts index cb7216258..5717f0869 100644 --- a/src/lib/differentials.ts +++ b/src/lib/differentials.ts @@ -324,6 +324,13 @@ export function resolveDifferentialCompareHandoff(ids: Iterable, query = export function resolveDifferentialCompareLaunchHref(ids: Iterable, query = ""): string { const handoff = resolveDifferentialCompareHandoff(ids, query); if (handoff.kind === "presentation") return handoff.href; + if (handoff.selection.diagnosisIds.length === 1) { + return differentialCompareHref( + "/differentials/presentations/acute-confusion-encephalopathy", + query, + handoff.selection.diagnosisIds, + ); + } const params = new URLSearchParams(); const trimmedQuery = query.trim(); if (trimmedQuery) params.set("q", trimmedQuery); diff --git a/tests/differentials-navigation.test.ts b/tests/differentials-navigation.test.ts index c935317e4..f6f4ee0aa 100644 --- a/tests/differentials-navigation.test.ts +++ b/tests/differentials-navigation.test.ts @@ -104,6 +104,13 @@ describe("differentials navigation", () => { expect(href).toContain("ids="); }); + it("launches single-diagnosis compare into the default presentation workflow", () => { + const href = resolveDifferentialCompareLaunchHref(["wernicke-encephalopathy"], "Pain"); + expect(href).toMatch(/^\/differentials\/presentations\//); + expect(href).toContain("acute-confusion-encephalopathy"); + expect(href).toContain("ids=wernicke-encephalopathy"); + }); + it("builds compare queue titles from known diagnosis slugs only", () => { const items = differentialCompareQueueItems([ "delirium",