- {!usesScopeSheet && scopeOpen ? (
+ {trustFooterChip ? (
+
+ ) : null}
+ {!hasScopeFooterChip && secondaryFooterChip ? (
+
+ ) : null}
+ {hasScopeFooterChip && !usesScopeSheet && scopeOpen ? (
;
+ passage: string[];
+ tableRows: Array<[string, string, string]>;
+};
const focusRing =
"focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)]";
const defaultQuery = "clozapine monitoring table";
+const mockSources: MockSourceDocument[] = [
+ {
+ slug: "clozapine-monitoring",
+ title: "Clozapine physical health monitoring protocol",
+ fileName: "clozapine-physical-health-monitoring.pdf",
+ kind: "Protocol",
+ defaultPage: 12,
+ pageCount: 18,
+ status: "Current",
+ review: "Review 2026",
+ section: "Blood test monitoring table",
+ summary:
+ "Mock source preview for the command-centre handoff. It keeps the exact page, table evidence, and actions visible without requiring private document authentication.",
+ tags: ["Medication", "Monitoring", "Shared care"],
+ matchedTerms: ["clozapine", "monitoring", "table"],
+ evidence: [
+ { label: "Table evidence", value: "8 rows", icon: Table2, tone: "success" },
+ { label: "PDF page", value: "p.12", icon: FileText, tone: "info" },
+ { label: "Review note", value: "2026", icon: AlertCircle, tone: "warning" },
+ ],
+ passage: [
+ "Monitoring requirements are grouped by treatment stage and missed-dose interval.",
+ "Restart and escalation decisions should be checked against the local protocol table.",
+ "Shared-care transfer requires the monitoring schedule and review responsibility to be visible.",
+ ],
+ tableRows: [
+ ["Stable treatment", "Continue scheduled FBC/ANC checks", "Routine review"],
+ ["Missed dose 48-72h", "Restart pathway and monitoring check", "Prescriber review"],
+ ["Review due", "Confirm local protocol currency", "Document source status"],
+ ],
+ },
+ {
+ slug: "acute-agitation-pathway",
+ title: "Acute agitation clinical pathway",
+ fileName: "acute-agitation-clinical-pathway.pdf",
+ kind: "Guideline",
+ defaultPage: 4,
+ pageCount: 9,
+ status: "Current",
+ review: "Local pathway",
+ section: "Flowchart and escalation pathway",
+ summary:
+ "Mock source preview showing how image and flowchart evidence can stay attached to the selected search result.",
+ tags: ["Risk", "Escalation", "ED"],
+ matchedTerms: ["agitation", "pathway", "flowchart"],
+ evidence: [
+ { label: "Image evidence", value: "flowchart", icon: FileImage, tone: "info" },
+ { label: "PDF page", value: "p.4", icon: FileText, tone: "success" },
+ { label: "Risk pathway", value: "visible", icon: AlertCircle, tone: "warning" },
+ ],
+ passage: [
+ "The pathway separates immediate safety steps from medication and senior review prompts.",
+ "Flowchart evidence remains visible before opening the full source file.",
+ "Escalation points are grouped so the result can be scoped or used for a follow-up answer.",
+ ],
+ tableRows: [
+ ["Immediate risk", "Use local safety pathway", "Escalate"],
+ ["De-escalation", "Document response and triggers", "Review"],
+ ["Senior input", "Confirm local governance", "Open source"],
+ ],
+ },
+ {
+ slug: "mental-health-act-forms",
+ title: "Mental Health Act forms quick reference",
+ fileName: "mental-health-act-forms-reference.pdf",
+ kind: "Quick reference",
+ defaultPage: 2,
+ pageCount: 6,
+ status: "Indexed",
+ review: "Form checklist",
+ section: "Forms and documentation",
+ summary:
+ "Mock source preview for form-heavy results, keeping the document type and target page obvious from the handoff.",
+ tags: ["Forms", "Workflow", "Legal"],
+ matchedTerms: ["forms", "workflow", "reference"],
+ evidence: [
+ { label: "Checklist", value: "forms", icon: BadgeCheck, tone: "success" },
+ { label: "PDF page", value: "p.2", icon: FileText, tone: "info" },
+ { label: "Workflow", value: "legal", icon: AlertCircle, tone: "warning" },
+ ],
+ passage: [
+ "The quick reference groups forms by use case and required documentation step.",
+ "The handoff preserves the target page so users can inspect the original source quickly.",
+ "Scope and answer actions remain available from the selected source preview.",
+ ],
+ tableRows: [
+ ["Assessment", "Open form checklist", "Confirm status"],
+ ["Transfer", "Check required document", "Open source"],
+ ["Review", "Record local governance", "Scope"],
+ ],
+ },
+];
+
async function fetchJson
(url: string, signal: AbortSignal, authorizationHeader: Record): Promise {
const response = await fetch(url, {
cache: "no-store",
@@ -74,12 +199,315 @@ function liveDocumentHref(documentId: string, result: ChunkSearchResult | undefi
return `/documents/${documentId}?${params.toString()}`;
}
+function numberParam(value: string | null, fallback: number) {
+ const parsed = Number(value);
+ return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;
+}
+
+function mockSourceFor(documentHint: string, query: string) {
+ const normalized = `${documentHint} ${query}`.toLowerCase();
+ return (
+ mockSources.find((source) => normalized.includes(source.slug) || normalized.includes(source.title.toLowerCase())) ??
+ (normalized.includes("agitation")
+ ? mockSources.find((source) => source.slug === "acute-agitation-pathway")
+ : null) ??
+ (normalized.includes("mental health act") || normalized.includes("forms")
+ ? mockSources.find((source) => source.slug === "mental-health-act-forms")
+ : null) ??
+ mockSources[0]
+ );
+}
+
+function TonePill({
+ children,
+ tone = "neutral",
+}: {
+ children: React.ReactNode;
+ tone?: "accent" | "info" | "success" | "warning" | "neutral";
+}) {
+ const toneClass =
+ tone === "accent"
+ ? "border-[color:var(--clinical-accent-border)] bg-[color:var(--clinical-accent-soft)] text-[color:var(--clinical-accent)]"
+ : tone === "info"
+ ? "border-[color:var(--info-border)] bg-[color:var(--info-soft)] text-[color:var(--info)]"
+ : tone === "success"
+ ? "border-[color:var(--success-border)] bg-[color:var(--success-soft)] text-[color:var(--success)]"
+ : tone === "warning"
+ ? "border-[color:var(--warning-border)] bg-[color:var(--warning-soft)] text-[color:var(--warning)]"
+ : "border-[color:var(--border-lux)] bg-[color:var(--surface-raised)] text-[color:var(--text-muted)]";
+ return (
+
+ {children}
+
+ );
+}
+
+function EvidenceCard({ label, value, icon: Icon, tone }: MockSourceDocument["evidence"][number]) {
+ const toneClass =
+ tone === "success"
+ ? "border-[color:var(--success-border)] bg-[color:var(--success-soft)] text-[color:var(--success)]"
+ : tone === "warning"
+ ? "border-[color:var(--warning-border)] bg-[color:var(--warning-soft)] text-[color:var(--warning)]"
+ : "border-[color:var(--info-border)] bg-[color:var(--info-soft)] text-[color:var(--info)]";
+ return (
+
+
+
+
+
{label}
+
{value}
+
+ );
+}
+
+function MockDocumentPagePreview({ source, page, chunk }: { source: MockSourceDocument; page: number; chunk: string }) {
+ return (
+
+
+
+
+ Mock source page
+
+
{source.section}
+
+
+ p.{page}
+ {chunk.replaceAll("-", " ")}
+
+
+
+
+
+
+
+
+
+
+
+ {source.passage.slice(1).map((line) => (
+
+ {line}
+
+ ))}
+
+
+
+
+
+ Source row
+ What to review
+ Action
+
+
+
+ {source.tableRows.map((row, index) => (
+
+ {row.map((cell) => (
+
+ {cell}
+
+ ))}
+
+ ))}
+
+
+
+
+
+ {Array.from({ length: 4 }).map((_, index) => {
+ const pageNumber = Math.max(1, page - 1 + index);
+ const active = pageNumber === page;
+ return (
+
+
p.{pageNumber}
+
+
+
+
+
+
+ );
+ })}
+
+
+
+ );
+}
+
+function MockSourceWorkbench({
+ source,
+ page,
+ chunk,
+ query,
+ message,
+ liveHref,
+}: {
+ source: MockSourceDocument;
+ page: number;
+ chunk: string;
+ query: string;
+ message: string;
+ liveHref?: string;
+}) {
+ return (
+
+
+
+
+
+
+
+
+ Mock source preview
+
+
+ {source.title}
+
+
{message}
+
+ {liveHref ? (
+
+
+ Open live document
+
+ ) : null}
+
+
+
+
+
+
+
+ {source.evidence.map((item) => (
+
+ ))}
+
+
+
+
+
+
+ );
+}
+
export function DocumentSearchLiveOpener() {
const router = useRouter();
const searchParams = useSearchParams();
- const { authorizationHeader } = useAuthSession();
+ const { authorizationHeader, status: authStatus } = useAuthSession();
const query = searchParams.get("q")?.trim() || defaultQuery;
const documentHint = searchParams.get("document")?.trim() || "clozapine";
+ const mockSource = useMemo(() => mockSourceFor(documentHint, query), [documentHint, query]);
+ const requestedPage = numberParam(searchParams.get("page"), mockSource.defaultPage);
+ const chunk = searchParams.get("chunk")?.trim() || "best-match";
const [state, setState] = useState({
status: "opening",
message: "Finding an indexed document and matching source chunk.",
@@ -91,6 +519,11 @@ export function DocumentSearchLiveOpener() {
const controller = new AbortController();
async function openLiveDocument() {
+ if (authStatus === "loading") {
+ setState({ status: "opening", message: "Checking browser document access." });
+ return;
+ }
+
try {
setState({ status: "opening", message: "Finding a real indexed document." });
const documentParams = new URLSearchParams({
@@ -118,8 +551,9 @@ export function DocumentSearchLiveOpener() {
if (documents.length === 0) {
setState({
- status: "error",
- message: "No indexed documents are available to open in the live viewer.",
+ status: "mock",
+ message:
+ "No indexed live document was available for this lookup. This mock preview shows the intended source handoff.",
});
return;
}
@@ -161,19 +595,22 @@ export function DocumentSearchLiveOpener() {
} catch (error) {
if (controller.signal.aborted) return;
setState({
- status: "error",
- message: error instanceof Error ? error.message : "The live document could not be opened.",
+ status: "mock",
+ message:
+ error instanceof Error
+ ? `${error.message} Showing the mock source preview instead.`
+ : "The live document could not be opened. Showing the mock source preview instead.",
});
}
}
void openLiveDocument();
return () => controller.abort();
- }, [authorizationHeader, lookupTerm, query, router]);
+ }, [authStatus, authorizationHeader, lookupTerm, query, router]);
return (
-
+
-
-
-
- {state.status === "opening" ? (
+ {state.status === "opening" ? (
+
+
+
- ) : (
-
- )}
-
-
-
- Live document handoff
-
-
- {state.status === "opening" ? "Opening the actual document" : "Could not open the actual document"}
-
-
{state.message}
-
-
-
- {query}
-
-
-
- actual viewer route
-
+
+
+
+ Live document handoff
+
+
+ Opening the actual document
+
+
{state.message}
+
+
+
+ {query}
+
+
+
+ actual viewer route
+
+
-
-
- {state.liveHref ? (
-
-
- Open live document
-
- ) : null}
-
+
+ ) : (
+
+ )}
);
diff --git a/src/components/forms/form-detail-page.tsx b/src/components/forms/form-detail-page.tsx
index daad9f0fb5..d12ee6db03 100644
--- a/src/components/forms/form-detail-page.tsx
+++ b/src/components/forms/form-detail-page.tsx
@@ -644,6 +644,10 @@ export function FormDetailPage({ form }: { form: FormRecord }) {
)}
+
+
Priority facts
@@ -655,10 +659,6 @@ export function FormDetailPage({ form }: { form: FormRecord }) {
-
-
diff --git a/src/components/services/services-navigator-preview.tsx b/src/components/services/services-navigator-preview.tsx
new file mode 100644
index 0000000000..bb6219ab0d
--- /dev/null
+++ b/src/components/services/services-navigator-preview.tsx
@@ -0,0 +1,771 @@
+"use client";
+
+import Link from "next/link";
+import {
+ ArrowRight,
+ Bookmark,
+ Check,
+ ChevronDown,
+ CircleAlert,
+ CircleCheck,
+ CircleX,
+ DollarSign,
+ ExternalLink,
+ Menu,
+ Mic,
+ Phone,
+ Plus,
+ Search,
+ Send,
+ ShieldCheck,
+ SlidersHorizontal,
+ Users,
+ X,
+ type LucideIcon,
+} from "lucide-react";
+import { useMemo, useState } from "react";
+
+import { cn } from "@/components/ui-primitives";
+import { searchServiceRecords, serviceRecords, type ServiceRecord, type ServiceStatusChip } from "@/lib/services";
+
+const defaultQuery = "13YARN crisis support aboriginal phone";
+
+function visibleText(value: string | null | undefined, fallback = "Confirm locally") {
+ return value?.trim() ? value.trim() : fallback;
+}
+
+function chipToneClass(tone: ServiceStatusChip["tone"] | undefined | null) {
+ if (tone === "danger") return "border-red-200 bg-red-50 text-red-700";
+ if (tone === "info") return "border-sky-200 bg-sky-50 text-sky-700";
+ if (tone === "warning") return "border-orange-200 bg-orange-50 text-orange-700";
+ if (tone === "success") return "border-emerald-200 bg-emerald-50 text-emerald-700";
+ return "border-slate-200 bg-slate-50 text-slate-600";
+}
+
+function criterionCounts(records: ServiceRecord[]) {
+ return records.reduce(
+ (totals, service) => {
+ for (const criterion of service.criteria ?? []) {
+ if (criterion.tone === "meet") totals.meets += 1;
+ if (criterion.tone === "caution") totals.cautions += 1;
+ if (criterion.tone === "reject") totals.rejects += 1;
+ }
+ return totals;
+ },
+ { meets: 0, cautions: 0, rejects: 0 },
+ );
+}
+
+function confidenceCounts(records: ServiceRecord[]) {
+ return records.reduce(
+ (totals, service) => {
+ const confidence = service.verification?.confidence ?? "Unknown";
+ if (confidence === "High") totals.high += 1;
+ else if (confidence === "Medium") totals.medium += 1;
+ else if (confidence === "Low") totals.low += 1;
+ else totals.unknown += 1;
+ return totals;
+ },
+ { high: 0, medium: 0, low: 0, unknown: 0 },
+ );
+}
+
+function ServiceBadge({ chip }: { chip: ServiceStatusChip }) {
+ return (
+
+
+ {visibleText(chip.label, "Status")}
+
+ );
+}
+
+function Metric({
+ icon: Icon,
+ label,
+ value,
+ detail,
+}: {
+ icon: LucideIcon;
+ label: string;
+ value: string;
+ detail: string;
+}) {
+ return (
+
+
+
+
+
+ {label}
+ {value}
+ {detail}
+
+
+ );
+}
+
+function ServiceCard({
+ service,
+ index,
+ selected,
+ onToggleSelected,
+ compact = false,
+}: {
+ service: ServiceRecord;
+ index: number;
+ selected: boolean;
+ onToggleSelected: (slug: string) => void;
+ compact?: boolean;
+}) {
+ const rank = index + 1;
+ const highlighted = rank <= 2;
+ const contact = visibleText(service.primaryContact?.value);
+ const route = visibleText(service.primaryContact?.detail ?? service.route, "Referral route pending");
+ const eligibility = visibleText(service.eligibility, "Eligibility pending");
+ const cost = visibleText(service.cost, "Cost pending");
+ const tags = [...(service.catchments ?? []), ...(service.tags ?? [])].slice(0, compact ? 3 : 5);
+
+ return (
+
+
+
+ {rank}
+
+
+
+
+ {service.title}
+
+ {!compact && highlighted ? (
+ Best fit
+ ) : null}
+
+
+ {(service.statusChips ?? []).slice(0, compact ? 3 : 4).map((chip) => (
+
+ ))}
+
+
+ {visibleText(service.subtitle ?? service.bestUse, "Open the record for referral details.")}
+
+
+
onToggleSelected(service.slug)}
+ className="grid h-9 w-9 place-items-center rounded-lg text-[#061740] hover:bg-slate-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#007a78]"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {tags.map((tag, tagIndex) => (
+ 2 ? "max-sm:hidden" : "",
+ )}
+ >
+ {tag}
+
+ ))}
+ {(service.tags?.length ?? 0) + (service.catchments?.length ?? 0) > tags.length ? (
+
+ +1
+
+ ) : null}
+
+
+
+
+ Open
+
+ onToggleSelected(service.slug)}
+ className="inline-flex h-9 min-w-[94px] items-center justify-center gap-1.5 rounded-lg bg-[#007a78] px-3 text-xs font-bold text-white shadow-[0_8px_18px_rgba(0,122,120,0.18)] hover:bg-[#006d6b] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#007a78]"
+ >
+
+ {selected ? "Selected" : "Select"}
+
+
+
+
+ );
+}
+
+function SearchBar({
+ value,
+ onChange,
+ compact = false,
+ showSubmit = true,
+}: {
+ value: string;
+ onChange: (next: string) => void;
+ compact?: boolean;
+ showSubmit?: boolean;
+}) {
+ return (
+
+ );
+}
+
+function Header() {
+ return (
+
+ );
+}
+
+function Stepper() {
+ const steps = [
+ ["1", "Search", "Find services"],
+ ["2", "Shortlist", "Pick best options"],
+ ["3", "Compare", "Review side by side"],
+ ["4", "Refer", "Send with confidence"],
+ ];
+ return (
+
+ {steps.map(([number, title, body], index) => (
+
+
+ {number}
+
+
+
+ {title}
+
+ {body}
+
+
+ ))}
+
+ );
+}
+
+function DesktopRightRail({
+ matches,
+ selected,
+ onToggleSelected,
+}: {
+ matches: ServiceRecord[];
+ selected: ServiceRecord[];
+ onToggleSelected: (slug: string) => void;
+}) {
+ const criteria = criterionCounts(matches);
+ const confidence = confidenceCounts(matches);
+ const localConfirmationCount = matches.filter((service) =>
+ (service.source?.status ?? "").toLowerCase().includes("confirmation"),
+ ).length;
+ const verifiedCount = matches.filter(
+ (service) =>
+ service.verification?.locallyVerified || (service.source?.status ?? "").toLowerCase().includes("source"),
+ ).length;
+ const checklistRows: Array<[string, number, LucideIcon, string]> = [
+ ["Meets", criteria.meets, CircleCheck, "text-emerald-600"],
+ ["Caution", criteria.cautions, CircleAlert, "text-orange-500"],
+ ["Does not meet", criteria.rejects, CircleX, "text-red-600"],
+ ["Source verified", verifiedCount, CircleCheck, "text-emerald-600"],
+ ["Local confirmation", localConfirmationCount, CircleAlert, "text-orange-500"],
+ ];
+
+ return (
+
+
+
+
Referral decision
+
+ Clear
+
+
+ Selected services ({selected.length})
+
+ {selected.map((service, index) => (
+ onToggleSelected(service.slug)}
+ className="grid min-h-16 grid-cols-[2rem_minmax(0,1fr)_auto] items-center gap-2 rounded-lg border border-slate-200 bg-white px-3 text-left"
+ >
+
+ {index + 1}
+
+
+ {service.title}
+
+ {visibleText(service.cost, "Cost pending")} · {visibleText(service.source?.status, "Source pending")}
+
+
+
+
+ ))}
+
+
+
+
+
+
Checklist
+
+ Edit
+
+
+
+ {checklistRows.map(([label, count, Icon, color]) => (
+
+
+
+ {label}
+
+ {count}
+
+ ))}
+
+
+ Review details
+
+
+
+
+
+
+
Source confidence
+
+ View details
+
+
+
+
+
+
+
+
+
+
+ High
+
+ {confidence.high}
+
+
+ Medium
+
+ {confidence.medium}
+
+
+ Low
+
+ {confidence.low}
+
+
+ Unknown
+
+ {confidence.unknown}
+
+
+
+
+
+ Compare selected ({selected.length})
+
+
+
+ Next step
+ Compare services side by side before referral.
+
+
+
+
+
+
+ );
+}
+
+function PhonePreview({
+ query,
+ onQueryChange,
+ matches,
+ selectedSlugs,
+ onToggleSelected,
+}: {
+ query: string;
+ onQueryChange: (next: string) => void;
+ matches: ServiceRecord[];
+ selectedSlugs: string[];
+ onToggleSelected: (slug: string) => void;
+}) {
+ return (
+
+
+
+
+
+
+
+
+
+ Services Navigator
+
+
+
+
+
+
+
{matches.length} referral matches
+
+ Best fit for crisis, ATSI-specific, phone referral.
+
+
+
+ Sort
+
+
+
+
+ {["Best fit", "Crisis", "ATSI-specific", "+3"].map((chip, index) => (
+ 2 ? "max-sm:hidden" : "",
+ index === 0 ? "border-[#007a78] bg-[#007a78] text-white" : "border-slate-200 bg-white text-slate-600",
+ )}
+ >
+ {chip}
+
+ ))}
+
+
+
+ Filters
+
+
+ {matches.slice(0, 3).map((service, index) => (
+
+ ))}
+
+
+
+
{selectedSlugs.length} selected
+
Compare
+
+
+
+
+
+ );
+}
+
+export function ServicesNavigatorPreview() {
+ const [query, setQuery] = useState(defaultQuery);
+ const matches = useMemo(() => {
+ const ranked = searchServiceRecords(query);
+ return ranked.length ? ranked.map((match) => match.service) : serviceRecords;
+ }, [query]);
+ const [selectedSlugs, setSelectedSlugs] = useState(() => serviceRecords.slice(0, 2).map((service) => service.slug));
+ const selectedServices = serviceRecords.filter((service) => selectedSlugs.includes(service.slug));
+
+ function toggleSelected(slug: string) {
+ setSelectedSlugs((current) => {
+ if (current.includes(slug)) return current.filter((item) => item !== slug);
+ return [slug, ...current].slice(0, 5);
+ });
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+ Filters
+
+
+
+
+
+
+
+
+ {matches.length} referral matches
+
+
+ Best fit for crisis, ATSI-specific, phone referral.
+
+
+
+ Sort
+
+
+
+
+ {["Best fit", "Crisis", "ATSI-specific", "Phone referral", "Free", "WA"].map((chip, index) => (
+ setQuery(index === 0 ? defaultQuery : chip)}
+ className={cn(
+ "min-h-8 rounded-full border px-3 text-xs font-bold",
+ index > 2 ? "max-sm:hidden" : "",
+ index === 0
+ ? "border-[#007a78] bg-[#007a78] text-white"
+ : "border-slate-200 bg-white text-slate-600",
+ )}
+ >
+ {chip}
+
+ ))}
+ setQuery("")}
+ className="min-h-8 px-2 text-xs font-bold text-blue-600"
+ >
+ Clear all
+
+
+
+ {matches.map((service, index) => (
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/tools-page-mockups/rectangle-direction-mockups.tsx b/src/components/tools-page-mockups/rectangle-direction-mockups.tsx
new file mode 100644
index 0000000000..6e5afbdea6
--- /dev/null
+++ b/src/components/tools-page-mockups/rectangle-direction-mockups.tsx
@@ -0,0 +1,527 @@
+"use client";
+
+import Link from "next/link";
+import {
+ ArrowRight,
+ BookOpen,
+ CheckCircle2,
+ ClipboardList,
+ Clock3,
+ FileText,
+ HeartPulse,
+ Pill,
+ Pin,
+ Search,
+ ShieldCheck,
+ Sparkles,
+ Star,
+ Stethoscope,
+ type LucideIcon,
+} from "lucide-react";
+import type { ReactNode } from "react";
+
+import { cn } from "@/components/ui-primitives";
+
+import { areaLabels, pinnedToolIds, toolById, tools, type ToolFixture } from "./tool-fixtures";
+import { useToolFilter, type ToolFilterId } from "./use-tool-filter";
+
+const focusRing =
+ "focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)]";
+
+function IconTile({ icon: Icon, active = false }: { icon: LucideIcon; active?: boolean }) {
+ return (
+
+
+
+ );
+}
+
+function SearchBar({
+ value,
+ onChange,
+ placeholder = "Search tools by clinical job, source, or workflow",
+}: {
+ value: string;
+ onChange: (value: string) => void;
+ placeholder?: string;
+}) {
+ return (
+
+ );
+}
+
+function CompactHeader({
+ title,
+ body,
+ query,
+ onQueryChange,
+ children,
+}: {
+ title: string;
+ body: string;
+ query: string;
+ onQueryChange: (value: string) => void;
+ children?: ReactNode;
+}) {
+ return (
+
+
+
+
+
+ {title}
+
+
+ {body}
+
+
+ {children}
+
+
+
+
+ );
+}
+
+function ActionPill({ label }: { label: string }) {
+ return (
+
+ {label}
+
+
+ );
+}
+
+function RectangleToolCard({
+ tool,
+ featured = false,
+ dense = false,
+}: {
+ tool: ToolFixture;
+ featured?: boolean;
+ dense?: boolean;
+}) {
+ return (
+
+
+
+
+
+ {tool.title}
+
+
+ {tool.description}
+
+
+
+
+
+
{tool.secondary}
+
+ {featured ? : null}
+ {featured ? "Suggested" : areaLabels[tool.area]}
+
+
+
+ );
+}
+
+function SavedWorkPanel() {
+ const saved = [
+ { title: "Lithium monitoring plan", tool: "Documents", icon: FileText },
+ { title: "Medication review draft", tool: "Medication", icon: Pill },
+ { title: "13YARN referral pathway", tool: "Services", icon: ClipboardList },
+ ];
+
+ return (
+
+
+
Saved work
+
+ View all
+
+
+
+ {saved.map((item) => {
+ const Icon = item.icon;
+ return (
+
+
+
+ {item.title}
+ {item.tool}
+
+
+
+ );
+ })}
+
+
+ );
+}
+
+function QuickStartPanel() {
+ const quickStarts = [
+ { label: "Ask a question", href: "/?mode=answer", icon: Search },
+ { label: "Compare differentials", href: "/differentials", icon: Stethoscope },
+ { label: "Prepare prescribing", href: "/?mode=prescribing", icon: Pill },
+ ];
+
+ return (
+
+
+ {quickStarts.map((item) => {
+ const Icon = item.icon;
+
+ return (
+
+
+
{item.label}
+
+
+ );
+ })}
+
+
+ );
+}
+
+function PhonePreview({ toolIds, title }: { toolIds: string[]; title: string }) {
+ const phoneTools = toolIds.map(toolById);
+
+ return (
+
+
+
+
+
+ Tools
+
+
+
+
+
+
Search clinical tools
+
+
+ {phoneTools.map((tool, index) => (
+
+
+
+
+ {tool.title}
+
+
+ {tool.primaryAction}
+
+
+
+
+ ))}
+
+
+
+
+ );
+}
+
+const workflowGroups = [
+ {
+ id: "assess",
+ title: "Assess",
+ body: "Start or refine a clinical view before acting.",
+ icon: Stethoscope,
+ toolIds: ["differentials", "clinical-kb-search"],
+ },
+ {
+ id: "reference",
+ title: "Reference",
+ body: "Find the source, page, table, or answer.",
+ icon: BookOpen,
+ toolIds: ["documents", "clinical-kb-search"],
+ },
+ {
+ id: "treat",
+ title: "Treat",
+ body: "Move from evidence to prescribing support.",
+ icon: HeartPulse,
+ toolIds: ["medication-prescribing", "documents"],
+ },
+ {
+ id: "coordinate",
+ title: "Coordinate",
+ body: "Connect the next referral, service, or form.",
+ icon: ClipboardList,
+ toolIds: ["services", "forms"],
+ },
+];
+
+function WorkflowLane({ group }: { group: (typeof workflowGroups)[number] }) {
+ const Icon = group.icon;
+
+ return (
+
+
+
+
+
{group.title}
+
{group.body}
+
+
+
+ {group.toolIds.map((id, index) => (
+
+ ))}
+
+
+ );
+}
+
+export function ToolsClinicalLanesMockup() {
+ const filter = useToolFilter(tools);
+
+ return (
+
+
+
+
+
+
+
+
+ {workflowGroups.map((group) => (
+
+ ))}
+
+
+
+
+
+ );
+}
+
+const workbenchFilters: { id: ToolFilterId; label: string; icon: LucideIcon }[] = [
+ { id: "all", label: "All", icon: CheckCircle2 },
+ { id: "clinical", label: "Clinical", icon: Stethoscope },
+ { id: "admin", label: "Workflow", icon: ClipboardList },
+ { id: "pinned", label: "Pinned", icon: Pin },
+];
+
+function FilterBar({ filterId, onToggle }: { filterId: ToolFilterId; onToggle: (filterId: ToolFilterId) => void }) {
+ return (
+
+ {workbenchFilters.map((item) => {
+ const Icon = item.icon;
+ const active = filterId === item.id;
+
+ return (
+ onToggle(item.id)}
+ className={cn(
+ "inline-flex min-h-10 shrink-0 items-center gap-2 rounded-md border px-3 text-sm font-bold",
+ active
+ ? "border-[color:var(--clinical-accent-border)] bg-[color:var(--clinical-accent-soft)] text-[color:var(--clinical-accent)]"
+ : "border-[color:var(--border)] bg-[color:var(--surface)] text-[color:var(--text-muted)] hover:text-[color:var(--text)]",
+ focusRing,
+ )}
+ >
+
+ {item.label}
+
+ );
+ })}
+
+ );
+}
+
+function ContinueStrip() {
+ return (
+
+
+
+
Continue medication review
+
+ Medication Prescribing · Monitoring plan review · May 12, 2025
+
+
+
+ Resume
+
+
+
+ );
+}
+
+export function ToolsActionWorkbenchMockup() {
+ const filter = useToolFilter(tools);
+ const visibleTools = filter.filtered.length ? filter.filtered : tools;
+
+ return (
+
+
+
+
+ Saved work
+
+
+
+
+
+
+
+
+
+
+
Tool workbench
+
+ Start with a focused task, source lookup, referral, form, or prescribing workflow.
+
+
+
+ {visibleTools.map((tool) => (
+ id === tool.id)} />
+ ))}
+
+
+
+
+
+
+
+
Useful shortcuts
+
+
+ {[
+ ["Ask a clinical question", "/?mode=answer"],
+ ["Find a source document", "/?mode=documents"],
+ ["Compare differentials", "/differentials"],
+ ["Prepare a prescription", "/?mode=prescribing"],
+ ].map(([label, href]) => (
+
+
{label}
+
+
+ ))}
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/tools-page-mockups/tools-page-mockup-page.tsx b/src/components/tools-page-mockups/tools-page-mockup-page.tsx
index d18dd2b941..6842229fe5 100644
--- a/src/components/tools-page-mockups/tools-page-mockup-page.tsx
+++ b/src/components/tools-page-mockups/tools-page-mockup-page.tsx
@@ -23,7 +23,7 @@ import {
X,
type LucideIcon,
} from "lucide-react";
-import type { ReactNode } from "react";
+import { useState, type ReactNode } from "react";
import { cn } from "@/components/ui-primitives";
@@ -311,6 +311,58 @@ function ToolCard({ tool, suggested = false }: { tool: ToolFixture; suggested?:
);
}
+function SelectableToolCard({
+ tool,
+ selected = false,
+ suggested = false,
+ onSelect,
+}: {
+ tool: ToolFixture;
+ selected?: boolean;
+ suggested?: boolean;
+ onSelect: () => void;
+}) {
+ return (
+
+
+
+
+
+
{tool.title}
+
+ {suggested ? : null}
+
+ {tool.sourceBacked ? : null}
+
+
+
+ {tool.description}
+
+
{tool.secondary}
+
+
+
+
{tool.lastUsed}
+
+ Preview
+
+
+
+
+ );
+}
+
function StatsStrip() {
const stats = [
{ label: "Tools", value: String(tools.length), icon: Grid2X2 },
@@ -543,12 +595,19 @@ function PhoneBrowserPreview({
title,
toolIds,
mode = "launch",
+ selectedTool,
+ onSelectTool,
+ onBackToDirectory,
}: {
title: string;
toolIds: string[];
mode?: "launch" | "workflow" | "directory";
+ selectedTool?: ToolFixture;
+ onSelectTool?: (tool: ToolFixture) => void;
+ onBackToDirectory?: () => void;
}) {
const featured = toolIds.map(toolById);
+ const SelectedIcon = selectedTool?.icon;
return (
@@ -574,93 +633,170 @@ function PhoneBrowserPreview({
-
-
- {mode === "workflow" ? (
-
- {["Assess", "Reference", "Treat", "Coordinate"].map((label) => (
-
- {label}
-
- ))}
-
- ) : null}
+ {selectedTool && SelectedIcon ? (
+
+
+
+ All tools
+
-
- {featured.map((tool, index) => {
- const Icon = tool.icon;
- return (
+
+
+
+ {selectedTool.title}
+
+
+ {selectedTool.description}
+
+
+
+ {selectedTool.sourceBacked ? : null}
+
+
+
+
+ Best for
+
+
+ {selectedTool.secondary}
+
+
+
+
+ Last used
+
+
+ {selectedTool.lastUsed}
+
+
+
-
-
-
- {tool.title}
-
-
- {areaLabels[tool.area]}
-
-
-
- {tool.sourceBacked ? (
-
- ) : null}
-
-
+ Open {selectedTool.primaryAction.toLowerCase()}
+
- );
- })}
-
+
-
-
-
- Recent work
-
- View
+
-
- {recentWork.slice(0, 2).map((item) => {
- const Icon = item.icon;
- return (
-
-
-
-
- {item.title}
+ ) : null}
+
+ {!selectedTool ? (
+ <>
+
+
+ {mode === "workflow" ? (
+
+ {["Assess", "Reference", "Treat", "Coordinate"].map((label) => (
+
+ {label}
+
+ ))}
+
+ ) : null}
+
+
+ {featured.map((tool, index) => {
+ const Icon = tool.icon;
+ const interactive = typeof onSelectTool === "function";
+ const rowClassName = cn(
+ "grid min-h-14 grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-2 rounded-md bg-[color:var(--surface)] px-2 text-left shadow-[var(--shadow-inset)]",
+ mode === "directory" && "rounded-none border-t border-[color:var(--border)] first:border-t-0",
+ index === 0 && mode !== "directory" && "border border-[color:var(--clinical-accent-border)]",
+ focusRing,
+ );
+ const rowContent = (
+ <>
+
+
+
+ {tool.title}
+
+
+ {areaLabels[tool.area]}
+
-
- {item.area}
+
+ {tool.sourceBacked ? (
+
+ ) : null}
+
-
-
-
- );
- })}
-
-
+ >
+ );
+
+ return interactive ? (
+
onSelectTool(tool)} className={rowClassName}>
+ {rowContent}
+
+ ) : (
+
+ {rowContent}
+
+ );
+ })}
+
+
+
+
+
+ Recent work
+
+ View
+
+
+ {recentWork.slice(0, 2).map((item) => {
+ const Icon = item.icon;
+ return (
+
+
+
+
+ {item.title}
+
+
+ {item.area}
+
+
+
+
+ );
+ })}
+
+
+ >
+ ) : null}
@@ -917,6 +1053,9 @@ const splitPaneFilters: { id: ToolFilterId; label: string; icon: LucideIcon }[]
function SplitPaneMockup() {
const filter = useToolFilter(tools);
const suggestedId = "services";
+ const [selectedToolId, setSelectedToolId] = useState(suggestedId);
+ const selectedTool = selectedToolId ? toolById(selectedToolId) : undefined;
+ const overviewToolIds = ["clinical-kb-search", suggestedId, "medication-prescribing", "favourites"];
return (
<>
@@ -995,13 +1134,21 @@ function SplitPaneMockup() {
Launcher overview
- Filters sit beside the overview, while the full-width All tools view below carries the main browsing
- weight.
+ Choose a tool to preview its purpose and recent context in the phone frame before opening it.
- {["clinical-kb-search", suggestedId, "medication-prescribing", "favourites"].map((id) => (
-
- ))}
+ {overviewToolIds.map((id) => {
+ const tool = toolById(id);
+ return (
+ setSelectedToolId(id)}
+ />
+ );
+ })}
>
)}
@@ -1012,6 +1159,9 @@ function SplitPaneMockup() {
title="Pocket directory"
toolIds={["clinical-kb-search", "documents", "differentials", suggestedId, "forms", "favourites"]}
mode="directory"
+ selectedTool={selectedTool}
+ onSelectTool={(tool) => setSelectedToolId(tool.id)}
+ onBackToDirectory={() => setSelectedToolId("")}
/>
diff --git a/tests/ui-tools.spec.ts b/tests/ui-tools.spec.ts
index eeee5c89fa..c9630429df 100644
--- a/tests/ui-tools.spec.ts
+++ b/tests/ui-tools.spec.ts
@@ -236,6 +236,13 @@ test.describe("Clinical KB applications launcher", () => {
expect(headingBox).not.toBeNull();
expect((searchBox?.y ?? 0) + (searchBox?.height ?? 0) / 2).toBeGreaterThan(820 * 0.72);
expect((headingBox?.y ?? 0) + (headingBox?.height ?? 0)).toBeLessThan(searchBox?.y ?? 0);
+ if (home.path === "/forms") {
+ await expect(page.getByRole("button", { name: "Open source scope" })).toHaveCount(0);
+ await page.getByRole("button", { name: "Open the form library" }).click();
+ await expect(page).toHaveURL(/\/forms$/);
+ await expect(page.getByRole("button", { name: "Mode Forms" })).toBeVisible();
+ await expect(page.getByTestId("form-search-results")).toHaveCount(0);
+ }
await expectNoPageHorizontalOverflow(page);
}
});