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
51 changes: 51 additions & 0 deletions .design-sync/NOTES.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
# design-sync notes — Clinical KB

- This repo is the Next.js app itself, not a component library: no `dist/`, no
Storybook. The sync uses the package shape with a hand-authored entry at
`.design-sync/entry.tsx`, passed to the converter via `--entry` (the driver
takes the same flag). Scope (user-confirmed 2026-07-13): the UI-primitives
layer + tokens only — app-level components (ClinicalDashboard, DocumentViewer,
mockups) depend on Supabase/Next internals and are out of scope.
- Styling is Tailwind v4 (`@import "tailwindcss"` in `src/app/globals.css`,
tokens in `@theme` + `:root`/`.dark`). There is no static stylesheet:
`cfg.buildCmd` compiles one with `@tailwindcss/cli` (installed into
`.ds-sync/`, not the repo lockfile) into `.design-sync/.cache/compiled.css`,
then appends `.design-sync/font-vars.css`. Tailwind v4 auto-scans the repo
for class usage, so authored previews under `.design-sync/previews/` are
picked up — always run `buildCmd` before the converter after editing previews.
- Fonts: the app loads Geist/Geist Mono via `next/font/google` (runtime vars
`--font-geist-sans`/`--font-geist-mono`). The bundle ships them from the
`geist` npm package (OFL) installed in `.ds-sync/`; `font-vars.css` maps the
variables to the family names.
- `.ds-sync/` is gitignored scratch: re-syncs must re-run
`npm i esbuild ts-morph @types/react @tailwindcss/cli geist` inside it.
- Worktrees in this repo start without `node_modules` — run `npm ci` first.
- `SourceProvenance`/`SourceStatusBadge` metadata field is
`clinical_validation_status` (NOT `validation_status`) — wrong key silently
falls back to "Not locally validated".
- `Sheet` renders `position:fixed`; its preview wraps stories in a
transformed, explicitly-sized container (see `previews/Sheet.tsx`) so the
overlay stays inside the card. Keep that wrapper on any preview edit.
- Default `guidelinesGlob` swept 46 repo process docs into `guidelines/` —
the config pins a curated 5-file design set; keep it curated.

## Known render warns

- None — 10/10 render clean, no thin/blank/variantsIdentical flags.

## Re-sync risks

- The compiled stylesheet (`.design-sync/.cache/compiled.css`) is generated by
`cfg.buildCmd` from `src/app/globals.css` + repo-wide class scanning; it goes
stale whenever globals.css or component class usage changes — always run
`buildCmd` before the converter.
- The Geist fonts and Tailwind CLI live in gitignored `.ds-sync/` — a fresh
clone must reinstall them with
`npm install --prefix .ds-sync --no-save --package-lock=false esbuild ts-morph @types/react @tailwindcss/cli geist`
or the build fails on `extraFonts`/`buildCmd`.
- Preview props were sanity-checked against component sources on 2026-07-13;
ui-primitives / sheet / AccessibleTable API changes can silently make the
authored previews unrepresentative — the driver re-verifies changed
components, but prop renames need preview edits.
- The `prompt-for-codex-medical-knowledge-base` import specifier in previews is
the package.json `name`; if the repo is renamed, update previews + config.
35 changes: 35 additions & 0 deletions .design-sync/config.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
{
"projectId": "08d6f126-3fd0-4764-aedf-0062a467280a",
"pkg": "prompt-for-codex-medical-knowledge-base",
"globalName": "ClinicalKB",
"shape": "package",
"srcDir": "src",
"tsconfig": "tsconfig.json",
"buildCmd": "node .ds-sync/node_modules/@tailwindcss/cli/dist/index.mjs -i src/app/globals.css -o .design-sync/.cache/compiled.css && cat .design-sync/font-vars.css >> .design-sync/.cache/compiled.css",
"cssEntry": ".design-sync/.cache/compiled.css",
"extraFonts": [".design-sync/fonts.css"],
"readmeHeader": ".design-sync/conventions.md",
"guidelinesGlob": [
"docs/design-system.md",
"docs/clinical-badge-system-guide.md",
"docs/redesign/02-design-direction.md",
"docs/redesign/09-ui-primitives-recipes.md",
"docs/redesign/permanent-colour-direction.md"
],
"overrides": {
"Sheet": { "cardMode": "single", "primaryStory": "OpenDialog", "viewport": "480x640" },
"AccessibleTable": { "cardMode": "column" }
},
"componentSrcMap": {
"InlineNotice": "src/components/ui-primitives.tsx",
"ToggleSwitch": "src/components/ui-primitives.tsx",
"SourceStatusBadge": "src/components/ui-primitives.tsx",
"SourceProvenance": "src/components/ui-primitives.tsx",
"PanelHeading": "src/components/ui-primitives.tsx",
"LoadingPanel": "src/components/ui-primitives.tsx",
"EmptyState": "src/components/ui-primitives.tsx",
"Sheet": "src/components/ui/sheet.tsx",
"SafeBoldText": "src/components/SafeBoldText.tsx",
"AccessibleTable": "src/components/AccessibleTable.tsx"
}
}
56 changes: 56 additions & 0 deletions .design-sync/conventions.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
# Clinical KB — build conventions

