Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/app/therapy-compass/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import { Suspense, type ReactNode } from "react";
import { GlobalSearchShell } from "@/components/clinical-dashboard/global-search-shell";
import { TherapyCompassWorkspace } from "@/components/therapy-compass";

// Therapy Compass keeps the universal header + rail but provides its own primary
// search surface (the in-tool therapy search), so the shared search composer is
// suppressed here. The workspace is mounted at the layout level so the therapy
// dataset and interaction state are shared across every /therapy-compass/* route,
// while each route renders its own screen into the workspace's main content.
// Therapy Compass uses the same universal header, rail, and responsive search
// composer as the other mode homes. The workspace is mounted at the layout level
// so the therapy dataset and interaction state are shared across every
// /therapy-compass/* route, while each route renders its own screen into the
// workspace's main content.
export default function TherapyCompassLayout({ children }: { children: ReactNode }) {
return (
<GlobalSearchShell initialMode="therapy-compass" searchComposerVisible={false}>
<GlobalSearchShell initialMode="therapy-compass">
{/* The workspace provider reads useSearchParams; an explicit boundary lets the
route family prerender on its own, independent of the shell's Suspense. */}
<Suspense fallback={null}>
Expand Down
50 changes: 2 additions & 48 deletions src/components/therapy-compass/nav.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,26 @@
"use client";

import { useTcBindings } from "./bindings";
import { AlertIcon } from "./icons";
import { s } from "./style-utils";

/**
* In-content tool navigation for Therapy Compass.
*
* The design shipped its own left sidebar, but inside this app that role is
* already filled by the universal rail, so the bespoke rail is dropped. Its
* eight destinations are kept reachable through a horizontal, repo-idiomatic
* pill nav that sits at the top of the content (sticky under the global
* core destinations are kept reachable through a horizontal, repo-idiomatic
* pill nav that sits at the top of non-home content (sticky under the global
Comment thread
BigSimmo marked this conversation as resolved.
* header) and scrolls horizontally on narrow viewports.
*/
export function TherapyCompassNav() {
const b = useTcBindings();
const reviewCount = b.reviewCount;
return (
<div
className="tc-topnav tc-no-print"
style={s(
`position:sticky;top:0;z-index:20;display:flex;align-items:center;gap:18px;padding:14px 40px;background:var(--surface-glass);backdrop-filter:blur(12px);border-bottom:1px solid var(--border);`,
)}
>
{/* Compact tool identity (the design's top-bar brand, slimmed down) */}
<div style={s(`display:flex;align-items:center;gap:11px;flex:none;`)}>
<span
style={s(
`display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;border-radius:10px;background:var(--clinical-accent-soft);color:var(--clinical-accent);flex:none;`,
)}
>
<svg width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7">
<circle cx="12" cy="12" r="9" />
<circle cx="12" cy="12" r="5.2" />
<circle cx="12" cy="12" r="1.6" fill="currentColor" stroke="none" />
</svg>
</span>
<span style={s(`display:flex;flex-direction:column;line-height:1.15;`)}>
<span style={s(`font-size:14px;font-weight:650;color:var(--text-heading);letter-spacing:-0.01em;`)}>
Therapy
</span>
<span style={s(`font-size:11px;color:var(--text-soft);`)}>Source-grounded decision support</span>
</span>
</div>

{/* Screen nav — horizontal, scrollable */}
<nav
className="tc-scroll"
Expand Down Expand Up @@ -106,30 +83,7 @@ export function TherapyCompassNav() {
</svg>
Patient Sheets
</button>
<span style={s(`width:1px;height:22px;flex:none;background:var(--border);margin:0 4px;`)} aria-hidden="true" />
<button type="button" className="tc-btn" onClick={b.goReview} style={b.navReview}>
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7">
<path d="M12 3 5 6v5c0 4.2 2.9 7.6 7 9 4.1-1.4 7-4.8 7-9V6Z" />
<path d="m9.2 12 1.9 1.9 3.7-3.8" />
</svg>
Review Queue
</button>
</nav>

{reviewCount > 0 ? (
<button
type="button"
className="tc-btn"
onClick={b.goReview}
title={`${reviewCount} records need review`}
style={s(
`display:inline-flex;align-items:center;gap:7px;flex:none;height:36px;padding:0 12px;border:1px solid var(--warning-border);border-radius:10px;background:var(--warning-bg);color:var(--warning-text);font-size:12.5px;font-weight:600;cursor:pointer;font-family:inherit;`,
)}
>
<AlertIcon size={15} strokeWidth={1.8} />
{reviewCount} to review
</button>
) : null}
</div>
);
}
275 changes: 47 additions & 228 deletions src/components/therapy-compass/screens/home-screen.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
"use client";

import { useState, type ReactNode } from "react";
import { FileText, Network, Search, ShieldCheck, Sparkles, Waypoints } from "lucide-react";

import { ModeHomeMain, ModeHomeTemplate, ModeHomeVerificationFooter } from "@/components/mode-home-template";
import { modeHomeDesktopComposerSlotId } from "@/lib/mode-home-composer";

import { useTcBindings } from "../bindings";
import { commandControl, linkButton } from "../controls";
import type { Therapy } from "../data/types";
import { ChevronRightIcon, CompassIcon, FileTextIcon, PathwayIcon, ScaleIcon, SearchIcon, SparkleIcon } from "../icons";
import { s } from "../style-utils";

const SUGGESTIONS = [
"Anxiety in outpatient care",
Expand All @@ -15,233 +14,53 @@ const SUGGESTIONS = [
"5-minute grounding",
"Relapse prevention",
];
const FEATURED_SLUGS = [
"cognitive-behavioural-therapy-cbt",
"behavioural-activation",
"dialectical-behaviour-therapy-dbt",
"eye-movement-desensitisation-and-reprocessing-emdr",
"acceptance-and-commitment-therapy-act",
"interpersonal-psychotherapy-ipt",
];

export function HomeScreen() {
const b = useTcBindings();
const [query, setLocalQuery] = useState("");

const bySlug = new Map(b.therapies.map((t) => [t.slug, t]));
const featured: Therapy[] = FEATURED_SLUGS.map((sl) => bySlug.get(sl)).filter((t): t is Therapy => Boolean(t));
const featuredList = (featured.length ? featured : b.therapies).slice(0, 6);
const pathways = b.pathways.slice(0, 3);

const submit = () => b.submitQuery(query);

return (
<section data-screen-label="Home" style={s(`max-width:1100px;margin:0 auto;`)}>
<div style={s(`text-align:center;padding:20px 0 8px;`)}>
<span
style={s(
`display:inline-flex;align-items:center;justify-content:center;width:56px;height:56px;border-radius:15px;background:var(--clinical-accent-soft);color:var(--clinical-accent);margin-bottom:16px;`,
)}
>
<CompassIcon size={28} strokeWidth={1.6} />
</span>
<h1
style={s(`margin:0 0 8px;font-size:30px;font-weight:700;color:var(--text-heading);letter-spacing:-0.025em;`)}
>
What therapy are you looking for?
</h1>
<p style={s(`margin:0 auto 24px;font-size:15px;color:var(--text-muted);max-width:56ch;`)}>
Search {b.therapies.length || "200+"} source-grounded therapy records by problem, symptom, skill or population
— or jump into a clinical pathway.
</p>
</div>

<div style={s(`display:flex;align-items:center;max-width:760px;margin:0 auto 14px;position:relative;`)}>
<SearchIcon size={20} strokeWidth={1.8} style={s(`position:absolute;left:18px;color:var(--text-soft);`)} />
<input
value={query}
onChange={(e) => setLocalQuery(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") submit();
}}
placeholder="Search problem, symptom, therapy, skill, population…"
aria-label="Search therapies"
style={s(
`width:100%;height:58px;padding:0 130px 0 50px;border:1px solid var(--border-strong);border-radius:15px;background:var(--surface);color:var(--text);font-size:16px;font-family:inherit;outline:none;box-shadow:var(--shadow-soft);`,
)}
/>
<button
type="button"
className="tc-btn"
onClick={submit}
style={s(`position:absolute;right:8px;${commandControl}height:44px;padding:0 20px;`)}
>
<SearchIcon size={16} strokeWidth={1.9} />
Search
</button>
</div>

<div style={s(`display:flex;flex-wrap:wrap;gap:9px;justify-content:center;max-width:760px;margin:0 auto 36px;`)}>
{SUGGESTIONS.map((sugg) => (
<button
key={sugg}
type="button"
className="tc-btn"
onClick={() => b.submitQuery(sugg)}
style={s(
`padding:8px 15px;border:1px solid var(--border);border-radius:20px;background:var(--surface);color:var(--text-muted);font-size:13px;font-weight:500;cursor:pointer;font-family:inherit;`,
)}
>
{sugg}
</button>
))}
</div>

{/* quick tools */}
<div style={s(`display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-bottom:30px;`)}>
<QuickTool
icon={SparkleIcon}
title="Recommend a therapy"
body="Match a clinical question to indexed options."
onClick={b.goRecommend}
/>
<QuickTool
icon={PathwayIcon}
title="Open a pathway"
body="Problem-based, step-by-step workflows."
onClick={b.goPathways}
/>
<QuickTool
icon={FileTextIcon}
title="Create a patient sheet"
body="Design and print a plain-language handout."
onClick={b.goSheets}
/>
</div>

{/* pathways */}
<div style={s(`display:flex;align-items:baseline;justify-content:space-between;margin-bottom:14px;`)}>
<h2 style={s(`margin:0;font-size:17px;font-weight:680;color:var(--text-heading);`)}>Key clinical pathways</h2>
<button type="button" onClick={b.goPathways} style={s(linkButton)}>
View all pathways
</button>
</div>
<div style={s(`display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-bottom:30px;`)}>
{pathways.map((p) => (
<button
key={p.slug}
type="button"
className="tc-btn tc-row"
onClick={() => {
b.selectPathway(p.slug);
b.goPathways();
}}
style={s(
`text-align:left;padding:20px;border:1px solid var(--border);border-radius:15px;background:var(--surface);box-shadow:var(--shadow-tight);cursor:pointer;font-family:inherit;`,
)}
>
<span
style={s(
`display:inline-flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:11px;background:var(--clinical-accent-soft);color:var(--clinical-accent);margin-bottom:14px;`,
)}
>
<PathwayIcon size={21} strokeWidth={1.5} />
</span>
<span
style={s(`display:block;font-size:15px;font-weight:650;color:var(--text-heading);margin-bottom:4px;`)}
>
{p.name}
</span>
<span
style={s(`display:block;font-size:12.5px;color:var(--text-muted);line-height:1.45;margin-bottom:12px;`)}
>
{p.clinicalProblem ?? p.summary ?? "Source-linked therapy workflow."}
</span>
<span style={s(`font-size:11.5px;font-weight:600;color:var(--text-soft);`)}>
{p.steps.length} linked therapy steps
</span>
</button>
))}
</div>

{/* therapies */}
<div style={s(`display:flex;align-items:baseline;justify-content:space-between;margin-bottom:14px;`)}>
<h2 style={s(`margin:0;font-size:17px;font-weight:680;color:var(--text-heading);`)}>
Frequently used therapies
</h2>
<button type="button" onClick={b.goSearch} style={s(linkButton)}>
Browse library
</button>
</div>
<div style={s(`display:grid;grid-template-columns:1fr 1fr;gap:12px;`)}>
{featuredList.map((t) => (
<button
key={t.slug}
type="button"
className="tc-btn tc-row"
onClick={() => b.open(t.slug)}
style={s(
`display:flex;align-items:center;gap:14px;padding:16px 18px;border:1px solid var(--border);border-radius:14px;background:var(--surface);text-align:left;cursor:pointer;font-family:inherit;`,
)}
>
<span
style={s(
`display:inline-flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:11px;background:var(--clinical-accent);color:var(--clinical-accent-contrast);flex:none;`,
)}
>
<ScaleIcon size={20} strokeWidth={1.6} />
</span>
<span style={s(`flex:1;min-width:0;`)}>
<span style={s(`display:block;font-size:14px;font-weight:650;color:var(--text-heading);`)}>{t.name}</span>
<span
style={s(
`display:block;font-size:12.5px;color:var(--text-muted);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;`,
)}
>
{t.bestUsedFor ?? t.category}
</span>
</span>
<ChevronRightIcon size={16} strokeWidth={1.8} style={s(`color:var(--text-soft);flex:none;`)} />
</button>
))}
</div>
</section>
);
}

function QuickTool({
icon: Icon,
title,
body,
onClick,
}: {
icon: (p: { size?: number; strokeWidth?: number }) => ReactNode;
title: string;
body: string;
onClick: () => void;
}) {
return (
<button
type="button"
className="tc-btn tc-row"
onClick={onClick}
style={s(
`display:flex;gap:14px;padding:18px 20px;border:1px solid var(--border);border-radius:15px;background:var(--surface);box-shadow:var(--shadow-tight);text-align:left;cursor:pointer;font-family:inherit;`,
)}
>
<span
style={s(
`display:inline-flex;align-items:center;justify-content:center;width:42px;height:42px;border-radius:11px;background:var(--clinical-accent-soft);color:var(--clinical-accent);flex:none;`,
)}
>
<Icon size={21} strokeWidth={1.6} />
</span>
<span>
<span style={s(`display:block;font-size:14.5px;font-weight:650;color:var(--text-heading);`)}>{title}</span>
<span style={s(`display:block;font-size:12.5px;color:var(--text-muted);margin-top:3px;line-height:1.4;`)}>
{body}
</span>
</span>
</button>
<ModeHomeMain testId="therapy-compass-home" className="justify-start sm:justify-center">
Comment thread
BigSimmo marked this conversation as resolved.
<ModeHomeTemplate
testId="therapy-compass"
title="What therapy are you looking for?"
subtitle={`Search ${b.therapies.length || "200+"} source-grounded therapy records by problem, symptom, skill or population — or jump into a clinical pathway.`}
icon={Search}
actionsLabel="Therapy workflows"
desktopComposerSlotId={modeHomeDesktopComposerSlotId}
Comment thread
BigSimmo marked this conversation as resolved.
actions={[
{
title: "Recommend a therapy",
description: "Match a clinical question to indexed options.",
icon: Sparkles,
href: "/therapy-compass/recommend",
},
{
title: "Open a pathway",
description: "Problem-based, step-by-step workflows.",
icon: Waypoints,
href: "/therapy-compass/pathways",
},
{
title: "Create a patient sheet",
description: "Design and print a plain-language handout.",
icon: FileText,
onClick: b.goSheets,
},
]}
pillsTitle="Common therapy searches"
pills={SUGGESTIONS.map((suggestion) => ({
label: suggestion,
onClick: () => b.submitQuery(suggestion),
icon: Network,
}))}
footer={
<ModeHomeVerificationFooter
icon={ShieldCheck}
label="Decision support"
body="Source-grounded — review status before clinical use"
/>
Comment on lines +57 to +61

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remove the duplicate Therapy verification footer

On /therapy-compass, this new mode-home footer renders the same “Decision support — Source-grounded…” notice that TherapyCompassMain still appends unconditionally after every route, so the home page now shows two identical clinical verification/governance footers. Keep only one footer for the home route or suppress the workspace footer there; a DOM assertion that the home page contains this notice once would catch the regression.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codex fix

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Testing

  • source /root/.nvm/nvm.sh && nvm use 24.15.0 >/dev/null && npm ci
  • source /root/.nvm/nvm.sh && nvm use 24.15.0 >/dev/null && npm run test -- tests/therapy-compass-mode-wiring.test.ts
  • source /root/.nvm/nvm.sh && nvm use 24.15.0 >/dev/null && npx prettier --check tests/therapy-compass-mode-wiring.test.ts src/components/mode-home-template.tsx src/components/clinical-dashboard/global-search-shell.tsx src/components/therapy-compass/nav.tsx src/components/therapy-compass/screens/home-screen.tsx
  • source /root/.nvm/nvm.sh && nvm use 24.15.0 >/dev/null && npm run typecheck
  • source /root/.nvm/nvm.sh && nvm use 24.15.0 >/dev/null && npm run verify:cheap — broad gate reproduced the existing unrelated tests/pdf-extraction-budget.test.ts failure: missing /tmp/clinical-kb-pdf-deadline-*/child.pid.
  • ⚠️ source /root/.nvm/nvm.sh && nvm use 24.15.0 >/dev/null && npm run ensure — local server did not become ready because Next.js failed fetching Google font resources from fonts.gstatic.com; dev-server.log showed repeated font request failures and /api/local-project-id returned 500. The failed dev server process was stopped afterward.

View task →

}
/>
</ModeHomeMain>
);
}
Loading
Loading