Files
Khalid Abdi 7846dacd42 chore: release v0.13.0
Bump version to 0.13.0 across root/backend/frontend, add the 0.13.0 CHANGELOG
entry, and refresh stale CLAUDE.md docs (the AI chat is real and @ai-sdk/react
is installed; the signing/approval flow is built).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 21:17:55 +03:00

167 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 under `node_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 is **real**: `components/chat/chat-panel.tsx` streams from the backend's tool-using agent
(`POST /api/chat`) via `useChat` and renders the record data parts it streams back.
- The chat parses `/patient <file#>` (or a bare `/<file#>`) in `components/chat/chat-panel.tsx` and
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`
(mode `create` | `edit`). The "Add patient" pill in `chat-input.tsx` opens it.
- Non-command messages stream a real reply from the backend agent (`POST /api/chat`).
### Auth & data (talks to `../backend`)
- **`lib/patients.ts`** keeps the canonical `Patient` types but its data functions (`getPatient`,
`listPatients`, `createPatient`, `updatePatient`) now call the backend via **`lib/api-client.ts`**
(`fetch` with `credentials: "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 from
`NEXT_PUBLIC_API_URL` (default `http://localhost:4000`); it appends `/api/auth`. **`lib/access.ts`**
mirrors 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 `/login` when no session cookie. The authoritative gate is client-side:
`components/auth/app-auth-guard.tsx` (wrapping `app/(app)/layout.tsx`) requires a session **and**
an active clinic, else redirects to `/login` / `/onboarding`.
- **Clinics (organizations):** `components/sidebar-02/team-switcher.tsx` is now the `OrgSwitcher`;
`components/settings/settings-care-team.tsx` manages members + invitations.
- `.env.local` / `.env.example` hold `NEXT_PUBLIC_API_URL`.
The signing / patient-owned-storage / approval features from the root vision **are built** (clinic
signing key, encrypted share/import, patient approval, clinic→wallet update push, QR pairing).
## Commands
```bash
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 in `lib/utils.ts`.
- **`app/`** — App Router. `app/(app)/page.tsx` is the product chat page (sidebar + chat panel);
`app/layout.tsx` loads the fonts and wraps children in `ThemeProvider` (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
the `render` prop and `useRender`/`mergeProps`, **not `asChild`**, 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 with `npx shadcn@latest add @coss/<name>`.
`carousel.tsx` is the one **non-COSS** primitive kept (no COSS equivalent). Config in
`components.json` (baseColor `neutral`).
- **`components/ai-elements/`** — a large AI-chat primitive library (`PromptInput`, `Conversation`,
`Message`, `Suggestion`, etc.) typed against **AI SDK v6** (`ai` package: `UIMessage`,
`ChatStatus`, `FileUIPart`). `@ai-sdk/react` (`useChat`) **is installed** and drives the live chat
(`chat-panel.tsx`) with a custom transport pointed at `POST /api/chat`.
- **`components/sidebar-02/`** — the dashboard sidebar (`SidebarProvider` / `Sidebar` /
`SidebarInset` from `components/ui/sidebar.tsx`). `app-sidebar.tsx` holds the nav config (New chat
· Patients · Settings) + notifications; `team-switcher.tsx` is the `OrgSwitcher` (clinic switch).
- **`components/chat/`** — the product chat UI. `chat-panel.tsx` owns message state + empty/active
layouts; `chat-input.tsx` is a bespoke (nonai-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/`: currently `en`, `de`, `fr`, `ar`, `so`). Whenever you add or rename a
> translation key, add it to **all** `locales/*/translation.json` files with a real translation for
> each language (not the English string copied over) — leaving a key in only `en/` 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`, not `middleware.ts`** — this customized Next renamed the
convention (`export function proxy(request)` + `config.matcher`, Node runtime). See
`node_modules/next/dist/docs/01-app/.../proxy.md`.
- `components/ai-elements/*` has **pre-existing type/lint errors** (Base UI drift) that used to fail
`next build`. `next.config.ts` now sets `output: "standalone"` plus
`typescript.ignoreBuildErrors` / `eslint.ignoreDuringBuilds` so production/Docker builds succeed —
**type-check app code with `npx tsc --noEmit`** (filter out `components/ai-elements/`), not via
`next build`. There's a `Dockerfile` for the standalone build.
- **`lucide-react@1.17` dropped 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 (with `components/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)
- **`MenuGroupLabel` must live inside `<MenuGroup>` (or `<MenuRadioGroup>`)** — there is no standalone
`MenuLabel`. Using it bare throws `MenuGroupContext is missing` at runtime.
- **`DialogPopup`/`SheetPopup` have no inner padding** — padding comes from
`DialogHeader`/`DialogPanel`/`DialogFooter`. For a form dialog: keep the header outside the form,
wrap **panel + footer** in `<form className="contents">`, and put the body in `DialogPanel`
(see `components/chat/patient-form-dialog.tsx`). Content placed directly in the popup is flush.
- **`Card` has no `size` prop** and **`Avatar` has no `size` prop**. For compact cards, override the
section padding via data-slot selectors (`compactCard` in `patient-cards.tsx`); for avatars use a
`size-*` class.
- **Solid destructive buttons:** use `variant="destructive"`, not the default variant + a `bg-*`
override — the default variant keeps `border-primary`, which is near-white in dark mode.
- **COSS `Field` is `items-start`** (children don't stretch). Add `w-full` to buttons/rows meant to
fill the field. There is **no `FieldGroup`** (use a flex column) and **no `InputGroupButton`**
(use `Button`).
- COSS keeps back-compat aliases (`DialogContent`, `CardContent``CardPanel`, `SelectContent`,
`TooltipContent`, `PopoverContent`, `SheetContent`), so old imports still resolve — but prefer the
canonical `*Popup`/`*Panel` names in new/edited code.
- The vendored `ai-elements/*` were repointed to COSS (live `conversation`/`message` fully migrated;
dormant files via aliased imports). Their remaining `tsc` errors are **pre-existing Base UI drift**,
not regressions — they stay behind `ignoreBuildErrors`.