Bump root, backend, and frontend to 0.2.1 and add the 0.2.1 changelog section.
Patch release: pagination styling, patient-sheet header reflow, message
avatars, and CHANGELOG-based GitHub Release notes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bump root, backend, and frontend to 0.2.0 and move the changelog Unreleased
notes under a dated 0.2.0 heading. Adds Patients pagination, per-patient record
history, patient summary PDF export, the messages bubble/attachment UI, the AI
import approval-card fix, and the Docker network-address fix.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add `npm run docker:tunnel` (docker-compose.tunnel.yml overlay): a
cloudflared sidecar opens a public quick tunnel to the backend, and the
backend auto-discovers its https://…trycloudflare.com URL from cloudflared's
metrics endpoint (services/relay-url.ts) and bakes it into the wallet-import
QR — so a phone on any network can scan, with no install/account/PUBLIC_RELAY_URL.
PUBLIC_RELAY_URL still wins when set. Verified end-to-end: the backend logs
the discovered tunnel URL on startup.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Scan-to-connect only worked on the same Wi-Fi because the QR carried a LAN
relay URL. Give the relay a public address two ways:
- `npm run dev:tunnel` (scripts/dev-tunnel.mjs) opens a cloudflared tunnel
to localhost:4000 and starts the backend with PUBLIC_RELAY_URL set to the
public https URL, so the QR carries it and a phone on any network connects.
- Deploy configs for Fly.io (fly.toml), Render (render.yaml), and Railway
(railway.json), all building the existing Dockerfile.
Also harden resolveRelayUrl to honor x-forwarded-proto so the derived QR URL
is https behind a TLS proxy (iOS ATS requires wss).
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>
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>
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>
Implements the first temetro backend: an Express 5 API on Postgres via
Drizzle ORM, with authentication and multi-tenant clinics powered by
Better Auth.
- Auth: email/password with required email verification, password reset,
rate limiting, CSRF/trusted-origins, secure cookies, session audit hook.
- Organizations (clinics) with RBAC (owner/admin/member/viewer) and an
extended `patient` permission set; member invitations by email.
- Org-scoped patient records mirroring the frontend Patient shape, with
CRUD endpoints gated by permission (read/write/delete).
- Email helper logs links to the console when SMTP is unset (zero-setup
local dev); Dockerfile + docker-compose (db + backend + frontend) with
migrations applied on startup and a configurable Postgres host port.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>