Skip to content
Merged
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
1 change: 1 addition & 0 deletions docs/codebase-index.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ Structured map for AI agents and onboarding. For live routes, see `docs/site-map
| `/medications`, `/medications/[slug]` | `src/app/medications/` |
| `/privacy` | `src/app/privacy/page.tsx` |
| `/reference/colour-coding` | `src/app/reference/` |
| `/safety-plan` | `src/app/safety-plan/page.tsx` |
| `/services`, `/services/[slug]` | `src/app/services/` |
| `/therapy-compass` | `src/app/therapy-compass/` |
| `/tools` | `src/app/tools/` |
Expand Down
2 changes: 1 addition & 1 deletion docs/site-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ This file is generated by `npm run sitemap:update`. Run `npm run sitemap:check`
- `/formulation/map` - Formulation mechanism domain map. Source: `src/app/formulation/map/page.tsx`.
- `/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`.
- `/safety-plan` - Patient safety plan generator (Stanley-Brown six steps) — a Tools-page clinical tool. Source: `src/app/safety-plan/page.tsx`.
- `/services` - Services home and search surface. Source: `src/app/services/page.tsx`.
- `/specifiers` - Psychiatric specifier home and local search surface. Source: `src/app/specifiers/page.tsx`.
- `/specifiers/builder` - Structured diagnostic wording builder. Source: `src/app/specifiers/builder/page.tsx`.
Expand Down Expand Up @@ -1002,7 +1003,6 @@ This file is generated by `npm run sitemap:update`. Run `npm run sitemap:check`
- `/mockups/favourites-set-navigator` - Route discovered from app directory Source: `src/app/mockups/favourites-set-navigator/page.tsx`.
- `/mockups/medication-prescribing` - Route discovered from app directory Source: `src/app/mockups/medication-prescribing/page.tsx`.
- `/mockups/mode-dropdown` - Route discovered from app directory Source: `src/app/mockups/mode-dropdown/page.tsx`.
- `/mockups/patient-safety-plan` - Route discovered from app directory Source: `src/app/mockups/patient-safety-plan/page.tsx`.
- `/mockups/recent-searches-bottom` - Route discovered from app directory Source: `src/app/mockups/recent-searches-bottom/page.tsx`.
- `/mockups/settings-search-clinical` - Route discovered from app directory Source: `src/app/mockups/settings-search-clinical/page.tsx`.
- `/mockups/settings-search-general` - Route discovered from app directory Source: `src/app/mockups/settings-search-general/page.tsx`.
Expand Down
1 change: 1 addition & 0 deletions scripts/generate-site-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ const routeDescriptions: Record<string, string> = {
"/medications": "Medication index redirect.",
"/medications/[slug]": "Medication detail.",
"/privacy": "Privacy and data-processing governance draft.",
"/safety-plan": "Patient safety plan generator (Stanley-Brown six steps) — a Tools-page clinical tool.",
"/services": "Services home and search surface.",
"/services/[slug]": "Registry-backed service detail.",
"/formulation": "Clinical formulation home and local mechanism search surface.",
Expand Down
22 changes: 22 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -2572,6 +2572,28 @@ html[data-motion="reduced"] .source-capsule-hit[aria-expanded="true"]:hover .sou
}
}

/* Safety plan generator (/safety-plan) print export.
* "Print / PDF" (and Cmd+P) should hand over the patient copy only, so the
* builder/editor chrome is removed and the patient-copy document header — which
* the global `header { display: none }` rule above would otherwise drop — is
* restored. Scoped to the tool so no other route's print behaviour changes. */
@media print {
.safety-plan-tool [data-print-hide] {
display: none !important;
}

.safety-plan-tool [data-safety-plan-copy] {
display: block !important;
position: static !important;
max-height: none !important;
overflow: visible !important;
}

.safety-plan-tool [data-safety-plan-copy] header {
display: grid !important;
}
}

/* iOS Safari bottom reserve transitions to match the composer's hide/show motion.
Shell routes animate the inner reserve pad; dashboard/answer animates
#main-content padding; DocumentViewer animates its own content pad. */
Expand Down
7 changes: 1 addition & 6 deletions src/app/mockups/mockups-layout-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,6 @@ export function MockupsLayoutClient({ children }: { children: ReactNode }) {
// at the bottom on phones), so the shared universal composer is suppressed here
// to avoid a second, floating search bar.
const isCalculatorsSearchPageMockup = pathname === "/mockups/calculators-search-page";
// The safety-plan generator is a full builder workspace with its own primary
// surfaces, so it hides the shared bottom search composer (same treatment as
// the tool and favourites mockups).
const isSafetyPlanMockup = pathname === "/mockups/patient-safety-plan";

return (
<GlobalMockupSearchShell
Expand All @@ -38,8 +34,7 @@ export function MockupsLayoutClient({ children }: { children: ReactNode }) {
!isFavouritesPageMockup &&
!isStandaloneDocumentFlow &&
!isUniversalSearchRedesignMockup &&
!isCalculatorsSearchPageMockup &&
!isSafetyPlanMockup
!isCalculatorsSearchPageMockup
}
chromeVisible={!isSourceOverlayRedesignMockup}
>
Expand Down
13 changes: 0 additions & 13 deletions src/app/mockups/patient-safety-plan/page.tsx

This file was deleted.

13 changes: 13 additions & 0 deletions src/app/safety-plan/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { Metadata } from "next";

import { PatientSafetyPlan } from "@/components/patient-safety-plan";

export const metadata: Metadata = {
title: "Safety plan generator - Clinical KB",
description:
"Build a patient safety plan with the Stanley-Brown six steps and a live patient copy to print, save as PDF, or hand over.",
};

export default function SafetyPlanPage() {
return <PatientSafetyPlan />;
Comment thread
BigSimmo marked this conversation as resolved.
Comment thread
BigSimmo marked this conversation as resolved.
}
1 change: 1 addition & 0 deletions src/components/applications-launcher-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ const launcherIconById: Record<string, LucideIcon> = {
services: Users,
forms: FileCheck2,
"care-plans": ClipboardCheck,
"safety-plan": ClipboardList,
monitoring: Waves,
favourites: Star,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,16 @@ import {
} from "@/components/ui-primitives";

/*
* Patient Safety Plan generator — design-exploration mockup.
* Patient Safety Plan generator — Tools-page clinical tool.
*
* A clinician builds an evidence-based safety plan *with* the patient (the
* Stanley-Brown Safety Planning Intervention, six prioritised steps), and a
* live patient-facing preview updates as they type — ready to print, save as
* PDF, or hand over. Sample content is seeded so the layout reads fully; every
* field is editable. Australian English + AU crisis resources throughout, per
* the Clinical KB (en-AU) voice. All chrome is token-driven so light/dark,
* reduced-motion and forced-colors follow the shared design system.
* field is editable and "Clear all" empties the plan. Australian English + AU
* crisis resources throughout, per the Clinical KB (en-AU) voice. All chrome is
* token-driven so light/dark, reduced-motion and forced-colors follow the
* shared design system.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
*/

const focusRing =
Expand Down Expand Up @@ -188,6 +189,18 @@ const SEED_REASONS: Entry[] = [
{ id: "r4", primary: "Being there for my niece" },
];

// Production default: a fresh plan starts blank so no sample/placeholder content
// (including the non-working example crisis numbers) can reach a printed handover.
// "Load example" restores the SEED content on demand for demos and training.
const EMPTY_ENTRIES: Record<StepKey, Entry[]> = {
warning: [],
coping: [],
people: [],
support: [],
professional: [],
environment: [],
};

/* ---------- small building blocks ---------- */

function AddRow({
Expand Down Expand Up @@ -324,7 +337,7 @@ function StepBuilderCard({
)}
aria-hidden="true"
>
<CheckCheck className="size-icon-xs" />
<CheckCheck className="size-icon-xs" aria-hidden="true" />
</span>
) : null}
</div>
Expand Down Expand Up @@ -406,9 +419,9 @@ function PreviewStep({ def, entries }: { def: StepDef; entries: Entry[] }) {

/* ---------- root ---------- */

export function PatientSafetyPlanMockup() {
const [entries, setEntries] = useState<Record<StepKey, Entry[]>>(SEED);
const [reasons, setReasons] = useState<Entry[]>(SEED_REASONS);
export function PatientSafetyPlan() {
const [entries, setEntries] = useState<Record<StepKey, Entry[]>>(EMPTY_ENTRIES);
const [reasons, setReasons] = useState<Entry[]>([]);
const [patient, setPatient] = useState("");
const [planDate, setPlanDate] = useState("");
const [mobileTab, setMobileTab] = useState<"build" | "preview">("build");
Expand Down Expand Up @@ -469,27 +482,52 @@ export function PatientSafetyPlanMockup() {
setCopied(true);
window.setTimeout(() => setCopied(false), 1600);
} catch {
/* clipboard unavailable in some embeds — mockup-safe no-op */
/* clipboard unavailable in some embeds — safe no-op */
}
};

const printPlan = () => {
if (typeof window !== "undefined") window.print();
};

const loadExample = () => {
const hasContent =
Object.values(entries).some((rows) => rows.length > 0) ||
reasons.length > 0 ||
patient.trim() !== "" ||
planDate.trim() !== "";
if (hasContent && !window.confirm("Replace the current plan with the example content?")) return;
setEntries(SEED);
setReasons(SEED_REASONS);
Comment thread
BigSimmo marked this conversation as resolved.
// Clear the patient context so example content can never carry a real
// patient's name or date into the preview/print output.
setPatient("");
setPlanDate("");
setFinalised(false);
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.

const clearAll = () => {
setEntries({ warning: [], coping: [], people: [], support: [], professional: [], environment: [] });
setEntries(EMPTY_ENTRIES);
setReasons([]);
setPatient("");
setPlanDate("");
setFinalised(false);
};

return (
<div className="min-w-0 bg-[color:var(--background)] text-[color:var(--text)]">
<main
id="main-content"
tabIndex={-1}
className={cn(
"safety-plan-tool min-w-0 bg-[color:var(--background)] text-[color:var(--text)]",
"focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-[color:var(--focus)]",
)}
>
{/* Tool header */}
<header className="border-b border-[color:var(--border)] bg-[color:var(--surface)]">
<div className="mx-auto grid max-w-7xl gap-4 px-4 py-4 sm:px-6 lg:grid-cols-[minmax(0,1fr)_auto] lg:items-center lg:px-8">
<div className="grid grid-cols-[auto_minmax(0,1fr)] items-start gap-3">
<span className="grid size-11 shrink-0 place-items-center rounded-2xl border border-[color:var(--clinical-accent-border)] bg-[color:var(--clinical-accent-soft)] text-[color:var(--clinical-accent)] shadow-[var(--shadow-inset)]">
<span className="grid size-tap shrink-0 place-items-center rounded-2xl border border-[color:var(--clinical-accent-border)] bg-[color:var(--clinical-accent-soft)] text-[color:var(--clinical-accent)] shadow-[var(--shadow-inset)]">
<ShieldCheck className="size-icon-lg" aria-hidden="true" />
</span>
<div className="min-w-0">
Expand Down Expand Up @@ -536,7 +574,7 @@ export function PatientSafetyPlanMockup() {
type="button"
onClick={() => setFinalised(true)}
disabled={!ready}
className={cn(primaryControl, "min-h-11 disabled:opacity-50")}
className={cn(primaryControl, "min-h-tap disabled:opacity-50")}
Comment thread
BigSimmo marked this conversation as resolved.
>
{finalised ? (
<Check className="size-icon-md" aria-hidden="true" />
Expand All @@ -550,7 +588,7 @@ export function PatientSafetyPlanMockup() {
</header>

{/* Mobile pane switch */}
<div className="mx-auto max-w-7xl px-4 pt-4 sm:px-6 lg:hidden">
<div data-print-hide className="mx-auto max-w-7xl px-4 pt-4 sm:px-6 lg:hidden">
<div
role="tablist"
aria-label="Safety plan view"
Expand Down Expand Up @@ -583,6 +621,7 @@ export function PatientSafetyPlanMockup() {
{/* ---------- Builder ---------- */}
<div
id="spg-panel-build"
data-print-hide
role="tabpanel"
aria-labelledby="spg-tab-build"
className={cn("min-w-0 grid content-start gap-4", mobileTab === "build" ? "grid" : "hidden", "lg:grid")}
Expand All @@ -591,10 +630,16 @@ export function PatientSafetyPlanMockup() {
<h2 className="text-sm font-extrabold uppercase tracking-[0.06em] text-[color:var(--text-soft)]">
Build the plan
</h2>
<button type="button" onClick={clearAll} className={softButton}>
<RotateCcw className="size-icon-sm" aria-hidden="true" />
Clear all
</button>
<div className="flex items-center gap-2">
<button type="button" onClick={loadExample} className={softButton}>
<Sparkles className="size-icon-sm" aria-hidden="true" />
Load example
</button>
<button type="button" onClick={clearAll} className={softButton}>
<RotateCcw className="size-icon-sm" aria-hidden="true" />
Clear all
</button>
</div>
</div>

{/* Patient context */}
Expand Down Expand Up @@ -700,6 +745,7 @@ export function PatientSafetyPlanMockup() {
{/* ---------- Preview ---------- */}
<div
id="spg-panel-preview"
data-safety-plan-copy
role="tabpanel"
aria-labelledby="spg-tab-preview"
className={cn(
Expand All @@ -709,7 +755,7 @@ export function PatientSafetyPlanMockup() {
)}
>
{/* Preview toolbar */}
<div className="mb-3 flex flex-wrap items-center justify-between gap-2">
<div data-print-hide className="mb-3 flex flex-wrap items-center justify-between gap-2">
Comment thread
BigSimmo marked this conversation as resolved.
<h2 className="text-sm font-extrabold uppercase tracking-[0.06em] text-[color:var(--text-soft)]">
Patient copy
</h2>
Expand All @@ -732,6 +778,7 @@ export function PatientSafetyPlanMockup() {
{finalised ? (
<div
role="status"
data-print-hide
className={cn(
"mb-3 flex items-center gap-2 rounded-lg border px-3 py-2 text-sm-minus font-bold",
toneSuccess,
Expand Down Expand Up @@ -823,12 +870,15 @@ export function PatientSafetyPlanMockup() {
</footer>
</article>

<p className="mt-3 flex items-center gap-1.5 px-1 text-2xs font-semibold text-[color:var(--text-soft)]">
<p
data-print-hide
className="mt-3 flex items-center gap-1.5 px-1 text-2xs font-semibold text-[color:var(--text-soft)]"
>
<ChevronRight className="size-icon-xs text-[color:var(--clinical-accent)]" aria-hidden="true" />
Sample content shown — edit every field with your patient.
Confirm every contact and crisis number with your patient before printing or sharing this plan.
</p>
</div>
</div>
</div>
</main>
);
}
11 changes: 10 additions & 1 deletion src/components/tools-page-mockups/tool-fixtures.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Brain, FileCheck2, FileText, Pill, Search, Star, type LucideIcon } from "lucide-react";
import { Brain, ClipboardList, FileCheck2, FileText, Pill, Search, Star, type LucideIcon } from "lucide-react";
import { appModeIcons } from "@/lib/app-mode-icons";
import { toolCatalogRecordById } from "@/lib/tools-catalog";

Expand Down Expand Up @@ -88,6 +88,15 @@ const fixtureExtras: ToolFixtureExtras[] = [
primaryAction: "Open",
secondary: "Forms, tasks, pathway checks",
},
{
id: "safety-plan",
icon: ClipboardList,
area: "care",
status: "ready",
lastUsed: "Today, 9:10 AM",
primaryAction: "Open",
secondary: "Warning signs, coping, supports, means safety",
},
{
id: "favourites",
icon: Star,
Expand Down
29 changes: 29 additions & 0 deletions src/lib/tools-catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,35 @@ export const toolCatalogRecords: ToolCatalogRecord[] = [
neededInput: ["Diagnosis or working problem", "Current plan", "Follow-up timeframe"],
output: "Care-plan structure, review points, and monitoring prompts.",
},
{
id: "safety-plan",
title: "Safety plan",
mobileTitle: "Safety plan",
description: "Build a patient safety plan with the Stanley-Brown six steps and a printable copy.",
bestFor: "Collaborative safety planning",
detail:
"Build an evidence-based safety plan with the patient — warning signs, coping strategies, supports, and means safety — with a live patient copy to print, save as PDF, or hand over.",
href: "/safety-plan",
area: "care",
status: "ready",
sourceBacked: false,
safetyFirst: true,
actionLabel: "Open",
keywords: [
"safety plan",
"safety planning",
"crisis",
"crisis plan",
"stanley brown",
"coping",
"warning signs",
"suicide",
"means safety",
],
checkFirst: ["Current risk and recent changes", "Warning signs and triggers", "Trusted supports and crisis lines"],
neededInput: ["Warning signs", "Coping strategies and supports", "Crisis contacts and means-safety steps"],
output: "A six-step safety plan with a patient copy to print, save as PDF, or hand over.",
},
{
id: "monitoring",
title: "Monitoring",
Expand Down
Loading