## Setup

No provider is required — every component renders standalone. Styling comes
entirely from `styles.css` (compiled Tailwind v4 + design tokens): make sure it
is loaded. Fonts are Geist (UI) and Geist Mono (codes/numbers), shipped in the
bundle; body text inherits `var(--font-sans)` from the stylesheet.

## Styling idiom

Tailwind utility classes, with **all colour/shadow through CSS variables** in
arbitrary-value form — never hardcoded colours:

- Text: `text-[color:var(--text)]`, `--text-heading`, `--text-muted`, `--text-soft`
- Surfaces: `bg-[color:var(--surface)]`, `--surface-raised`, `--surface-inset`, `--surface-subtle`, `--surface-lux`, `--surface-wash`
- Borders: `border-[color:var(--border)]`, `--border-strong`, `--border-lux`
- Accent (primary action colour): `--clinical-accent`, `--clinical-accent-hover`, `--clinical-accent-soft`, `--clinical-accent-border`, `--clinical-accent-contrast`
- Command (primary buttons): `--command`, `--command-hover`, `--command-contrast`
- Status: `--danger-text/-bg/-border/-solid/-solid-contrast`, plus the same
families for `success`, `warning`, `info` (e.g. `--success-text`)
- Shadows: `shadow-[var(--shadow-tight)]`, `--shadow-soft`, `--shadow-hover`, `--shadow-inset`, `--shadow-lux`
- Focus ring: `focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)]`

Radius rules: `rounded-md` chips/pills, `rounded-lg` controls/cards/panels,
`rounded-xl` sheets/dialogs. Tap targets: `min-h-tap` / `h-tap w-tap` (44px).
Dark mode is automatic via the `.dark` class — the variables flip; never write
`dark:` colour overrides yourself.

## Class-string vocabulary (exported constants)

The bundle exports ready-made class strings — compose them instead of
re-deriving surfaces: `panel`, `quietPanel`, `glassPanel`, `raisedCard`,
`insetCard`, `sourceCard`, `evidenceSurface`, `primaryControl`,
`floatingControl`, `toolbarButton`, `navPill`, `metadataPill`, `shellChip`,
`fieldLabel`, `fieldControl`, `fieldControlPlain`, `eyebrowText`, `textMuted`,
`proseMeasure`, `codeText`, `compactMetadataRow`, `iconTile`, `clinicalDivider`.
Join with the exported `cn(...)` helper.

## Example

```tsx
import { PanelHeading, quietPanel, primaryControl, cn } from "<pkg>";
import { FileText } from "lucide-react";

<section className={cn(quietPanel, "p-4 space-y-3")}>
<PanelHeading icon={FileText} title="Document library" description="Indexed guidelines and protocols." />
<button className={primaryControl}>Upload document</button>
</section>;
```

## Where the truth lives

