Commit Graph

57 Commits

Author SHA1 Message Date
Khalid Abdi e656eae362 settings: Records import & export for temetro data
Backend: GET /api/settings/records/export downloads the clinic's full patient
archive as JSON; POST /api/settings/records/import creates new patients (skips
existing file numbers, drops cross-clinic provider links). Both admin-gated.

Frontend: Records settings now has a working Export button and an Import flow
(choose file -> preview count -> apply) wired to the new endpoints.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 18:44:37 +03:00
Khalid Abdi bbc6869745 tasks: assign a task to a specific person
Backend: add tasks.assignee_user_id (nullable, fk user) + migration; persist
it through create/update and surface tasks to the assigned person in listTasks.

Frontend: New Task dialog gains a three-way Assignee control
(Myself / Department / Person) with a provider picker from /api/staff/providers;
task card + detail show the assignee's name when set.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 18:40:56 +03:00
Khalid Abdi 913a217e1d feat(meetings): live invite, scheduling calendar, Discord-style redesign
- Live invite: ring a clinic member into a room (socket call:invite + a bell
  notification); the invitee gets a toast with a Join action. Notifications of
  type "meeting" deep-link to /messages/meetings?room=, which auto-joins.
- Scheduling: new scheduled_meetings table + /api/meetings/events (list mine,
  create, delete); a Calendar tab on the Meetings page with a month picker
  (meeting-day dots), the day's agenda, and a Schedule-meeting dialog
  (title/date/time/participants).
- Redesign: rounded control bar with tooltips (mic/cam/screen/invite + separated
  red Leave), speaking ring on tiles (Web Audio), and live room-occupancy counts
  via call:presence broadcasts. Migration 0025.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 02:07:41 +03:00
Khalid Abdi e841cb56e1 fix(chat,analysis,sidebar): compact cards, sparse citations, range + active state
- Chat patient cards are now compact previews (header + key stat + "Click for
  more"); cards size to content (items-start) instead of stretching to the tallest;
  Edit record moved into the summary detail dialog
- AI citations render once per source per message (collapses per-word spam) and the
  prompt now asks the model to cite sparingly (once per paragraph, not per list item)
- Sidebar sub-items use exact-match active state so Meetings no longer lights Inbox
- Analysis defaults to All Time, drops the 12m option, and clamps the chart window
  to >=2 points so 30d/Today render instead of collapsing

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 01:57:31 +03:00
Khalid Abdi c88b674196 feat(meetings): Discord-style staff voice/video calls under Messages
Backend: meeting_rooms table + /api/meetings (list/create/delete, org-scoped),
and WebRTC mesh signaling over the existing authed Socket.io (call:join with a
≤4 cap and org authorization, call:signal relay, peer-joined/left, disconnect
cleanup). Migration 0024.

Frontend: Messages nav is now expandable (Inbox + Meetings); new
/messages/meetings page with a room list and a Discord-style call UI — a
useWebRtcMesh hook (camera/mic, screen share via replaceTrack, ≤4 mesh) and a
tile grid with a mic/camera/screen/leave control bar. New lib/meetings + i18n.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 20:48:26 +03:00
Khalid Abdi 0fa2802723 feat(ai): inline source citations in the AI chat
Retrieval tools now register a PHI-free sourceId per record/list and stream a
data-source part with the real clinician-facing title; the system prompt asks
the model to cite facts inline as [[src:id]]. The chat renders those markers as
numbered inline citation chips (PreviewCard hover) via a Streamdown link
override, with a Sources footer fallback when the model omits markers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 20:41:48 +03:00
Khalid Abdi 2f36875d37 backend: add per-clinic staff specialty (Care Team -> patient sheet)
New staff_profile table (org + user, unique) holding a clinician's clinical
specialty. GET /api/staff and /api/staff/providers now include specialty; new
PATCH /api/staff/:userId (member:update) upserts it. Migration 0023.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 20:28:52 +03:00
Khalid Abdi 1c52dcaf84 fix(chat): graph mode renders the record graph, not cards
In Graph mode, a natural-language request ("graph for John Smith") went
through the getPatient tool, which always emitted record cards — so the
clinician got Summary/Vitals cards instead of a graph (the mode prompt
even wrongly claimed the graph rendered automatically). Thread the
composer mode into the chat tools and have getPatient emit
data-recordGraph in graph mode (matching the client-side /patient
fast-path), with the cards kept for chat/analysis modes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 21:45:09 +03:00
Khalid Abdi f9615fa74e feat: edit parsed records before importing
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>
2026-06-18 21:03:33 +03:00
Khalid Abdi 064aa22099 backend: AI normalizes imports itself + never replies blank
- Migration prompt now tells the agent to map/normalize an uploaded export
  into temetro's shape itself (gender words, non-numeric IDs, delimited
  allergy/med/problem lists, per-visit encounters) and to fix-and-re-preview
  skipped rows rather than telling the clinician to reformat the file.
