# Changelog All notable changes to temetro are recorded here. The format is loosely based on [Keep a Changelog](https://keepachangelog.com/), and the project follows [Semantic Versioning](https://semver.org/). See [RELEASING.md](./RELEASING.md) for how releases are cut and published. ## [Unreleased] ## [0.10.0] — 2026-07-07 ### Added - **Patient Portal doctor picker & availability.** The portal now lists the clinic's doctors and books against a chosen provider, showing only free slots. New public endpoints `GET /api/portal/:clinic/doctors` and `GET /api/portal/:clinic/availability?provider=&date=` (display-safe fields only), and `POST /api/portal/:clinic/appointments` accepts an optional `provider` (`backend/src/routes/portal.ts`). The existing 409 conflict check stays authoritative. - **Patient Portal links in Settings.** Settings → Signing → Patient Portal adds **open**, **copy link**, and **QR code** actions (`components/settings/settings-portal.tsx`); the QR carries the backend base (`?api=`) so the patient wallet app can book natively when it scans it. - **Clinic location "Use my current location".** The location editor fills map coordinates from the browser's geolocation (`components/settings/settings-location.tsx`). - **Wallet app native Patient Portal.** Scanning a clinic's portal QR opens a native booking screen (doctor list → free-slot picker → confirm) in the patient wallet app. ### Fixed - **Arabic (RTL) layout.** The sidebar now anchors to the **right** for RTL locales and the toggle switch mirrors correctly, instead of leaving the shell misaligned (`components/sidebar-02/app-sidebar.tsx`, `components/ui/switch.tsx`). - **Wallet app:** record-card **bottom sheet no longer freezes** the app (dropped the per-frame animated blur overlay for HeroUI's built-in overlay); **Reset wallet** now confirms in a native HeroUI dialog with Liquid Glass actions; fixed the **white edge flash** on screen transitions in dark mode; the home/onboarding/register **logo** is now the Temetro mark. ### Changed - New i18n keys (`settings.portal.*`, geolocation strings) are translated into **all** shipped locales (en, de, fr, ar, so). ## [0.9.0] — 2026-07-06 ### Added - **Patient blood type & phone number.** The patient record now carries a `bloodType` (e.g. `O+`) and a `phone` number. Both are shown in the record sheet and chat summary card and are editable in the add/edit patient form. `phone` is a demographic/contact field (visible to and editable by the **reception** role); `bloodType` is treated as clinical PHI and is **redacted for reception** (like allergies/vitals). New columns `patients.phone` / `patients.blood_type` (migration `0033`). - **Clinic location setting.** A new org-scoped `clinic_settings` table (migration `0034`) stores the clinic's address (address / city / country) plus optional map coordinates (latitude / longitude), set in **Settings → Signing → Clinic location** (owner/admin only). New endpoints `GET /api/clinic/settings` (any clinician) and `PUT /api/clinic/location` (owner/admin). This will be surfaced in the patient wallet app to show a clinic's location. ### Changed - New i18n keys for the above are translated into **all** shipped locales (en, de, fr, ar, so), per the coverage rule now documented in `frontend/CLAUDE.md`. ## [0.8.2] — 2026-07-05 ### Fixed - **`RELAY_URL` now defaults to the hosted relay** (`https://network.temetro.com`) instead of `http://localhost:8080`. The old default silently failed for anyone who joined the network without explicitly setting `RELAY_URL` — the backend's hub connection could never reach the relay (inside Docker `localhost` is the container itself), so it never authenticated and QR pairing generated a QR pointing at an unreachable `localhost`. Self-hosters running their own relay still override `RELAY_URL`. Updated `.env.example` accordingly. ### Changed - Generating a pairing QR (`POST /api/patients/wallet/pair`) now ensures the clinic's relay hub is connected before pre-registering the request, so the routing is set up even if the connection was opened lazily. ### Fixed - **QR "scan to connect" pairing** was broken by the multi-clinic relay routing (v0.8.0): pairing has no wallet number, so the clinic never sent a `wallet:send` to register the request, and the relay rejected the scanning device's response as "unknown or expired". The clinic now **pre-registers** the pairing request with the relay (a new `hub:expect { requestId }` event on `POST /api/patients/wallet/pair`), so the device's response routes back correctly. On hub (re)connect the backend re-registers its still-pending requests, so routing also survives a relay restart. `POST /pair` now also requires the clinic to have joined the network (clear 409 instead of a dead QR), surfaced in the import dialog. ### Added - **Multi-clinic Temetro Network.** The relay now serves many self-hosted clinics at once. Each clinic authenticates to the `/hub` namespace by **signing a challenge with its own Ed25519 clinic signing key** (`services/signing.ts`) — a per-clinic identity, not a shared password — and the relay routes every device response back to only the clinic that originated the request (keyed by `requestId`), so clinics never see each other's traffic. `wallet:online` is fanned out only to clinics with pending work for that wallet. - **"Join Temetro Network" opt-in.** A per-clinic toggle in **Settings → Signing** (backed by `clinic_signing_keys.network_enabled`, `GET`/`PUT /api/signing/network`, owner/admin only). Off by default; enabling opens the clinic's relay connection, disabling tears it down. Wallet import/push endpoints return **409** while a clinic hasn't joined. Localised in all five languages. ### Changed - **The backend keeps one authenticated relay connection per network-enabled org** (`services/relay-client.ts` — `connectOrg`/`disconnectOrg`, a `hubs` map keyed by `orgId`), instead of a single shared-token connection. `emitToWallet`/`sendToWallet` now take an `orgId`, and the offline-flush (`pendingUpdatesForWallet`) is org-scoped. - **`RELAY_TOKEN` is now optional/legacy.** Clinics authenticate with their signing key, so an open relay needs no shared secret; `RELAY_TOKEN` only gates an optional *private* relay. ## [0.7.0] — 2026-07-05 ### Added - **Temetro Network** — a standalone, high-performance **relay** (Rust + Axum + socketioxide) that connects the backend to patient wallet apps, in its own repo ([github.com/temetro/temetro-network](https://github.com/temetro/temetro-network)) and deployable on Railway. It replaces the flaky Cloudflare quick-tunnel that used to expose the backend's embedded `/wallet` Socket.io namespace to phones. The relay is a **dumb, stateless pipe**: a `/wallet` namespace for devices (challenge/Ed25519-signature auth, room keyed by wallet number) and a `RELAY_TOKEN`-authenticated `/hub` namespace for the backend. It forwards sealed ciphertext verbatim, keeps no database, and its only crypto is verifying a device's auth signature (proven byte-for-byte compatible with `wallet-crypto.ts`). ### Changed - **The backend is now a client of the relay, not the wallet server.** The `/wallet` Socket.io namespace was removed from `src/realtime.ts`; a new `src/services/relay-client.ts` connects to the relay's `/hub` (`emitToWallet` delegates to its `sendToWallet`), handles device responses (`wallet:share-response` / `wallet:update-response` / `wallet:revoke`) and flushes missed updates on `wallet:online` — calling the same `wallet-share` / `wallet-updates` services as before. New `RELAY_URL` + `RELAY_TOKEN` env vars; the wallet-import QR now points at `RELAY_URL`. ## [0.6.0] — 2026-07-04 ### Added - **Read-only FHIR R4 server** — temetro can now be a FHIR **server**, not just a client. A new endpoint tree at **`/fhir`** (mounted outside `/api`, bearer-only) exposes each clinic's records as FHIR R4: **Patient**, **Observation** (labs + synthesized vital signs), **AllergyIntolerance**, **Condition**, **MedicationRequest**, **Encounter** and **Appointment**, plus an unauthenticated **`GET /fhir/metadata`** CapabilityStatement. Searches return searchset `Bundle`s with `_count`/`_offset` pagination and self/next/prev links. Because temetro stores free-text clinical values, every `CodeableConcept` is **text-only** (no SNOMED/LOINC) and patients carry an **age** extension rather than a `birthDate` — documented in the CapabilityStatement and API docs. - **Per-clinic FHIR API keys** — machine-to-machine auth via `Authorization: Bearer tmf_…`. Keys are created/revoked under **Settings → Integrations → FHIR server** (owner/admin), **SHA-256-hashed** at rest, and shown **once** at creation. Every FHIR request is org-scoped (no cross-clinic reads) and written to the activity log with the key name and result count. New `fhir_api_keys` table, `middleware/fhir-auth.ts`, the `services/fhir-server/` mapping module (queries, resources, bundle, capability, keys), the `/fhir` router, and `GET/POST/DELETE /api/integrations/fhir-server/keys`. New `fhirServer` locale namespace across all five languages. ## [0.5.0] — 2026-07-03 ### Added - **Clinic → wallet record-update push** — a clinician can push an updated record to a **wallet-linked** patient (a permanent, approved share). The record snapshot is **signed** with the clinic's Ed25519 key and **sealed** to the wallet's X25519 key (derived from its Ed25519 wallet number via the birational map — verified byte-for-byte against the wallet's own derivation), stored `pending`, and delivered over the `/wallet` relay live **and** on the wallet's next authenticated connect (so an offline phone catches up). The patient reviews it in a **pending-updates inbox** and approves/denies; the wallet signs its decision, the backend verifies it, and the on-device record is replaced only on approval. The wallet **pins** the clinic key (TOFU) and warns on a key change. New `POST /api/patients/wallet/push`, `GET /api/patients/wallet/{link/:fileNumber,updates,updates/:id}`, `walletRecordUpdates` table + service, `wallet:update-request` / `wallet:update-response` relay events, a "Push to wallet" dialog with live status, and a "Sent updates" list under Settings → Signing. New `walletPush` / `walletUpdatesList` locale namespaces across all five languages. (The wallet app half ships in the sibling `temetro-app` repo.) ## [0.4.0] — 2026-07-03 ### Added - **Ambient AI visit scribe** — a **Record visit** action on the patient sheet turns a clinician↔patient conversation into a draft **SOAP** encounter note. Record with the microphone (`MediaRecorder`, stored as an auditable patient attachment) or paste a transcript; the backend transcribes via the user's **OpenAI (Whisper)** or **Gemini** key, de-identifies the transcript + patient context through **Veil**, and the model drafts a structured note that the clinician **reviews and edits before saving** — the same write-approval gate as the chat agent. New `POST /api/scribe/{transcribe,draft,save}` (`backend/src/routes/scribe.ts`, `services/ai/transcribe.ts`), a `veil.redactText()` free-text redactor, and an `appendEncounter` service that adds one note without touching the rest of the record. Gated by `patient:write` + the clinic AI policy (reception and disabled-AI accounts don't see it). New `scribe` locale namespace across all five languages. Drafting also works with local Ollama from a pasted transcript. ## [0.3.0] — 2026-07-02 ### Added - **Three new interface languages** — Somali (`so`), Arabic (`ar`) and German (`de`) join English and French, selectable in Settings → Profile → Language. Each locale carries a full translation of the ~1,660 UI strings, with native names shown in the selector (Soomaali, العربية, Deutsch). - **Right-to-left (RTL) support** — selecting Arabic sets `dir="rtl"` on the document (applied before first paint via an inline script, so no flash), flips physical spacing/alignment to logical CSS utilities, mirrors directional icons, and loads an Arabic-capable typeface (IBM Plex Sans Arabic) appended to the font stack for per-character fallback. - **Language roams across devices** — the chosen language is persisted to the per-user `user_settings` preferences and re-applied on sign-in, with localStorage remaining the offline source of truth. - **`frontend/scripts/check-locales.mjs`** (+ `npm run check-locales`) — a parity check that fails on missing/extra keys or `{{placeholder}}` mismatches across locales and warns when Arabic count-keys lack the full CLDR plural forms. ## [0.2.5] — 2026-07-01 ### Fixed - **Multi-arch Docker images** — the `release` workflow now builds and publishes `khalidxv/temetro-backend` and `khalidxv/temetro-frontend` for both `linux/amd64` and `linux/arm64`. Previously the images were amd64-only, so `docker compose pull` on Apple Silicon failed with *no matching manifest for linux/arm64/v8* and fell back to building from source. ### Changed - **Language switcher** in Settings → Profile is now a **select** that asks for confirmation before switching the interface language, instead of applying the change instantly on a button tap. ## [0.2.4] — 2026-06-29 ### Added - **Pagination** on the Activity and Invoices pages (10 per page), matching the Patients page, via a shared `ListPagination` component. - **French (Français)** interface language, with a language switcher in Settings → Profile. The choice persists on the device. - **"Check for updates"** button in Settings → About & updates that forces a fresh check. ### Changed - **Update detection** now reads the latest version from **Docker Hub** image tags (the channel clinics actually pull), falling back to the GitHub release if Docker Hub is unreachable. This fixes "About & updates" showing *Up to date* when a newer image was already published. - **docker compose** host ports are now configurable (`BACKEND_PORT`, `FRONTEND_PORT`, `ADMINER_PORT`, alongside `POSTGRES_PORT`) so a port clash on `docker compose up -d` can be resolved from `.env` without editing the file. ## [0.2.3] — 2026-06-29 ### Fixed - **AI chat patient record cards** now render on Google Gemini for name lookups. "Show me 's medical record" relied on the model chaining `searchPatients` → `getPatient`, but Gemini often called `searchPatients` and then emitted only a canned closing line ("Here's the record.") without the second tool call — so no card was ever drawn. `searchPatients` now displays the record card directly when exactly one patient matches, so the flow no longer depends on a follow-up tool call. (The previous Gemini fix in 0.2.2 only covered the empty-schema list tools.) ## [0.2.2] — 2026-06-28 ### Fixed - **AI chat record cards** now render on Google Gemini. The card-emitting chat tools (`listAppointments`, `listTasks`, `listPrescriptions`, `getClinicInfo`, `getAnalytics`, `listInventory`) used an empty parameter schema; Gemini can't emit a function call for a schema with no properties, so it printed the call as `tool_code` text instead of invoking the tool — leaving replies as plain text (e.g. "Show today's schedule" leaked a raw `` block) with no cards. The tools now share a non-empty schema so Gemini calls them; other providers are unaffected. ## [0.2.1] — 2026-06-27 ### Fixed - **Patients pagination** controls now render as proper buttons — the prev/next and page-number controls were unstyled and wrapping (the COSS `PaginationLink` drops its button styling when given a `render` prop). - **Patient detail sheet header** reflowed: actions (Download summary / Transfer / Edit / Delete) moved to their own wrapping row so the patient name is no longer truncated. - **Messages thread** now shows **sender and recipient avatars** alongside the chat bubbles. - **Release notes** — the `release` workflow now publishes the matching `CHANGELOG.md` section as the GitHub Release body (instead of only the auto-generated "Full Changelog" link). ## [0.2.0] — 2026-06-27 ### Added - **Patients table pagination.** The Patients list now paginates at 10 rows per page (COSS `Pagination`), so large clinics no longer scroll endlessly. - **Per-patient record history.** The patient detail sheet shows an audit timeline of every add/change on that chart. `GET /api/activity/patient/:fileNumber`. - **Patient summary PDF.** A **Download summary** action on the patient sheet produces a clean, printable one-page clinical summary (browser "Save as PDF"). - **AI setup notice.** A single, dismissible heads-up appears above the chat input on a fresh chat when no AI provider (API key or local Ollama) is configured; it clears itself once you send a message. - **Version & update awareness.** `GET /api/version` reports the running version and checks GitHub Releases for a newer one; Settings → **About & updates** shows the current/latest version, and an optional, dismissible banner appears when an update is available. - **LAN access.** The frontend now resolves the backend URL from the host the browser is using, so other departments can reach temetro at `http://:3000` with no rebuild. A Settings panel surfaces the shareable network address. `GET /api/network` reports detected LAN addresses. - **Prebuilt Docker images** published to Docker Hub (`khalidxv/temetro-backend`, `khalidxv/temetro-frontend`) via a tag-triggered GitHub Actions release workflow. - **Voice dictation** on the AI chat input (Web Speech API), with graceful fallback where the browser doesn't support it. ### Changed - **Messages thread** rebuilt on the shadcn `Message` / `Bubble` / `Attachment` components (COSS colour tokens preserved) for a cleaner conversation surface. - **Patient status badges** now use semantic colours (active → success, inpatient → info) instead of a flat secondary badge. - `docker-compose.yml` references the published images (with a build fallback) and no longer bakes a fixed API URL into the frontend. ### Fixed - **AI import approval card.** `previewImport` now declares a concrete record schema so Google Gemini emits a real tool call (and the approval card renders) instead of dumping a `tool_code`/JSON wall as text. The system prompt also forbids printing tool calls and re-listing fields. - **Settings network address** no longer shows a bogus container IP / "Error" under Docker — the `/api/network` endpoint now skips container-internal interfaces, and the panel falls back to the helpful LAN hint. ## [0.1.0] — 2026-06-26 Initial baseline: clinician AI-chat UI wired to the TypeScript/Express/Postgres API (Better Auth, multi-tenant clinics, org-scoped patient records), the patient wallet encrypted-share flow, and Dockerised local run.