diff --git a/.design-sync/NOTES.md b/.design-sync/NOTES.md new file mode 100644 index 0000000000..15c5296775 --- /dev/null +++ b/.design-sync/NOTES.md @@ -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. diff --git a/.design-sync/config.json b/.design-sync/config.json new file mode 100644 index 0000000000..d9554e530b --- /dev/null +++ b/.design-sync/config.json @@ -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" + } +} diff --git a/.design-sync/conventions.md b/.design-sync/conventions.md new file mode 100644 index 0000000000..357dd25e1a --- /dev/null +++ b/.design-sync/conventions.md @@ -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 ""; +import { FileText } from "lucide-react"; + +
+ + +
; +``` + +## 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. diff --git a/.design-sync/entry.tsx b/.design-sync/entry.tsx new file mode 100644 index 0000000000..8c323b1700 --- /dev/null +++ b/.design-sync/entry.tsx @@ -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"; diff --git a/.design-sync/font-vars.css b/.design-sync/font-vars.css new file mode 100644 index 0000000000..f0e0629eb4 --- /dev/null +++ b/.design-sync/font-vars.css @@ -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"; +} diff --git a/.design-sync/fonts.css b/.design-sync/fonts.css new file mode 100644 index 0000000000..0a2814d2c1 --- /dev/null +++ b/.design-sync/fonts.css @@ -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; +} diff --git a/.design-sync/previews/AccessibleTable.tsx b/.design-sync/previews/AccessibleTable.tsx new file mode 100644 index 0000000000..47715f0049 --- /dev/null +++ b/.design-sync/previews/AccessibleTable.tsx @@ -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 = () => ( + +); + +export const CompactPreview = () => ( + +); + +export const FromMarkdown = () => ( + 60 | No change |\n| 30–60 | Reduce dose 50% |\n| < 30 | Avoid |`} + /> +); diff --git a/.design-sync/previews/EmptyState.tsx b/.design-sync/previews/EmptyState.tsx new file mode 100644 index 0000000000..8e4a8f77aa --- /dev/null +++ b/.design-sync/previews/EmptyState.tsx @@ -0,0 +1,18 @@ +import { EmptyState } from "prompt-for-codex-medical-knowledge-base"; +import { Inbox, SearchX } from "lucide-react"; + +export const NoDocuments = () => ( + +); + +export const NoResults = () => ( + +); diff --git a/.design-sync/previews/InlineNotice.tsx b/.design-sync/previews/InlineNotice.tsx new file mode 100644 index 0000000000..5dee82cbb2 --- /dev/null +++ b/.design-sync/previews/InlineNotice.tsx @@ -0,0 +1,25 @@ +import { InlineNotice } from "prompt-for-codex-medical-knowledge-base"; + +export const Success = () => ( + Document indexed — 42 chunks embedded and searchable. +); + +export const Info = () => ( + A newer edition of this guideline is available from the publisher. +); + +export const Warning = () => ( + + This source is past its scheduled review date — verify before relying on doses. + +); + +export const DangerDismissable = () => ( + {}}> + Upload failed: the PDF could not be parsed. Try re-exporting the document. + +); + +export const Neutral = () => ( + Sign in to keep your uploads private to your workspace. +); diff --git a/.design-sync/previews/LoadingPanel.tsx b/.design-sync/previews/LoadingPanel.tsx new file mode 100644 index 0000000000..b74401b6e4 --- /dev/null +++ b/.design-sync/previews/LoadingPanel.tsx @@ -0,0 +1,7 @@ +import { LoadingPanel } from "prompt-for-codex-medical-knowledge-base"; + +export const Spinner = () => ; + +export const Skeleton = () => ; + +export const SkeletonLong = () => ; diff --git a/.design-sync/previews/PanelHeading.tsx b/.design-sync/previews/PanelHeading.tsx new file mode 100644 index 0000000000..c4e61243a9 --- /dev/null +++ b/.design-sync/previews/PanelHeading.tsx @@ -0,0 +1,12 @@ +import { PanelHeading } from "prompt-for-codex-medical-knowledge-base"; +import { FileText, ShieldCheck } from "lucide-react"; + +export const WithDescription = () => ( + +); + +export const TitleOnly = () => ; diff --git a/.design-sync/previews/SafeBoldText.tsx b/.design-sync/previews/SafeBoldText.tsx new file mode 100644 index 0000000000..d21af7fdd2 --- /dev/null +++ b/.design-sync/previews/SafeBoldText.tsx @@ -0,0 +1,13 @@ +import { SafeBoldText } from "prompt-for-codex-medical-knowledge-base"; + +export const WithBoldSegments = () => ( +

+ +

+); + +export const PlainText = () => ( +

+ +

+); diff --git a/.design-sync/previews/Sheet.tsx b/.design-sync/previews/Sheet.tsx new file mode 100644 index 0000000000..ca2a0e2eb1 --- /dev/null +++ b/.design-sync/previews/Sheet.tsx @@ -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 }) => ( +
+ {children} +
+); + +export const OpenDialog = () => ( + + {}} + title="Filter documents" + description="Narrow the library by status and jurisdiction." + > +
+ + +
+
+ +); + +export const WithFooter = () => ( + + {}} + title="Delete document?" + description="This removes the document and its index entries." + footer={ +
+ + +
+ } + > +

Sepsis pathway v3.2.pdf — 18 indexed chunks.

+
+ +); diff --git a/.design-sync/previews/SourceProvenance.tsx b/.design-sync/previews/SourceProvenance.tsx new file mode 100644 index 0000000000..582517b75c --- /dev/null +++ b/.design-sync/previews/SourceProvenance.tsx @@ -0,0 +1,16 @@ +import { SourceProvenance } from "prompt-for-codex-medical-knowledge-base"; + +export const FullProvenance = () => ( + +); + +export const MinimalProvenance = () => ( + +); diff --git a/.design-sync/previews/SourceStatusBadge.tsx b/.design-sync/previews/SourceStatusBadge.tsx new file mode 100644 index 0000000000..2d5c9f56fc --- /dev/null +++ b/.design-sync/previews/SourceStatusBadge.tsx @@ -0,0 +1,9 @@ +import { SourceStatusBadge } from "prompt-for-codex-medical-knowledge-base"; + +export const Current = () => ; + +export const ReviewDue = () => ; + +export const Outdated = () => ; + +export const Unknown = () => ; diff --git a/.design-sync/previews/ToggleSwitch.tsx b/.design-sync/previews/ToggleSwitch.tsx new file mode 100644 index 0000000000..4462b9f82c --- /dev/null +++ b/.design-sync/previews/ToggleSwitch.tsx @@ -0,0 +1,9 @@ +import { ToggleSwitch } from "prompt-for-codex-medical-knowledge-base"; + +export const On = () => {}} aria-label="Include archived documents" />; + +export const Off = () => {}} aria-label="Include archived documents" />; + +export const Disabled = () => {}} aria-label="Managed by policy" />; + +export const ReadOnlyIndicator = () => ; diff --git a/.gitignore b/.gitignore index 805ef4bdfc..377d5ada80 100644 --- a/.gitignore +++ b/.gitignore @@ -83,3 +83,10 @@ scratch/ .tmp-visual/ *.pid tmp_output.txt + +# design-sync (generated/scratch state) +.ds-sync/ +ds-bundle/ +.design-sync/.cache/ +.design-sync/learnings/ +.design-sync/node_modules/