Patient record: - Add `bloodType` and `phone` to the patient model (schema, canonical types on both backend + frontend, zod validation). `phone` is a demographic field (reception can read/write); `bloodType` is clinical PHI, redacted for the reception role. Surface both in the record sheet, chat summary card, and the add/edit patient form. Migration 0033. Clinic location: - New org-scoped `clinic_settings` table (address/city/country + optional lat/long), service, and routes: GET /api/clinic/settings (any clinician) and PUT /api/clinic/location (owner/admin). Edited in Settings → Signing → Clinic location. Consumed later by the wallet app. Migration 0034. i18n: - Translate all new keys into every shipped locale (en/de/fr/ar/so) and document the "translate into every locale" rule in frontend/CLAUDE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
11 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
@AGENTS.md
The import above is load-bearing: this is a customized Next.js 16 with breaking changes vs. public docs. Before writing any Next.js code (routing, metadata,
next/image,next/font, route handlers, config), read the relevant guide undernode_modules/next/dist/docs/01-app/.
What this is
temetro — an open-source clinical "AI middleman" (see the project vision in the root
../CLAUDE.md). This app is the clinician-facing chat UI. It is now wired to the real
../backend/ for authentication and patient data (no longer a standalone UI-only demo); the AI
chat replies are still mocked (no LLM call yet).
- The chat parses
/patient <file#>(or a bare/<file#>) incomponents/chat/chat-panel.tsxand renders the record as a horizontal row of cards (components/chat/patient-cards.tsx), with small dependency-free trend sparklines (components/chat/sparkline.tsx). Each card opens a detail dialog; the Summary card has an Edit record button. - Patients can be created and edited via the shared
components/chat/patient-form-dialog.tsx(modecreate|edit). The "Add patient" pill inchat-input.tsxopens it. - Non-command messages still get a mock assistant reply.
Auth & data (talks to ../backend)
lib/patients.tskeeps the canonicalPatienttypes but its data functions (getPatient,listPatients,createPatient,updatePatient) now call the backend vialib/api-client.ts(fetchwithcredentials: "include"; 401 →/login). The old in-memory fixture is gone.lib/auth-client.ts— Better Auth React client (useSession,signIn/up/out,organization.*,useActiveOrganization,useListOrganizations). Base URL fromNEXT_PUBLIC_API_URL(defaulthttp://localhost:4000); it appends/api/auth.lib/access.tsmirrors the backend's RBAC roles for the org client.app/(auth)/— designed auth pages (login, signup, verify-email, forgot/reset-password, onboarding = create clinic, accept-invite), in their own chrome-less layout.- Route protection: this Next renames
middleware→proxy.ts(root) — an optimistic redirect to/loginwhen no session cookie. The authoritative gate is client-side:components/auth/app-auth-guard.tsx(wrappingapp/(app)/layout.tsx) requires a session and an active clinic, else redirects to/login//onboarding. - Clinics (organizations):
components/sidebar-02/team-switcher.tsxis now theOrgSwitcher;components/settings/settings-care-team.tsxmanages members + invitations. .env.local/.env.exampleholdNEXT_PUBLIC_API_URL.
The signing / patient-owned-storage / approval features from the root vision are still not built.
Commands
npm run dev # Next dev server (Turbopack) on http://localhost:3000
npm run build # production build (runs a full TypeScript typecheck — see caveat below)
npm run start # serve the production build
npm run lint # eslint (eslint-config-next: core-web-vitals + typescript)
There is no test runner configured (no test script, no vitest/jest/playwright). Don't invent
test commands; verify changes by running the dev server.
Version control: this folder is its own git repo. Commit after every change
(git -C . add -A && git commit), with the Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
trailer. See the root ../CLAUDE.md for the project-wide per-folder commit policy.
Architecture
- Stack: Next.js 16.2.6 (App Router) · React 19 · TypeScript · Tailwind CSS v4. Path alias
@/*→ repo root (tsconfig.json).cn()(clsx + tailwind-merge) lives inlib/utils.ts. app/— App Router.app/(app)/page.tsxis the product chat page (sidebar + chat panel);app/layout.tsxloads the fonts and wraps children inThemeProvider(next-themes) +I18nProvider(see Theming and i18n below).components/ui/— COSS components (built on Base UI@base-ui/react, not Radix), installed via the shadcn CLI from the@coss/*registry. APIs follow Base UI: composition uses therenderprop anduseRender/mergeProps, notasChild, and popups use canonical COSS names (DialogPopup/DialogPanel,MenuPopup,SelectPopup,TooltipPopup,PreviewCard,Group) — COSS also ships back-compat aliases (DialogContent,CardContent,SelectContent,TooltipContent, …). Add/update primitives withnpx shadcn@latest add @coss/<name>.carousel.tsxis the one non-COSS primitive kept (no COSS equivalent). Config incomponents.json(baseColorneutral).components/ai-elements/— a large AI-chat primitive library (PromptInput,Conversation,Message,Suggestion, etc.) typed against AI SDK v6 (aipackage:UIMessage,ChatStatus,FileUIPart). Note:@ai-sdk/react(useChat) is not installed — chat state is managed with local React state.components/sidebar-02/— the dashboard sidebar (SidebarProvider/Sidebar/SidebarInsetfromcomponents/ui/sidebar.tsx).app-sidebar.tsxholds the nav config (New chat · Patients · Settings) + notifications;team-switcher.tsxis theOrgSwitcher(clinic switch).components/chat/— the product chat UI.chat-panel.tsxowns message state + empty/active layouts;chat-input.tsxis a bespoke (non–ai-elements) input matching a specific design.
Theming
Tailwind v4 with @theme inline in app/globals.css, using COSS's default neutral tokens
(@coss/colors-neutral; values reference Tailwind palette vars like --color-neutral-* plus
--alpha()/color-mix()). Both light (:root) and dark (.dark) palettes are defined;
next-themes (components/theme-provider.tsx) toggles them with defaultTheme="dark" +
enableSystem, so the app still defaults to dark. The theme toggle is the Theme item in the
sidebar-footer user menu (components/sidebar-02/nav-user.tsx, via useTheme()). Fonts follow the
COSS variable contract (--font-sans, --font-heading = Inter; --font-mono = Geist Mono). The
radius scale (rounded-2xl … rounded-4xl) is derived from --radius, so those utilities are
larger than stock Tailwind.
Always use semantic tokens (bg-muted, bg-input, border-border, text-muted-foreground,
…), never hardcoded oklch/hex colors — the latter break the light theme. Dark surface tokens are
subtle alphas (--muted/--secondary/--accent ≈ white/4%, --input ≈ white/8%, --border ≈
white/6%), so layered surfaces stay close in lightness.
i18n
i18next + react-i18next (config in lib/i18n/config.ts, resources in
lib/i18n/locales/<lng>/translation.json). components/i18n-provider.tsx wraps the app in
app/layout.tsx. Use const { t } = useTranslation() + nested keys (e.g. t("auth.login.title"))
in client components. To add a language, drop a locales/<lng>/translation.json and register it
in resources/supportedLngs in config.ts.
Translate into EVERY locale, not just English. The app ships multiple languages (
lib/i18n/locales/: currentlyen,de,fr,ar,so). Whenever you add or rename a translation key, add it to alllocales/*/translation.jsonfiles with a real translation for each language (not the English string copied over) — leaving a key in onlyen/ships a broken UI in the others. Keep the nested structure identical across every locale file.
Coverage: essentially all user-facing strings are now keyed (every app page + its dialogs/sheets,
auth pages, settings panels, the sidebar/user menu, chat input, patient cards/detail/form, messages,
notifications, notes). Keys are grouped by feature (appointments.*, patientCard.*, messages.*,
…). When adding UI, add a key rather than a literal. The only intentional literals left are clinical
option values stored as data (appointment types, dose frequencies/durations) and proper nouns
(e.g. Ed25519). There is no language switcher yet (English only); locale is auto-detected.
Gotchas
- Route protection lives in
proxy.ts, notmiddleware.ts— this customized Next renamed the convention (export function proxy(request)+config.matcher, Node runtime). Seenode_modules/next/dist/docs/01-app/.../proxy.md. components/ai-elements/*has pre-existing type/lint errors (Base UI drift) that used to failnext build.next.config.tsnow setsoutput: "standalone"plustypescript.ignoreBuildErrors/eslint.ignoreDuringBuildsso production/Docker builds succeed — type-check app code withnpx tsc --noEmit(filter outcomponents/ai-elements/), not vianext build. There's aDockerfilefor the standalone build.lucide-react@1.17dropped brand glyphs (e.g.Github,Discord) — import them and you get a build error. Use inline SVGs instead.- The marketing landing page is not in this monorepo — it lives in the sibling Desktop folder
~/Desktop/temetro/landing-page(next to~/Desktop/temetro/docs), a separate Next.js app / git repo seeded from this app (withcomponents/landing/). Edit and commit it there. - Multiple lockfiles in the tree produce a harmless Turbopack "inferred workspace root" warning.
COSS composition gotchas (hit during the shadcn→COSS migration)
MenuGroupLabelmust live inside<MenuGroup>(or<MenuRadioGroup>) — there is no standaloneMenuLabel. Using it bare throwsMenuGroupContext is missingat runtime.DialogPopup/SheetPopuphave no inner padding — padding comes fromDialogHeader/DialogPanel/DialogFooter. For a form dialog: keep the header outside the form, wrap panel + footer in<form className="contents">, and put the body inDialogPanel(seecomponents/chat/patient-form-dialog.tsx). Content placed directly in the popup is flush.Cardhas nosizeprop andAvatarhas nosizeprop. For compact cards, override the section padding via data-slot selectors (compactCardinpatient-cards.tsx); for avatars use asize-*class.- Solid destructive buttons: use
variant="destructive", not the default variant + abg-*override — the default variant keepsborder-primary, which is near-white in dark mode. - COSS
Fieldisitems-start(children don't stretch). Addw-fullto buttons/rows meant to fill the field. There is noFieldGroup(use a flex column) and noInputGroupButton(useButton). - COSS keeps back-compat aliases (
DialogContent,CardContent→CardPanel,SelectContent,TooltipContent,PopoverContent,SheetContent), so old imports still resolve — but prefer the canonical*Popup/*Panelnames in new/edited code. - The vendored
ai-elements/*were repointed to COSS (liveconversation/messagefully migrated; dormant files via aliased imports). Their remainingtscerrors are pre-existing Base UI drift, not regressions — they stay behindignoreBuildErrors.