From 1420ac698ea31bf7d506e3dcfb3c8d25467c19c4 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Mon, 13 Jul 2026 19:10:04 +0800 Subject: [PATCH 1/3] fix(ia): canonicalise Tools route and unify medication/composer naming (PT-11, PT-12, PT-14) "Tools" and "Applications" were two names for the same launcher: /?mode=tools (mode home) plus a standalone /applications route titled "Applications - Clinical KB". Rename the standalone route to /tools to match the mode name and the /services//forms sibling pattern, and keep /applications as a query-preserving redirect for old links and history. Unify the medication naming: the sidebar said "Medications" while the mode label is "Medication"; use "Medication" in both. Drop the follow-up composer's "Ask Clinical Guide" placeholder override so every answer composer uses the task-oriented mode placeholder ("Ask a clinical question..."). Site map regenerated. Co-Authored-By: Claude Fable 5 --- docs/site-map.md | 1 + src/app/applications/page.tsx | 16 ++++-------- src/app/{applications => tools}/error.tsx | 6 ++--- src/app/{applications => tools}/layout.tsx | 2 +- src/app/{applications => tools}/loading.tsx | 0 src/app/tools/page.tsx | 25 +++++++++++++++++++ .../clinical-dashboard/ClinicalSidebar.tsx | 2 +- .../global-search-shell.tsx | 2 +- .../master-search-header.tsx | 5 ++-- tests/ui-smoke.spec.ts | 4 +-- tests/ui-tools.spec.ts | 10 ++++---- 11 files changed, 47 insertions(+), 26 deletions(-) rename src/app/{applications => tools}/error.tsx (71%) rename src/app/{applications => tools}/layout.tsx (75%) rename src/app/{applications => tools}/loading.tsx (100%) create mode 100644 src/app/tools/page.tsx diff --git a/docs/site-map.md b/docs/site-map.md index 17c063780..c5ac29efa 100644 --- a/docs/site-map.md +++ b/docs/site-map.md @@ -19,6 +19,7 @@ This file is generated by `npm run sitemap:update`. Run `npm run sitemap:check` - `/privacy` - Privacy and data-processing governance draft. Source: `src/app/privacy/page.tsx`. - `/reference/colour-coding` - Route discovered from app directory Source: `src/app/reference/colour-coding/page.tsx`. - `/services` - Services home and search surface. Source: `src/app/services/page.tsx`. +- `/tools` - Route discovered from app directory Source: `src/app/tools/page.tsx`. ## Mode/query routes diff --git a/src/app/applications/page.tsx b/src/app/applications/page.tsx index 6e844a80c..9636694ca 100644 --- a/src/app/applications/page.tsx +++ b/src/app/applications/page.tsx @@ -1,11 +1,4 @@ -import type { Metadata } from "next"; - -import { ApplicationsLauncherPage } from "@/components/applications-launcher-page"; - -export const metadata: Metadata = { - title: "Applications - Clinical KB", - description: "Launch Clinical KB applications, workflows, and connected clinical tools.", -}; +import { redirect } from "next/navigation"; type ApplicationsPageProps = { searchParams?: Promise<{ @@ -17,9 +10,10 @@ function firstSearchParam(value: string | string[] | undefined) { return Array.isArray(value) ? value[0] : value; } -export default async function ApplicationsRoute({ searchParams }: ApplicationsPageProps) { +// "Tools" is the canonical name and /tools the canonical route (PT-11); this +// legacy route only forwards old links and browser history. +export default async function ApplicationsRedirect({ searchParams }: ApplicationsPageProps) { const params = searchParams ? await searchParams : {}; const query = firstSearchParam(params.q)?.trim(); - - return query ? : ; + redirect(query ? `/tools?q=${encodeURIComponent(query)}` : "/tools"); } diff --git a/src/app/applications/error.tsx b/src/app/tools/error.tsx similarity index 71% rename from src/app/applications/error.tsx rename to src/app/tools/error.tsx index 2cc05921d..18b34e6bf 100644 --- a/src/app/applications/error.tsx +++ b/src/app/tools/error.tsx @@ -7,9 +7,9 @@ export default function ErrorBoundary({ error, reset }: { error: Error & { diges ); } diff --git a/src/app/applications/layout.tsx b/src/app/tools/layout.tsx similarity index 75% rename from src/app/applications/layout.tsx rename to src/app/tools/layout.tsx index 641518a9f..3e4972261 100644 --- a/src/app/applications/layout.tsx +++ b/src/app/tools/layout.tsx @@ -2,7 +2,7 @@ import type { ReactNode } from "react"; import { GlobalSearchShell } from "@/components/clinical-dashboard/global-search-shell"; -export default function ApplicationsLayout({ children }: { children: ReactNode }) { +export default function ToolsLayout({ children }: { children: ReactNode }) { return ( {children} diff --git a/src/app/applications/loading.tsx b/src/app/tools/loading.tsx similarity index 100% rename from src/app/applications/loading.tsx rename to src/app/tools/loading.tsx diff --git a/src/app/tools/page.tsx b/src/app/tools/page.tsx new file mode 100644 index 000000000..cd5dc31f1 --- /dev/null +++ b/src/app/tools/page.tsx @@ -0,0 +1,25 @@ +import type { Metadata } from "next"; + +import { ApplicationsLauncherPage } from "@/components/applications-launcher-page"; + +export const metadata: Metadata = { + title: "Tools - Clinical KB", + description: "Launch Clinical KB tools, workflows, and connected clinical applications.", +}; + +type ToolsPageProps = { + searchParams?: Promise<{ + q?: string | string[]; + }>; +}; + +function firstSearchParam(value: string | string[] | undefined) { + return Array.isArray(value) ? value[0] : value; +} + +export default async function ToolsRoute({ searchParams }: ToolsPageProps) { + const params = searchParams ? await searchParams : {}; + const query = firstSearchParam(params.q)?.trim(); + + return query ? : ; +} diff --git a/src/components/clinical-dashboard/ClinicalSidebar.tsx b/src/components/clinical-dashboard/ClinicalSidebar.tsx index ca207e0c1..854110ad3 100644 --- a/src/components/clinical-dashboard/ClinicalSidebar.tsx +++ b/src/components/clinical-dashboard/ClinicalSidebar.tsx @@ -74,7 +74,7 @@ const sidebarToolItems = [ { id: "forms", label: "Forms", icon: ClipboardPen, href: "/forms" }, { id: "favourites", label: "Favourites", icon: Heart, href: "/favourites" }, { id: "differentials", label: "Differentials", icon: BrainCircuit, href: "/differentials" }, - { id: "prescribing", label: "Medications", icon: Pill, href: "/?mode=prescribing" }, + { id: "prescribing", label: "Medication", icon: Pill, href: "/?mode=prescribing" }, { id: "tools", label: "Tools", icon: Wrench, href: "/?mode=tools" }, ] as const; diff --git a/src/components/clinical-dashboard/global-search-shell.tsx b/src/components/clinical-dashboard/global-search-shell.tsx index 9e0643655..5d1e40b47 100644 --- a/src/components/clinical-dashboard/global-search-shell.tsx +++ b/src/components/clinical-dashboard/global-search-shell.tsx @@ -247,7 +247,7 @@ function GlobalStandaloneSearchShellClient({ (searchMode === "forms" && pathname === "/forms") || (searchMode === "favourites" && pathname === "/favourites") || (searchMode === "differentials" && pathname === "/differentials") || - (searchMode === "tools" && pathname === "/applications")); + (searchMode === "tools" && pathname === "/tools")); const isDifferentialPresentationWorkflow = pathname.startsWith("/differentials/presentations"); const shouldShowDesktopSidebar = !hideDesktopSidebar; const effectiveSidebarCollapsed = isDifferentialPresentationWorkflow ? true : sidebarCollapsed; diff --git a/src/components/clinical-dashboard/master-search-header.tsx b/src/components/clinical-dashboard/master-search-header.tsx index 6e2fc1d5b..8e2931d2c 100644 --- a/src/components/clinical-dashboard/master-search-header.tsx +++ b/src/components/clinical-dashboard/master-search-header.tsx @@ -415,8 +415,9 @@ export function MasterSearchHeader({ const activeQuickFilterCount = (scopeFilters.sourceStatuses?.length ? 1 : 0) + (scopeFilters.locality ? 1 : 0) + activeLabelFilterCount; const submitLabel = trimmedQuery ? selectedSearch.submitBusyLabel : selectedSearch.submitIdleLabel; - const queryPlaceholder = - composerPlaceholder ?? (isAnswerFooterComposer ? "Ask Clinical Guide" : selectedSearch.placeholder); + // One task-oriented placeholder per mode (PT-14): the follow-up composer must + // not swap to brand copy that hides what the input actually does. + const queryPlaceholder = composerPlaceholder ?? selectedSearch.placeholder; const SelectedAppModeIcon = appModeIcons[selectedAppMode.id]; const actionMenuModeOptions = useMemo( () => diff --git a/tests/ui-smoke.spec.ts b/tests/ui-smoke.spec.ts index e5276e459..6a5a27b1b 100644 --- a/tests/ui-smoke.spec.ts +++ b/tests/ui-smoke.spec.ts @@ -946,7 +946,7 @@ test.describe("Clinical KB UI smoke coverage", () => { { name: "Forms", href: "/forms" }, { name: "Favourites", href: "/favourites" }, { name: "Differentials", href: "/differentials" }, - { name: "Medications", href: "/?mode=prescribing" }, + { name: "Medication", href: "/?mode=prescribing" }, { name: "Tools", href: "/?mode=tools" }, ] as const) { await expect(page.getByRole("link", { name: tool.name, exact: true })).toHaveAttribute("href", tool.href); @@ -963,7 +963,7 @@ test.describe("Clinical KB UI smoke coverage", () => { { path: "/?mode=answer", label: "Answer" }, { path: "/?mode=documents", label: "Documents" }, { path: "/favourites", label: "Favourites" }, - { path: "/?mode=prescribing", label: "Medications" }, + { path: "/?mode=prescribing", label: "Medication" }, ] as const) { await gotoApp(page, route.path); if (route.path.includes("mode=answer")) { diff --git a/tests/ui-tools.spec.ts b/tests/ui-tools.spec.ts index 55801e9e3..f860a361b 100644 --- a/tests/ui-tools.spec.ts +++ b/tests/ui-tools.spec.ts @@ -307,9 +307,9 @@ test.describe("Clinical KB tools launcher", () => { }); } - test("standalone applications route uses the shared global search", async ({ page }) => { + test("standalone tools route uses the shared global search", async ({ page }) => { await page.setViewportSize({ width: 1280, height: 900 }); - await gotoLauncher(page, "/applications"); + await gotoLauncher(page, "/tools"); await expect(page.getByRole("heading", { level: 1, name: "Tools" })).toBeVisible(); await expect(visibleGlobalSearchInput(page)).toHaveCount(1); @@ -469,7 +469,7 @@ test.describe("Clinical KB tools launcher", () => { { path: "/forms", testId: "forms-home", heading: "Forms", headingLevel: 1 }, { path: "/differentials", testId: "differentials-home", heading: "Differentials", headingLevel: 1 }, { path: "/favourites", testId: "favourites-hub", heading: "Favourites command library", headingLevel: 1 }, - { path: "/applications", testId: "tools-home", heading: "Tools", headingLevel: 1 }, + { path: "/tools", testId: "tools-home", heading: "Tools", headingLevel: 1 }, ] as const) { await gotoLauncher(page, home.path); await expect(page.getByTestId(home.testId)).toBeVisible(); @@ -638,7 +638,7 @@ test.describe("Clinical KB tools launcher", () => { test("phone mode homes keep the shared search in the hero, not the bottom dock", async ({ page }) => { await page.setViewportSize({ width: 390, height: 820 }); - for (const home of ["/services", "/forms", "/differentials", "/applications"]) { + for (const home of ["/services", "/forms", "/differentials", "/tools"]) { await gotoLauncher(page, home); const heroInput = page.locator(".mode-home-composer-slot").getByTestId("global-search-input"); await expect(heroInput).toBeVisible({ timeout: 15_000 }); @@ -695,7 +695,7 @@ test.describe("Clinical KB tools launcher", () => { { path: "/services", testId: "services-home", heading: "Find a service", headingLevel: 1 }, { path: "/forms", testId: "forms-home", heading: "Forms", headingLevel: 1 }, { path: "/differentials", testId: "differentials-home", heading: "Differentials", headingLevel: 1 }, - { path: "/applications", testId: "tools-home", heading: "Tools", headingLevel: 1 }, + { path: "/tools", testId: "tools-home", heading: "Tools", headingLevel: 1 }, ] as const) { await gotoLauncher(page, home.path); await expect(page.getByTestId(home.testId)).toBeVisible(); From 3b7e22fe436743edaa1fcaff1a249b373a5bc8be Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Mon, 13 Jul 2026 23:57:31 +0800 Subject: [PATCH 2/3] fix(tools): preserve live and legacy search state --- src/app/applications/page.tsx | 18 +++++++++++------- src/app/tools/page.tsx | 17 ++--------------- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/src/app/applications/page.tsx b/src/app/applications/page.tsx index 9636694ca..15b66e16d 100644 --- a/src/app/applications/page.tsx +++ b/src/app/applications/page.tsx @@ -1,19 +1,23 @@ import { redirect } from "next/navigation"; type ApplicationsPageProps = { - searchParams?: Promise<{ - q?: string | string[]; - }>; + searchParams?: Promise>; }; -function firstSearchParam(value: string | string[] | undefined) { - return Array.isArray(value) ? value[0] : value; +function forwardedSearchParams(params: Record) { + const forwarded = new URLSearchParams(); + for (const [key, value] of Object.entries(params)) { + for (const item of Array.isArray(value) ? value : [value]) { + if (item !== undefined) forwarded.append(key, item); + } + } + return forwarded.toString(); } // "Tools" is the canonical name and /tools the canonical route (PT-11); this // legacy route only forwards old links and browser history. export default async function ApplicationsRedirect({ searchParams }: ApplicationsPageProps) { const params = searchParams ? await searchParams : {}; - const query = firstSearchParam(params.q)?.trim(); - redirect(query ? `/tools?q=${encodeURIComponent(query)}` : "/tools"); + const query = forwardedSearchParams(params); + redirect(query ? `/tools?${query}` : "/tools"); } diff --git a/src/app/tools/page.tsx b/src/app/tools/page.tsx index cd5dc31f1..04ec346c8 100644 --- a/src/app/tools/page.tsx +++ b/src/app/tools/page.tsx @@ -7,19 +7,6 @@ export const metadata: Metadata = { description: "Launch Clinical KB tools, workflows, and connected clinical applications.", }; -type ToolsPageProps = { - searchParams?: Promise<{ - q?: string | string[]; - }>; -}; - -function firstSearchParam(value: string | string[] | undefined) { - return Array.isArray(value) ? value[0] : value; -} - -export default async function ToolsRoute({ searchParams }: ToolsPageProps) { - const params = searchParams ? await searchParams : {}; - const query = firstSearchParam(params.q)?.trim(); - - return query ? : ; +export default function ToolsRoute() { + return ; } From 2bad7ba0499626edd8f1833a18983596d0b9f941 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Tue, 14 Jul 2026 00:22:53 +0800 Subject: [PATCH 3/3] fix: probe canonical tools route during startup --- scripts/ensure-local-server.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ensure-local-server.mjs b/scripts/ensure-local-server.mjs index 223cbeb1d..155f877d7 100644 --- a/scripts/ensure-local-server.mjs +++ b/scripts/ensure-local-server.mjs @@ -27,7 +27,7 @@ const printUrlOnly = process.argv.slice(2).includes("--print-url"); const debugEnabled = process.env.ENSURE_DEBUG === "1"; const startupLockStaleMs = 3 * 60 * 1000; const readyStableMs = 5 * 1000; -const readinessPaths = ["/", "/applications"]; +const readinessPaths = ["/", "/applications", "/tools"]; function debug(message) { if (debugEnabled) console.error(`[ensure-local-server] ${message}`);