diff --git a/src/components/therapy-compass/screens/search-screen.tsx b/src/components/therapy-compass/screens/search-screen.tsx index ebf2816984..5102ca017f 100644 --- a/src/components/therapy-compass/screens/search-screen.tsx +++ b/src/components/therapy-compass/screens/search-screen.tsx @@ -16,6 +16,8 @@ import { } from "@/components/clinical-dashboard/result-filter-control"; import { pageContainer } from "@/components/ui-primitives"; import { therapyScreenHref } from "@/lib/therapy-compass-navigation"; +import { isAdministratorUser } from "@/lib/authorization"; +import { useAuthIfAvailable } from "@/lib/example-data/store"; import { useTcBindings } from "../bindings"; import { matchesAvailability, matchesTopics } from "../data/select"; @@ -34,6 +36,7 @@ const MAX_CARDS = 24; export function SearchScreen() { const b = useTcBindings(); + const isAdministrator = isAdministratorUser(useAuthIfAvailable()?.session?.user); const q = b.search.query; // Nothing typed is browsing, not a search: show the list A to Z with no // "Best match" or "Why matched", so it never reads as the app having picked @@ -300,20 +303,23 @@ export function SearchScreen() { ) : null} {/* The sign-off queue left the main tabs (it is editorial work, not a - clinical destination). This quiet link keeps it one tap away. */} -

- -

+ clinical destination). This quiet link keeps it one tap away for a site + administrator, the only reader who can sign off (owner decision, 13 Oct 2026). */} + {isAdministrator ? ( +

+ +

+ ) : null} ); } diff --git a/src/lib/mode-secondary-navigation.ts b/src/lib/mode-secondary-navigation.ts index 13469c9360..92f096e66e 100644 --- a/src/lib/mode-secondary-navigation.ts +++ b/src/lib/mode-secondary-navigation.ts @@ -93,12 +93,13 @@ export const modeSecondaryNavigationRegistry = { { id: "recommend", label: "Recommend", href: "/therapy-compass/recommend" }, { id: "compare", label: "Compare", href: "/therapy-compass/compare" }, { id: "pathways", label: "Pathways", href: "/therapy-compass/pathways" }, - // The qualified-clinician sign-off queue. It becomes `audience: "administrators"` once the owner's - // account carries the site administrator role; until then hiding it would hide it from the owner too. + // The qualified-clinician sign-off queue, listed for site administrators only (owner decision, + // 13 Oct 2026, once the owner's account carried the role). The route still opens for anyone and + // the server still refuses a sign-off from anyone else. // `group: "more"` keeps it registered (the route, the pill name, `activeId`) but off the main // tabs, which list only ungrouped entries (owner decision, October 2026). The Therapy search // page links to it, so it stays one tap away. - { id: "review", label: "Review", href: "/therapy-compass/review", group: "more" }, + { id: "review", label: "Review", href: "/therapy-compass/review", group: "more", audience: "administrators" }, ], // Two genuinely distinct surfaces: `/factsheets/search` (query + filters + // result rows) and `/factsheets/topics` (category browse). Search leads, the diff --git a/tests/mode-secondary-navigation.test.ts b/tests/mode-secondary-navigation.test.ts index a044377c25..ec86b3724e 100644 --- a/tests/mode-secondary-navigation.test.ts +++ b/tests/mode-secondary-navigation.test.ts @@ -643,6 +643,14 @@ describe("mode secondary navigation registry", () => { } }); + it("lists Therapy's sign-off queue for site administrators only", () => { + const entries = modeSecondaryNavigationEntries("therapy-compass"); + const ids = (isAdministrator: boolean) => + visibleModeSecondaryNavigationEntries(entries, { isEditor: true, isAdministrator }).map((entry) => entry.id); + expect(ids(false)).not.toContain("review"); + expect(ids(true)).toContain("review"); + }); + it("names the right On Call page in the pill, including the two editors and the picker", () => { const cases: Record = { "/on-call": "now", diff --git a/tests/therapy-compact-row.dom.test.tsx b/tests/therapy-compact-row.dom.test.tsx index 046455229c..5f8d0b6780 100644 --- a/tests/therapy-compact-row.dom.test.tsx +++ b/tests/therapy-compact-row.dom.test.tsx @@ -41,6 +41,11 @@ function therapy(overrides: Partial = {}): Therapy { } const bindingsState = vi.hoisted(() => ({ results: [] as Therapy[] })); +const authState = vi.hoisted(() => ({ siteRole: undefined as string | undefined })); + +vi.mock("@/lib/example-data/store", () => ({ + useAuthIfAvailable: () => ({ session: { user: { app_metadata: { site_role: authState.siteRole } } } }), +})); vi.mock("@/components/therapy-compass/bindings", () => ({ useTcBindings: () => ({ @@ -118,8 +123,9 @@ describe("Therapy compact rows", () => { } }); - it("keeps the Review queue one tap away now that it has left the main tabs", () => { + it("keeps the Review queue one tap away for a site administrator", () => { bindingsState.results = [therapy()]; + authState.siteRole = "administrator"; render(); const link = screen.getByTestId("therapy-review-queue-link"); @@ -128,6 +134,14 @@ describe("Therapy compact rows", () => { expect(link).toHaveTextContent("12 to review"); }); + it("leaves the Review queue link out for anyone who is not a site administrator", () => { + bindingsState.results = [therapy()]; + authState.siteRole = undefined; + render(); + + expect(screen.queryByTestId("therapy-review-queue-link")).toBeNull(); + }); + it("still shows what the row used for Best used for and Best fit, word for word, in the record", () => { const record = therapy(); render();