- Guarantee a non-empty reply: when the model ends on a tool call with no
  text, ask it to summarize (external+Veil path) and fall back to a generic
  line; the streaming path appends the same fallback. Raise the step cap to 8.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 20:52:33 +03:00
Khalid Abdi 2c9cec0112 backend: tolerant patient import validation
Real-world exports were rejected wholesale (file number must be digits,
sex must be M/F, every allergy/med/problem/encounter a full object), so
AI imports skipped everything. Normalize at the schema edge instead:
strip non-digits from the file number, map gender words to M/F, accept a
bare string for allergies/medications/problems (filling sensible
defaults), and default missing encounter fields (type → "Visit"). The
manual patient form shares this schema and benefits too.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 20:52:33 +03:00
Khalid Abdi 43eaccb97e feat: HL7/FHIR, e-prescribing & insurance claims integrations
Real, standards-compliant integration clients that the clinic points at
its own (sandbox or production) endpoints — no mock data.

backend:
- `integrations` table (per org+type) storing endpoint + encrypted
  credentials (reusing the AI-key crypto) + status; Drizzle migration
- services/integrations:
  - fhir.ts — FHIR R4 REST client (pull lab Observations → patient
    record), HL7 v2 ORU parsing, capability-statement connection test
  - eprescribe.ts — NCPDP SCRIPT NewRx message build + transmit
  - claims.ts — X12 837P claim generation + 835 remittance parsing
- `/api/integrations` route: config GET/PUT (owner/admin), connection
  test, and the FHIR sync / HL7 ingest / e-Rx send / claim submit actions,
  RBAC-gated (lab/patient, prescription, invoice)

frontend:
- lib/integrations.ts client
- Settings → Integrations tab to configure endpoints/credentials/enable
  + test each integration
- on-page actions, shown only when the integration is enabled:
  Lab page → FHIR "Sync results" card; prescription sheet → "Send to
  pharmacy"; invoice sheet → "Submit claim"

Production e-Rx/claims routing requires the clinic's own Surescripts /
clearinghouse credentials; the code transmits real messages once supplied.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 20:15:00 +03:00
Khalid Abdi b1abb29108 feat: patient & lab file attachments (real backend storage)
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>
2026-06-18 20:04:03 +03:00
Khalid Abdi be3968ce64 chore: add community health files
Add MIT LICENSE (root, backend, frontend), CONTRIBUTING.md, CODE_OF_CONDUCT.md,
SECURITY.md, GitHub issue templates (bug/feature), and PR template.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 20:50:09 +03:00
Khalid Abdi d30c310336 feat(chat): replace model picker with situation modes (Chat/Analysis/Graph)
The composer's SELECT is now a clinician-facing mode picker instead of a list
of LLM vendors (the model still comes from Settings → AI and continues to drive
the Veil consent gate). The mode travels with each send:

- backend appends an Analysis/Graph directive to the agent's system prompt
- Graph mode renders an Obsidian-style RecordGraph for the /patient fast-path
  (new data-recordGraph message part reusing the shared graph component)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 19:30:12 +03:00
