The import preview only showed counts + skipped-row errors with no way to
fix anything. Now every parsed record is editable before import:
- backend: shared validatePatientImport() (used by the previewImport tool)
+ POST /api/ai/import/validate for dry-run re-validation; the import
preview payload now carries the original records (and the source record on
each invalid row) so the UI can edit them.
- frontend: the import card gets a "Review & edit" dialog listing every
record with a ready / needs-fixing badge; opening one reuses the full
PatientFormDialog in a new non-persisting review mode (editable file
number) and re-validates on save, so skipped rows can be fixed and
included.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a real file-storage layer to the backend and wire upload UI into the
frontend.
backend:
- new `attachments` table (org-scoped, links to a patient file number and
optionally a lab result) + Drizzle migration
- `/api/attachments` route: upload (multer → disk under UPLOAD_DIR),
list, stream/download, delete; gated by patient:write OR lab:write via
a new requireAnyPermission helper so lab staff can attach analyses
- UPLOAD_DIR env (default ./uploads) + a persistent docker volume
frontend:
- lib/attachments.ts client (multipart upload, list, delete, preview URL)
- staged file picker in the patient Add/Edit dialog (uploaded after save)
and the lab Add-result dialog (linked to the result)
- a Files section in the patient sheet that lists attachments and opens
them in a preview dialog (images inline, others via download)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Messages: search the inbox and the compose member picker.
- Care Team: clickable member rows open an employee dialog showing role +
permissions, with change-role (updateMemberRole) and remove.
- Patients: Primary Care is now a provider dropdown (defaults to self for a
doctor); add a Transfer action + dialog wired to the transfer API.
- Activity: entries are clickable, opening a detail dialog.
- Analytics: Section takes a columns prop so each row fills evenly (no orphan
card in Appointments).
- Add lib/staff.ts (listProviders), transferPatient client, rolePermissionSummary
helper, and i18n keys for all new strings.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the email-invitation flow with admin-provisioned staff accounts and
add role-based access that changes what each member sees.
Backend:
- Enable Better Auth `username` plugin (staff sign in by username); regenerate
auth schema (+ username/displayUsername on user) and migration 0007.
- Add `doctor` and `reception` roles to the access-control RBAC. `reception` is
scoped to scheduling + registration (no `prescription` statement).
- New `/api/staff` route: POST creates a user (auth.api.signUpEmail) and adds
them to the active clinic (auth.api.addMember); GET lists members + usernames.
Gated by requirePermission({ member: ["create"] }).
- Redact clinical PHI for the reception role in the patients service (read,
create and update) so demographics-only is enforced server-side.
Frontend:
- usernameClient + Email|Username tabs on the login form.
- lib/roles.ts: useActiveRole + Better-Auth-permission-driven nav visibility,
default landing, and a route guard (reception -> /appointments, blocked from
clinical routes). Applied to the sidebar, command palette and auth guard.
- Care team page now provisions staff via a two-step Add-team-member dialog
(details -> username/password) hitting /api/staff; removes the email-invite
and pending-invitation UI. New members are contactable from Messages
automatically (they become org members).
- Hide clinical sections of the patient form and the admin-only settings tabs
for non-clinical/non-admin roles.
All permission management stays in Better Auth (per the better-auth skills now
referenced in backend/CLAUDE.md).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Finish the i18n pass: settings panels (profile/care-team/signing), the
chat heading + input, the patient cards / detail / create-edit form, and
the notes page + rich-text editor are all keyed in en/translation.json.
All 526 static t() keys resolve. Document the new backend resources +
Socket.io realtime (backend README/CLAUDE) and the i18n coverage
(frontend CLAUDE).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wire COSS's (Base UI) Sonner-style stacked toast into the app:
- Install @coss/toast (components/ui/toast.tsx) and mount <ToastProvider> in
the root layout; add a small notify.{success,error,info,warning} helper
(lib/toast.ts) over toastManager so callers don't repeat the toast shape.
- Toast on the events requested: clinic created (onboarding), auth actions
(sign in / sign up / sign out / password reset request + reset), patient
saved (create/edit), and all failure branches as error toasts (kept inline
errors as a fallback). New auth toast strings added to the en translation.
- Replace the logo everywhere: overwrite public/temetro-logo.png with the new
1024² square mark (same aspect ratio as the old 500², so existing 32×32
next/image usages stay undistorted; no reference changes needed).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Install COSS Calendar (@coss/calendar, react-day-picker) and add a co-located
DatePicker (Popover + Button trigger + Calendar) in patient-form-dialog. It
reads/writes the same formatted date string the form already uses (formatDate/
parseDate), so the Patient model is unchanged. Replace the free-text Visit 'Date'
and Problem 'Since' inputs with the picker.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- MenuGroupLabel must live inside a MenuGroup (Base UI context) — wrap the
label in nav-user, team-switcher, and nav-notifications. Fixes the
'MenuGroupContext is missing' runtime error.
- Patient form/detail dialogs: COSS DialogPopup has no inner padding; move the
body into DialogPanel (form as 'contents') so fields get p-6 instead of being
flush to the edges.
- Settings 'Delete' button used the default variant + bg override, keeping a
near-white border-primary in dark mode. Use variant='destructive'.
- Auth forms: COSS Field is items-start (no stretch). Make the submit button and
the password-row/grid w-full, and underline the bottom sign-in/up link.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Connects the UI-only app to the new backend over Better Auth + a small
patient API client, replacing the in-memory fixture and placeholder identity.
- Better Auth React client (lib/auth-client.ts) + shared access-control
roles; API client (lib/api-client.ts) sending credentials cross-origin.
- Designed (auth) route group: login, signup, verify-email, forgot/reset
password, clinic onboarding, and accept-invite.
- proxy.ts (this Next's renamed middleware) optimistic redirect + an
authoritative client AppAuthGuard requiring a session and active clinic.
- Real identity in nav-user (useSession + sign out); the unused team
switcher repurposed as an organization (clinic) switcher; Care-team
settings tab manages members and invitations.
- lib/patients.ts now calls the org-scoped API (types unchanged); patients
table and create/edit dialog updated for async create/update.
- next.config: standalone output + Dockerfile for containerized runs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Generalize add-patient-dialog → shared PatientFormDialog (create | edit).
Edit mode prefills from the patient; both modes now cover all sections
with add/remove rows: identity, vitals, allergies, meds, problems, labs,
visits. Save writes to the store (addPatient overwrites by file #).
- Fix the dialog scrollbar: fixed header + footer (Save/Cancel pinned),
only the field body scrolls, scrollbar hidden (no-scrollbar).
- patient-cards: "Edit record" button on the Summary card opens the editor
(stopPropagation so it doesn't open the detail dialog); PatientResult
holds the editor state and exposes onPatientUpdated.
- chat-panel: onPatientUpdated replaces the message's patient in place, so
saved edits update the on-screen cards (no duplicate message, no requery).
- chat-input: Add pill now uses PatientFormDialog mode="create".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>