Read `styles.css` for the full token set (`:root` and `.dark` blocks) and each
component's `.d.ts` + `.prompt.md` for its API. Icons are `lucide-react`
components passed as the `icon` prop where accepted.
7 changes: 7 additions & 0 deletions .design-sync/entry.tsx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
// design-sync bundle entry — the app has no library build, so this file
// enumerates the design-layer surface that syncs to claude.ai/design.
// Components AND the utility class-string vocabulary both ride along.
export * from "@/components/ui-primitives";
export { Sheet } from "@/components/ui/sheet";
export { SafeBoldText } from "@/components/SafeBoldText";
export { AccessibleTable } from "@/components/AccessibleTable";
6 changes: 6 additions & 0 deletions .design-sync/font-vars.css
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
/* next/font injects these variables at runtime in the app; the synced bundle
* ships Geist via @font-face instead, so map the variables to the families. */
:root {
--font-geist-sans: "Geist";
--font-geist-mono: "Geist Mono";
}
17 changes: 17 additions & 0 deletions .design-sync/fonts.css
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
/* Geist variable fonts (OFL) from the `geist` npm package installed in
* .ds-sync/ — parsed by design-sync's extraFonts, which copies the woff2s
* into the uploaded bundle's fonts/ directory. */
@font-face {
font-family: Geist;
src: url("../.ds-sync/node_modules/geist/dist/fonts/geist-sans/Geist-Variable.woff2") format("woff2");
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Geist Mono";
src: url("../.ds-sync/node_modules/geist/dist/fonts/geist-mono/GeistMono-Variable.woff2") format("woff2");
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
39 changes: 39 additions & 0 deletions .design-sync/previews/AccessibleTable.tsx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
import { AccessibleTable } from "prompt-for-codex-medical-knowledge-base";

const doseRows = [
["Amoxicillin", "500 mg", "8-hourly", "Oral", "5 days"],
["Doxycycline", "100 mg", "12-hourly", "Oral", "7 days"],
["Benzylpenicillin", "1.2 g", "6-hourly", "IV", "Until stable"],
];
const doseColumns = ["Antibiotic", "Dose", "Frequency", "Route", "Duration"];

export const ClinicalDoseTable = () => (
<AccessibleTable
caption="Community-acquired pneumonia — empirical therapy (adult)"
rows={doseRows}
columns={doseColumns}
/>
);

export const CompactPreview = () => (
<AccessibleTable
caption="Severity assessment (CORB)"
compact
densePreview
previewRows={2}
rows={[
["Confusion", "New onset", "1"],
["Oxygen saturation", "≤ 90%", "1"],
["Respiratory rate", "≥ 30/min", "1"],
["Blood pressure", "Systolic < 90 mmHg", "1"],
]}
columns={["Criterion", "Threshold", "Points"]}
/>
);

export const FromMarkdown = () => (
<AccessibleTable
caption="Renal dose adjustment"
markdown={`| eGFR (mL/min) | Adjustment |\n| --- | --- |\n| > 60 | No change |\n| 30–60 | Reduce dose 50% |\n| < 30 | Avoid |`}
/>
);
18 changes: 18 additions & 0 deletions .design-sync/previews/EmptyState.tsx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
import { EmptyState } from "prompt-for-codex-medical-knowledge-base";
import { Inbox, SearchX } from "lucide-react";

export const NoDocuments = () => (
<EmptyState
icon={Inbox}
title="No documents yet"
body="Upload a guideline or protocol to make it searchable across the workspace."
/>
);

export const NoResults = () => (
<EmptyState
icon={SearchX}
title="No matches"
body="Try a broader term — searches cover titles, content and clinical synonyms."
/>
);
25 changes: 25 additions & 0 deletions .design-sync/previews/InlineNotice.tsx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
import { InlineNotice } from "prompt-for-codex-medical-knowledge-base";

export const Success = () => (
<InlineNotice tone="success">Document indexed — 42 chunks embedded and searchable.</InlineNotice>
);

export const Info = () => (
<InlineNotice tone="info">A newer edition of this guideline is available from the publisher.</InlineNotice>
);

export const Warning = () => (
<InlineNotice tone="warning">
This source is past its scheduled review date — verify before relying on doses.
</InlineNotice>
);

export const DangerDismissable = () => (
<InlineNotice tone="danger" onDismiss={() => {}}>
Upload failed: the PDF could not be parsed. Try re-exporting the document.
</InlineNotice>
);

export const Neutral = () => (
<InlineNotice tone="neutral">Sign in to keep your uploads private to your workspace.</InlineNotice>
);
7 changes: 7 additions & 0 deletions .design-sync/previews/LoadingPanel.tsx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
import { LoadingPanel } from "prompt-for-codex-medical-knowledge-base";

export const Spinner = () => <LoadingPanel label="Searching guidelines…" />;

export const Skeleton = () => <LoadingPanel variant="skeleton" label="Loading answer" lines={3} />;

export const SkeletonLong = () => <LoadingPanel variant="skeleton" label="Loading document list" lines={5} />;
12 changes: 12 additions & 0 deletions .design-sync/previews/PanelHeading.tsx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
import { PanelHeading } from "prompt-for-codex-medical-knowledge-base";
import { FileText, ShieldCheck } from "lucide-react";

export const WithDescription = () => (
<PanelHeading
icon={FileText}
title="Document library"
description="Guidelines, protocols and local policies indexed for search."
/>
);

export const TitleOnly = () => <PanelHeading icon={ShieldCheck} title="Source governance" />;
13 changes: 13 additions & 0 deletions .design-sync/previews/SafeBoldText.tsx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
import { SafeBoldText } from "prompt-for-codex-medical-knowledge-base";

export const WithBoldSegments = () => (
<p style={{ maxWidth: "60ch" }}>
<SafeBoldText text="Give **ceftriaxone 2 g IV daily** for 7 days. Review at **48 hours** and step down to oral therapy once afebrile." />
</p>
);

export const PlainText = () => (
<p style={{ maxWidth: "60ch" }}>
<SafeBoldText text="No dose adjustment is required for mild hepatic impairment." />
</p>
);
58 changes: 58 additions & 0 deletions .design-sync/previews/Sheet.tsx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
import type { ReactNode } from "react";
import { Sheet } from "prompt-for-codex-medical-knowledge-base";

/* Sheet renders position:fixed; the transform on this wrapper makes it the
* containing block so the overlay stays inside the preview card. */
const Frame = ({ children }: { children: ReactNode }) => (
<div style={{ transform: "translateZ(0)", position: "relative", height: 560, overflow: "hidden", borderRadius: 12 }}>
{children}
</div>
);

export const OpenDialog = () => (
<Frame>
<Sheet
open
onClose={() => {}}
title="Filter documents"
description="Narrow the library by status and jurisdiction."
>
<div style={{ display: "grid", gap: "0.75rem", padding: "0.25rem 0" }}>
<label htmlFor="status-select" className="text-sm font-semibold">
Status
</label>
<select
id="status-select"
className="h-11 rounded-lg border border-[color:var(--border)] bg-[color:var(--surface-raised)] px-3 text-sm"
>
<option>Current</option>
<option>Review due</option>
<option>Outdated</option>
</select>
</div>
</Sheet>
</Frame>
);

export const WithFooter = () => (
<Frame>
<Sheet
open
onClose={() => {}}
title="Delete document?"
description="This removes the document and its index entries."
footer={
<div style={{ display: "flex", gap: "0.5rem", justifyContent: "flex-end" }}>
<button className="inline-flex min-h-tap items-center rounded-lg border border-[color:var(--border-lux)] bg-[color:var(--surface-raised)] px-4 text-sm font-semibold">
Cancel
</button>
<button className="inline-flex min-h-tap items-center rounded-lg bg-[color:var(--danger-solid)] px-4 text-sm font-semibold text-[color:var(--danger-solid-contrast)]">
Delete
</button>
</div>
}
>
<p className="text-sm text-[color:var(--text-muted)]">Sepsis pathway v3.2.pdf — 18 indexed chunks.</p>
</Sheet>
</Frame>
);
16 changes: 16 additions & 0 deletions .design-sync/previews/SourceProvenance.tsx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
import { SourceProvenance } from "prompt-for-codex-medical-knowledge-base";

export const FullProvenance = () => (
<SourceProvenance
metadata={{
clinical_validation_status: "approved",
review_date: "2026-03-01",
jurisdiction: "NSW Health",
extraction_quality: "good",
}}
/>
);

export const MinimalProvenance = () => (
<SourceProvenance metadata={{ clinical_validation_status: "unverified", extraction_quality: "partial" }} />
);
9 changes: 9 additions & 0 deletions .design-sync/previews/SourceStatusBadge.tsx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
import { SourceStatusBadge } from "prompt-for-codex-medical-knowledge-base";

export const Current = () => <SourceStatusBadge metadata={{ document_status: "current" }} />;

export const ReviewDue = () => <SourceStatusBadge metadata={{ document_status: "review_due" }} />;

export const Outdated = () => <SourceStatusBadge metadata={{ document_status: "outdated" }} />;

export const Unknown = () => <SourceStatusBadge metadata={{}} />;
9 changes: 9 additions & 0 deletions .design-sync/previews/ToggleSwitch.tsx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
import { ToggleSwitch } from "prompt-for-codex-medical-knowledge-base";

export const On = () => <ToggleSwitch enabled onToggle={() => {}} aria-label="Include archived documents" />;

export const Off = () => <ToggleSwitch enabled={false} onToggle={() => {}} aria-label="Include archived documents" />;

export const Disabled = () => <ToggleSwitch enabled disabled onToggle={() => {}} aria-label="Managed by policy" />;

export const ReadOnlyIndicator = () => <ToggleSwitch enabled aria-label="Source approved" />;
Loading