Khalid Abdi d37a4e9425 feat: delete controls across add-pages (lab, dispense, rx, inventory, tasks)
Backend:
- DELETE /api/patients/:fileNumber/labs (remove one lab result, lab:write)
- DELETE /api/dispenses/:id (void a ledger entry, inventory:write)

Frontend (all guarded by ConfirmDialog):
- lab "Recent results" rows: delete result
- pharmacy "Recently dispensed" rows: delete record
- prescriptions/tasks detail sheets + inventory detail dialog gain a delete
  action (prescription delete stays off the shared Pharmacy sheet)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 19:23:41 +03:00
Khalid Abdi f0fe501d62 backend: prescription start/end dates + dispense ledger
Add optional startDate/endDate columns to prescriptions (schema,
validation, types, service) — when set, endDate drives expiry.

Add a new append-only `dispenses` resource (schema, types, validation,
service, route at /api/dispenses) recording who received which medication,
gated on the existing inventory RBAC statement. Migration 0020.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 20:44:47 +03:00
Khalid Abdi 15fc7fdf26 feat(messages): attach files and share appointments
- new message_attachments table + POST/GET /api/conversations/attachments
  (base64 upload, capped 10MB; authed clinic-scoped download)
- messages carry an attachments JSON column (file refs or appointment
  snapshots); realtime + REST send accept attachments and allow
  attachment-only messages; migration 0019
- composer "+" menu (Files / Appointments): file picker uploads and
  stages chips; appointment picker searches by patient and attaches a
  snapshot; both render in the thread (download chip / appointment card)
- raise express json limit to 15mb for base64 uploads

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 18:58:11 +03:00
Khalid Abdi addddc8972 feat(ai): clinic-wide AI kill-switch (admin-controlled)
- new org_ai_policy table + GET/PUT /api/ai/policy (read for any member,
  write owner/admin only); migration 0018
- /api/chat hard-blocks (403) when AI is off for the caller
- Settings → AI "Availability" section: enable AI, or disable for
  employees only (owners/admins keep access); read-only for non-admins
- sidebar, command palette and route guard hide/redirect the AI chat
  when it's disabled for the current user

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 18:42:50 +03:00
Khalid Abdi 6fdb7bdb46 feat(tasks): kanban board with To Do / In Progress / Done columns
- add a `status` field to tasks (backend schema/validation/service/types
  + frontend types), kept in sync with the legacy `done` flag
- migration 0017 adds the column and backfills done tasks to "done"
- rewrite the tasks page as a three-column board: per-column add buttons,
  draggable cards, and a status mover in the detail sheet

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 18:35:48 +03:00
Khalid Abdi 6e5598262e feat(chat): file attachments as chips, CoT closed, AI add-to-inventory
Chat fixes (#1/#6/#7):
- chat-input passes raw File[] up instead of inlining file text
- chat-panel sends files as FileUIPart and renders them with the
  ai-elements Attachments component (no more raw text dumps)
- backend extracts text-like file content for the model in routes/chat
- Chain-of-Thought now defaults to collapsed
- file upload works regardless of whether the input has text

AI add-to-inventory (#2):
- new proposeInventory tool (validates items, streams an approval card)
- system prompt distinguishes stocking inventory vs. billing a patient
- ActionPreviewCard commits inventory via POST /api/inventory

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 18:28:05 +03:00
Khalid Abdi 3aa699aefe feat: clinic-wide AI (analytics/earnings/inventory + invoice-from-file),
real Live card, persistent chat history

Analytics & earnings:
- Analytics now carries real money computed from invoices (billed/paid/
  outstanding + by-month); new Earnings section on the Analysis page drawn with
  the project's Bklit chart components (shared EarningsChart).

AI agent reaches the whole clinic:
- new read tools getClinicInfo / getAnalytics / listInventory render clinic,
  analytics (with a Bklit earnings chart) and inventory cards in chat
- proposeInvoice turns an uploaded purchase/medication list into an invoice
  (new "invoice" action-preview kind → createInvoice); invoices/appointments
  auto-create/link a patient (ensurePatient) so they hit the Patients page

Live card:
- plots real data — patients checked in today — via GET /api/analytics/live
  (polled); value pill clamped and margins widened so nothing spills the card

Persistent AI chat history (Claude-style):
- ai_chat_threads + ai_chat_messages (migration 0016); per-user, org-scoped
  thread CRUD under /api/chat/threads
- chat panel owns a thread id, loads /?thread=<id>, and auto-saves after each
  exchange; sidebar lists past chats (open/delete), "New chat" starts fresh

Verified with backend typecheck + frontend tsc + next build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 22:25:15 +03:00
Khalid Abdi b946dc9226 fix: chat input clipping, AI appointments create patients, batched approval
1. Chat input toolbar was clipped in the empty state: the form's overflow-hidden
   made its flex min-height resolve to 0, so the vertically-centered layout
   squeezed it and hid the bottom toolbar (attach/add-patient/model/mic/send).
   Add `shrink-0` to the form; let the empty-state column scroll.
2. AI-imported appointments now create/link a patient: services.ensurePatient
   reuses a same-name patient or creates one (auto file number, source "ai");
   appointments.createAppointment calls it when the booking has no file number,
   so imported people appear on the Patients page.
3. Many proposals collapse into one BatchActionPreviewCard → a review dialog
   with per-row remove and "Add all" (commits sequentially), instead of one
   Add/Discard card per record.

Plus: widen the Live chart right margin so the value pill stays inside the card.
Verified with `next build`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 21:23:25 +03:00
Khalid Abdi 9086867782 frontend: Claude-style AI chat (tool cards, reasoning, queue, suggestions)
Wire the existing ai-elements into the chat:
- collapsible tool-call cards (name, params, result, status) for native tool
  parts on the streamed path
- a reasoning block (shimmer while thinking, "Thought for Ns") when the model
  emits reasoning; backend forwards it via toUIMessageStream({ sendReasoning })
- a message queue: typing + Enter while the assistant is responding queues the
  message (shown above the input, removable) and auto-sends when it goes idle
- starter suggestion chips on the empty state, each tied to a tool

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 20:06:57 +03:00
Khalid Abdi d096c4fe9d feat: invoices — patient billing with installments + PDF export
Backend (new `invoice` RBAC resource, granted to clinicians + reception):
- invoices table (line items + installments as JSONB), types, zod validation,
  service (CRUD + splitIntoInstallments + auto invoice numbers), org-scoped
  REST routes mounted at /api/invoices, activity logging (migration 0015)

Frontend:
- lib/invoices.ts API client + money/date helpers
- /invoices page: list with KPIs and search, create/edit dialog (searchable
  patient combobox, inline line-item editor, live total), detail sheet to split
  a bill into equal monthly installments, delete, and Download PDF
- dependency-free PDF via a print-styled window (browser "Save as PDF")
- sidebar "Invoices" entry under the Patients group; "Added by AI" badge honored

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 19:58:08 +03:00
Khalid Abdi 929bec8f31 feat: AI-added records save with placeholders + "Added by AI" provenance
Stop blocking AI imports/proposals on missing non-critical fields. Records
the chat agent drafts now save with safe placeholders, auto-generated file
numbers, and a source="ai" marker that surfaces an "Added by AI" badge so a
clinician can review/edit them later.

Backend:
- add `source` (manual|ai) column to patients/appointments/prescriptions
  (migration 0014) + canonical types, services, validation schemas
- relax patient/appointment validation: empty file number allowed, demographic
  + type/provider/initials fall back to placeholders (initials derived from name)
- patients.generateFileNumber() auto-assigns an MRN when one is missing
- proposeAppointment accepts a name when no file number resolves; AI commits +
  /api/ai/import stamp source="ai"

Frontend:
- `source` on Appointment/Patient/Prescription types; AI commits send source="ai"
- reusable <AiBadge> shown on the Patients table/detail and prescriptions list

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 19:27:17 +03:00
Khalid Abdi fa2e499440 feat: live AI chat (thinking + inline Veil) and display/add actions
Make the chat feel alive and let the agent act on the clinic — safely.

UX (frontend):
- Stream `data-step` parts from each tool into an inline Chain-of-Thought
  trace, plus a "Thinking…" shimmer while a request is in flight (works even
  on the non-streamed external+Veil path).
- Replace the modal Veil consent Dialog with an inline, once-per-session
  "Veil" confirmation above the input (with a "Use local model" option).
- Render new list + action-preview cards; chat-input now uses COSS tokens.

Agent (backend):
- Add display tools (listAppointments / listTasks / listPrescriptions) and
  propose tools (proposeAppointment / proposeTask / proposePrescription) that
  validate as a dry run and stream an approval card — nothing is written until
  the clinician approves, via the existing RBAC-gated create endpoints.
- previewImport now also covers single-patient add + migration.
- System prompt: display + add only, never edit/delete or alter the schema;
  stronger migration guidance. ToolContext carries the viewer for task scoping.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 23:49:18 +03:00
Khalid Abdi ddf4b49d82 fix: chat works with any configured provider + surfaces errors
The chat defaulted to a Claude model, so a user who saved only a Gemini key got
a silent failure (backend derived Anthropic, found no key, errored — and the UI
showed nothing).

- Backend: resolveModel now falls back to whichever provider actually has a key
  (preferring the configured one), so a Gemini key just works regardless of the
  picked model. Clear 400 if no provider is configured at all.
- Frontend: the chat seeds its model/effort from the saved AI config, and now
  surfaces request failures as both a persistent alert banner and a toast —
  never silent.
- Settings: switching provider auto-selects that provider's default model.
- Refreshed the model catalog to current ids (Gemini 2.5 Pro/Flash + 2.0 Flash;
  dropped the retired gemini-1.5-pro); per-provider default model updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 19:27:37 +03:00
Khalid Abdi a80086ef72 backend: zero-setup Docker — auto-generate & persist secrets
`docker compose up` now works with no .env editing. A new entrypoint
(docker-entrypoint.sh) generates any missing secret (BETTER_AUTH_SECRET,
AI_CREDENTIALS_KEY) on first start and persists it to the temetro_secrets
volume, so values stay stable across restarts (rotating them would log users
out / invalidate stored AI keys). Real values passed via .env/compose still win.

This also fixes the boot failure where the compose backend ran with
NODE_ENV=production but never passed AI_CREDENTIALS_KEY through, tripping the
production guard. The secret is now an optional pass-through and BETTER_AUTH_SECRET
no longer hard-fails when unset.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 19:15:11 +03:00
Khalid Abdi af956a2779 backend: skip native build scripts in Docker build stage
The build stage ran `npm ci`, which tried to compile better-sqlite3 — a
dev-only transitive dependency of @better-auth/cli — and failed in Alpine for
lack of Python/node-gyp. The build step is just `tsc` (no native binaries
needed) and better-sqlite3 is never used at runtime (the prod stage omits dev
deps), so install dev deps with --ignore-scripts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 18:56:14 +03:00
Khalid Abdi 56b09c269b backend: document the AI chat + Veil in CLAUDE.md
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 18:48:56 +03:00
Khalid Abdi 3a7378e00d backend: AI chat agent with Veil PHI safeguard (providers, /chat, import)
Real LLM chat replacing the mock, backend-centric per the plan:

- Multi-provider API-key mode (OpenAI / Anthropic / Gemini via the AI SDK) plus
  local Ollama (OpenAI-compatible endpoint). Provider is derived from the
  picked model id; the matching stored key is used. New user_ai_settings table
  holds per-user config with provider API keys encrypted at rest (AES-256-GCM,
  src/lib/crypto.ts, keyed by AI_CREDENTIALS_KEY).
- POST /api/ai/config (get/put, secrets never returned), POST /api/ai/test
  (Ollama ping / key presence), POST /api/ai/import (approved migration commit,
  re-validated server-side, reuses the audited patient service).
- POST /api/chat: streamText agent with tools (getPatient, getPatientLabs,
  searchPatients, previewImport). Real record data streams to the clinician as
  custom data parts (cards) while the model sees only Veil-redacted results.
- Veil (src/services/ai/veil.ts): de-identifies patient identifiers to tokens
  before external calls, resolves tokens on tool args, and rehydrates the final
  answer. Bypassed for local Ollama. External mode runs non-streamed so the
  rehydrated text is correct. Every call is audited (provider + Veil level).
- Shared role-scoping helpers extracted to src/lib/role-scope.ts (reused by the
  patient routes and chat tools so visibility rules match).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 18:35:16 +03:00
Khalid Abdi 9545fdd360 backend: fix seed-inventory strict-null type error
Guard the per-org count lookup so the build (tsc -p) doesn't fail on the
possibly-undefined first row.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 19:22:07 +03:00
Khalid Abdi 67fdf15de4 backend: org-scoped pharmacy inventory model
Add an `inventory` resource mirroring the prescriptions feature end-to-end:
Drizzle table (org-scoped, indexed), domain type, zod validation, service
(list/get/create/update/delete), and an RBAC-gated CRUD router mounted at
/api/inventory. Grant the new `inventory` statement to roles — pharmacy gets
read/write, full clinicians read/write/delete, reception/lab none — in both the
backend access control and (mirrored) the frontend. Record writes in the
activity log via a new `inventory` entity type. Includes the migration and an
idempotent demo seed script.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 19:16:28 +03:00
Khalid Abdi f550f4cbd7 backend: per-conversation unread counts
Count messages from others newer than the caller's read pointer alongside the
existing last-message lookup, expose it as unreadCount on
ConversationSummary, and derive the unread boolean from it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 21:04:12 +03:00
Khalid Abdi 84c71de2d4 backend: add POST /api/patients/:fileNumber/labs append endpoint
Lab staff hold lab:write but not patient:write, so they can't go through the
wholesale PUT update. The new endpoint appends lab rows (positions continue
after the current max), bumps updatedAt, records activity and notifies the
clinic — without touching the rest of the record.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 21:03:38 +03:00
Khalid Abdi e37d131800 backend: replace viewer role with pharmacy and lab department roles
Drop the unused read-only "viewer" role and remap any members (and pending
invitations) holding it to "member" via a custom migration. Add a "lab"
statement (read/write) granted to all full clinicians, plus two new
provisionable department roles: "pharmacy" (patient/appointment read,
prescription read+write — no delete, which doubles as the full-clinician
marker the frontend probes) and "lab" (patient/appointment read, task queue,
lab results via the new statement). Both join TASK_DEPARTMENTS so tasks can
be assigned to them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 21:03:15 +03:00
Khalid Abdi 50fbb6cd40 backend: per-user settings store (/api/settings) + enable account deletion
- user_settings table (userId PK -> user, jsonb preferences) + migration
- GET/PUT /api/settings (requireAuth, user-scoped; zod-validated flat map)
- Better Auth user.deleteUser enabled so users can delete their own account

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 20:05:34 +03:00
Khalid Abdi 6a0fab97ae backend: per-doctor patient visibility, transfer, provider picker & scoped activity
- Add patients.primary_provider_id (FK to user) + migration; persist it through
  create/update and surface it on the Patient shape.
- Scope patient list/get for the `doctor` role to their own panel (with a
  createdBy fallback for legacy rows); admin/owner/member/reception/viewer keep
  seeing every patient.
- Add POST /api/patients/:fileNumber/transfer to reassign a chart (updates the
  provider link + PCP label, records activity, notifies the clinic).
- Add GET /api/staff/providers (any member) listing clinical-capable members for
  the PCP picker and transfer dialog.
- Scope the activity feed: non-admins see only their own actions; owners/admins
  see the whole clinic.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 19:51:17 +03:00
Khalid Abdi 4f8793c765 fix: clinic creation gating, analysis line charts, username + task assignee, delete confirm
- Hide "Create clinic" (sidebar footer) for non-admins; only owner/admin can
  spin up additional clinics. Onboarding for brand-new users is unaffected.
- Analysis: drop the bar charts; show line charts (Sparkline) inside KPI cards
  that open a detail dialog with the full chart + per-point breakdown.
- Add Team Member: validate the username client-side (no spaces; letters,
  numbers, dots, underscores) with a clear warning + field hint.
- Tasks: New Task now has an Assignee selector (Myself / Other → department).
  Tasks are visible to the department they're assigned to (or the creator), and
  show who created them. Backend adds assignee_role + created_by_name with
  visibility filtering in listTasks; owners/admins see all.
- Care team: removing a member now asks for confirmation first (dialog) and
  surfaces success/failure + refreshes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 19:52:12 +03:00
Khalid Abdi 8ab0552cf8 feat(analysis): add trend charts to the Analysis page
The Analysis page only showed KPI numbers. Add two real time-series and render
them as dependency-free bar charts (matching components/chat/sparkline.tsx):

- backend: GET /api/analytics now returns `trends.patientsByMonth` (new patients
  per month over the last 6 months) and `trends.appointmentsByWeekday`
  (appointments per day for the current week), bucketed in JS from one query each.
- frontend: new components/analysis/bar-chart.tsx and two chart sections on the
  Analysis view (Patient growth, Appointments this week), with i18n keys.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 19:20:15 +03:00
Khalid Abdi 6213da9477 feat: admin-provisioned staff, username login & role-based access
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>
2026-06-08 19:12:07 +03:00
Khalid Abdi ab2f10bffc i18n: convert settings, chat, patient records and notes; update docs
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>
2026-06-08 02:23:20 +03:00
Khalid Abdi 288d14758f feat: real notifications in the sidebar bell
Auto-generate notifications on patient record create/update (fan out to
the other clinic members, pushed live), and wire the sidebar bell to real
data via a useNotifications hook over the shared socket: live unread badge,
real list, mark-all-read on open. Drops the hardcoded sample array and the
dead "View all" link.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 21:29:57 +03:00
Khalid Abdi 730e07fcfc feat: real-time staff messaging over Socket.io
Add conversations/participants/messages tables, a participant-scoped REST
API (/api/conversations) and a Socket.io server (session-authenticated
handshake; per-user + per-conversation rooms) sharing the HTTP port. New
messages broadcast live and create per-recipient notifications. Also lands
the notifications table + service + routes (used by the message flow). The
Messages page is rewritten: live threads, unread state, and a compose
dialog to start a conversation with a clinic member.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 21:26:28 +03:00
Khalid Abdi 48378ebc5e feat: computed analytics endpoint, rework Analysis page
Add GET /api/analytics returning real aggregates over the clinic's
patients/appointments/prescriptions/tasks, and rebuild the Analysis page
to render them. Drops the fabricated revenue/profit cards — temetro has no
billing data source.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 19:49:50 +03:00
Khalid Abdi 75940313a4 feat: activity audit log written from all resource routes
Add the activity_log table, a best-effort recordActivity() service and a
GET /api/activity feed, and write entries on create/update/delete of
patients, notes, appointments, prescriptions and tasks. The Activity page
now shows the real audit trail (actor, action, patient context, time);
the fabricated signing hashes / approval badges are gone — that vision
stays deferred.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 19:46:22 +03:00
Khalid Abdi 25254dd4c1 feat: org-scoped tasks backend, wire tasks page
Add the tasks table, validation, service and routes (/api/tasks with a
PATCH for partial updates / the done toggle, RBAC-gated) and the frontend
data module. The tasks board now loads, creates and toggles real data
(optimistic toggle with rollback).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 19:41:10 +03:00
Khalid Abdi 7ef9da59bd feat: org-scoped prescriptions backend, wire prescriptions page
Add the prescriptions table, validation, service and CRUD routes
(/api/prescriptions, RBAC-gated; prescriber defaults to the signed-in
clinician, prescribedAt to today) and the frontend data module. The page
now loads/persists real data and computes its status KPIs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 19:37:46 +03:00
Khalid Abdi dec04ec506 feat: org-scoped appointments backend, wire appointments page
Add the appointments table, validation, service and CRUD routes
(/api/appointments, RBAC-gated) and the matching frontend data module.
The appointments page now loads and persists real data; KPIs are computed
from it and the schedule/calendar anchor to the real current date.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 19:33:37 +03:00