- Record history: replace fragile COSS Timeline separator math with a
continuous-rail list so every audited change renders in full (RTL-safe).
- Prescriptions: new reusable COSS DatePicker (Popover + Calendar) replaces
raw <input type="date"> for Start/End date.
- Update banner: rebuilt with COSS Alert variant="warning"; pinned physical
bottom-right so it stays bottom-right under Arabic RTL.
- AI setup notice: thin attached warning banner with clearer copy; now shown
above the input in active chats too, not just the empty state.
- Settings profile: wire the previously dead Specialty (Select) and
Professional links (editable rows) into the persisted preferences.
- Patients: add a status filter and broaden search to conditions/allergies.
- Sidebar user menu: quick language switch submenu (RTL already wired).
- ai-elements: fix a subset of Base UI type drift (22 -> 12; remainder is
cmdk->Autocomplete migration drift kept behind ignoreBuildErrors).
- i18n: new keys added across all five locales (en/de/fr/ar/so).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bump root/backend/frontend to 0.12.1 and move the CHANGELOG notes under a dated
0.12.1 heading (centered wallet-sync stepper, timeline record history, and the
landing-page globe deferral). Adds the radix-ui dependency pulled in by the
Origin UI stepper/timeline primitives.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the bespoke left-aligned DialogStepper with the Origin UI Stepper
primitive (components/ui/stepper.tsx) so the numbered indicators sit centered
inline with their labels. Rebuild the patient sheet's Record History section as
a vertical Timeline (components/ui/timeline.tsx) showing the actor name, an
entity-type icon, the action, and the date, replacing the flat avatar list.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bump to 0.12.0 and record the changelog: in-dialog send-to-wallet stepper,
past-date-blocking appointment/invoice pickers, wallet-number-only portal link,
and removal of the stale Settings Features section.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When the selected patient has a linked wallet, create/edit dialogs now show
a two-step stepper: after saving, step 2 offers to push the change to the
patient's wallet (reusing pushWalletUpdate + approval polling). Added a shared
useWalletSync hook and DialogStepper/WalletSyncStep components, wired into the
appointment, invoice, prescription, patient-edit, and scribe dialogs. Falls
back to the old close-on-save when the patient has no wallet. Added walletSync.*
keys to all locales.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Appointment date picker disables days before today.
- Invoice issue-date picker disables past days by default, with an opt-in
"Back-date" checkbox for recording older invoices (edit mode keeps past
dates). Added invoices.dialog.backdate to all locales.
- Removed the inert, outdated "Features" section (patient-owned storage /
require signed toggles) from Settings and its now-unused i18n keys.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- SidebarMenuAction (the expand chevron on Patients/Pharmacy/Messages) used a
physical right-1, so in Arabic it sat on the right over the icons. Use the
logical end-1 so it's on the right in English and the left in Arabic, clear of
the icons.
- Revert the earlier header change: the notification bell and collapse toggle go
back to their original placement (no RTL column stacking, no mirrored glyph,
notifications popover back to side="right").
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bump root/backend/frontend to 0.11.0 and record the 0.11.0 changelog:
relay-routed Patient Portal + wallet linking, phone-ready portal QR,
appointments/invoices synced to the wallet, clinic-location reverse geocoding,
and the Arabic RTL sidebar fix.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- lib/portal.ts: getPortalLink + portalPairingUri build a temetro-portal: URI
(relay URL + clinic signing key) instead of a localhost API URL.
- Signing settings QR now encodes that pairing URI, so a real phone can reach
the clinic over the Temetro Network relay (fixes "server cannot be accessed").
- Portal kiosk gains a third "Link my wallet" option that shows the same QR.
- New portal.choose.wallet* / portal.wallet.* keys in all 5 locales.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Arabic RTL: stack the collapse arrow/bell above the nav icons instead of
pinning them to the opposite edge; mirror the panel-toggle glyph; flip the
notifications popover to open toward the content side.
- "Use my current location" now reverse-geocodes (OpenStreetMap Nominatim) to
fill address/city/country, not just latitude/longitude, with a graceful
coordinates-only fallback. New settings.location.geoPartial key in all 5 locales.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
backend: public GET /api/portal/:clinic/doctors and /availability, and
thread a chosen provider into portal bookings (conflict check unchanged).
frontend: fix site-wide Arabic RTL — anchor the sidebar right for RTL and
mirror the Switch thumb. Add a Patient Portal section (open/copy/QR) to
Settings → Signing and a "Use my current location" GPS button to the clinic
location editor. New i18n keys across all five locales.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Patient record:
- Add `bloodType` and `phone` to the patient model (schema, canonical types on
both backend + frontend, zod validation). `phone` is a demographic field
(reception can read/write); `bloodType` is clinical PHI, redacted for the
reception role. Surface both in the record sheet, chat summary card, and the
add/edit patient form. Migration 0033.
Clinic location:
- New org-scoped `clinic_settings` table (address/city/country + optional
lat/long), service, and routes: GET /api/clinic/settings (any clinician) and
PUT /api/clinic/location (owner/admin). Edited in Settings → Signing → Clinic
location. Consumed later by the wallet app. Migration 0034.
i18n:
- Translate all new keys into every shipped locale (en/de/fr/ar/so) and document
the "translate into every locale" rule in frontend/CLAUDE.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The default http://localhost:8080 silently failed for clinics that joined
the network without setting RELAY_URL — inside Docker localhost is the
container itself, so the hub connection never reached the relay (endless
"relay unreachable" retries) and pairing QRs encoded an unreachable
localhost. Default to https://network.temetro.com so "Join Temetro Network"
works out of the box; self-hosters running their own relay still override
it. Also (re)ensure the hub is connected before pre-registering a pairing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
QR "scan to connect" pairing has no wallet number, so nothing registered
the request with the relay and the scanning device's response was rejected
("unknown or expired request"). The backend now pre-registers the pairing
request over the relay's new hub:expect event on POST /pair, and
re-registers still-pending requests on hub (re)connect so routing survives
a relay restart. /pair now requires the clinic to have joined the network
(clear 409, surfaced in the import dialog, localized in all five langs).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The relay is now multi-clinic. Each clinic authenticates to the /hub
namespace by signing a challenge with its own Ed25519 clinic signing key
(a per-clinic identity, not a shared RELAY_TOKEN), 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.
Backend:
- clinic_signing_keys.network_enabled + GET/PUT /api/signing/network
(owner/admin) to join/leave the network.
- relay-client keeps one authenticated hub connection per network-enabled
org (connectOrg/disconnectOrg, hubs map keyed by orgId); emitToWallet/
sendToWallet take orgId; offline flush is org-scoped.
- Wallet import/push return 409 until a clinic joins.
- RELAY_TOKEN is now optional/legacy (open relay needs no shared secret).
Frontend:
- "Join Temetro Network" toggle in Settings → Signing, localized in all
five languages (en, fr, de, so, ar).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Devices no longer connect to the backend directly. The /wallet Socket.io
namespace is removed from realtime.ts; a new services/relay-client.ts connects
to the standalone Temetro Network relay's /hub namespace (RELAY_TOKEN-auth),
emitToWallet delegates to its sendToWallet, and device responses + wallet:online
replay are handled there via the same wallet-share/wallet-updates services.
- Add RELAY_URL + RELAY_TOKEN env (env.ts, .env.example, docker-compose.yml);
the wallet-import QR (resolveRelayUrl) now points at RELAY_URL.
- Add socket.io-client dependency.
- Document the Temetro Network folder/service in root + backend CLAUDE.md.
- Bump root/backend/frontend to 0.7.0; CHANGELOG entry.
The relay service itself lives in github.com/temetro/temetro-network.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Expose temetro's own records as a read-only FHIR R4 server at /fhir,
authenticated with per-clinic API keys (tmf_… bearer tokens, SHA-256
hashed, shown once). Serves Patient, Observation (labs + vitals),
AllergyIntolerance, Condition, MedicationRequest, Encounter and
Appointment as text-only CodeableConcepts (temetro stores free-text
clinical values); CapabilityStatement at /fhir/metadata (unauth).
Searchset Bundles with _count/_offset pagination and self/next/prev
links; every request is org-scoped and written to the activity log.
Keys are created/revoked under Settings → Integrations (owner/admin).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A clinician can push an updated record to a wallet-linked patient (permanent
share). The snapshot is signed with the clinic 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,
delivered over the /wallet relay live and on the wallet's next authenticated
connect (offline catch-up). The patient approves/denies in-app; the wallet signs
its decision, the backend verifies it, and the record is replaced only on
approval. Wallet pins the clinic key (TOFU) and warns on change.
Backend: walletRecordUpdates table + service, ed25519PubToX25519Hex helper,
POST /api/patients/wallet/push, GET .../link/:fileNumber|updates|updates/:id,
wallet:update-request / wallet:update-response relay events.
Frontend: "Push to wallet" dialog with live status, wallet-link gating on the
patient sheet, "Sent updates" list under Settings → Signing, walletPush /
walletUpdatesList locale namespaces across all five languages. Bumps to v0.5.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Record a clinician↔patient visit (or paste a transcript) on the patient
sheet; the backend transcribes it (OpenAI Whisper / Gemini), de-identifies
the transcript + context through Veil, and drafts a structured SOAP note
the clinician reviews and edits before saving — the same write-approval
gate as the chat agent.
Backend: POST /api/scribe/{transcribe,draft,save} (routes/scribe.ts,
services/ai/transcribe.ts), veil.redactText() free-text redactor,
appendEncounter service, audio MIME types on attachments. Gated by
patient:write + the clinic AI policy (reception/disabled-AI excluded).
Frontend: ScribeDialog + lib/scribe.ts, "Record visit" on the patient
detail, gated by clinical access + AI availability. New `scribe` locale
namespace across all five languages. Bumps to v0.4.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add three UI locales (so/ar/de) with full ~1,660-key translations alongside
en/fr, selectable in Settings → Profile. Arabic gets full right-to-left support:
- config.ts registers the locales and exports a `dirFor` helper; an inline
<head> script in layout.tsx sets <html dir/lang> before first paint (no RTL
flash), and i18n-provider keeps them in sync on language change.
- ~160 physical direction utilities converted to logical (ms/me/ps/pe/
start/end/text-start/text-end); directional chevrons/arrows get rtl:rotate-180;
chat-bubble align variants fixed to logical.
- IBM Plex Sans Arabic appended to the sans/heading font stacks for
per-character Arabic fallback.
- Language persists to the backend user_settings and re-applies on sign-in so it
roams across devices (localStorage stays the offline source of truth).
- New scripts/check-locales.mjs (npm run check-locales) enforces key/placeholder
parity and Arabic CLDR plural completeness.
Bump to 0.3.0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- release.yml: add QEMU + platforms: linux/amd64,linux/arm64 to both
build-push steps so the published images work on Apple Silicon (fixes
'no matching manifest for linux/arm64/v8' on docker compose pull).
- Settings -> Profile language picker is now a select that opens a
confirmation dialog before applying, instead of switching instantly.
- CLAUDE.md: require a dated docs changelog entry for every release.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GET /api/version now reads the latest version from Docker Hub image tags (the
actual update channel clinics pull), falling back to the GitHub release if
Docker Hub is unreachable, with a shorter 1h cache and a `?refresh=1` bypass.
Settings → About & updates gains a "Check for updates" button that forces a
fresh, cache-bypassing lookup.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract the Patients pagination into a reusable ListPagination component
(components/ui/list-pagination.tsx, carrying the pageWindow helper) and use it
on the Activity feed and the Invoices list (10/page, search resets to page 1).
Patients is refactored onto the same component, removing the duplicated block.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A Language section in the Profile panel offers English / Français, wired to
i18n.changeLanguage (persisted to localStorage via the detector cache).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Register `fr` in the i18n config (resources + supportedLngs) and add a full
French translation (locales/fr/translation.json) with exact key parity to
English (1661 keys; placeholders and plural suffixes preserved). Also adds the
shared `common.pagination.*` keys, `settings.version.checkNow`, and
`settings.profile.language.*`, and drops the now-unused `patients.pagination.*`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
- Patients pagination: render the prev/next + page-number controls as COSS
Buttons inside the Pagination structure. PaginationLink drops its
buttonVariants styling when given a `render` prop, so the controls were
unstyled and wrapping; using Button restores proper pill/number buttons.
- Patient detail sheet: move the action buttons (Download summary / Transfer /
Edit / Delete) to their own wrapping row beneath the identity block so the
patient name is no longer truncated on the narrow sheet.
- Messages thread: add sender/recipient avatars (MessageAvatar) at the bottom
of each message group, with a spacer to keep stacked bubbles aligned.
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>
Show a single, dismissible heads-up on a fresh chat when no AI provider (an
API key or a local Ollama endpoint) is configured, linking to Settings → AI.
It only renders on the empty state, so it clears itself once the first message
is sent.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Compose the conversation thread from the shadcn Message, Bubble, and
Attachment components (Base UI under the hood), keeping COSS semantic colour
tokens — outgoing bubbles use the primary variant, incoming use muted, and
shared files/appointments/password-resets render as Attachment cards. Day
separators, sender grouping, and timestamps are preserved.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add GET /api/activity/patient/:fileNumber (every audited change on one chart,
newest first, readable by any clinic member) and surface it as a Record
history timeline in the patient detail sheet. Add a Download summary action
that builds a clean, printable one-page clinical summary in the browser
(Save as PDF) — no PDF dependency, nothing leaves the server.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the COSS Pagination primitive and page the patients list at 10 rows/page
(search resets to page 1; the page is clamped at render so a shrinking list
never strands you past the last page). Replace the flat "secondary" status
badge with semantic colours: active → success, inpatient → info, discharged →
outline. Includes the i18n keys for pagination, the AI setup notice, and the
patient record-history / PDF export features.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
/api/network reported the container's bridge IP (172.x) when running in
Docker, which surfaced as a broken/"Error" value in Settings → About &
updates. Skip container-internal interfaces (detected via /.dockerenv) so the
endpoint returns no address inside a container, and guard the panel against an
unexpected response shape — both paths fall back to the helpful
"open via the server's IP" hint.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reference prebuilt temetro/temetro-{backend,frontend} images in docker-compose
(with a build fallback) and stop baking the frontend API URL. Add a
tag-triggered GitHub Actions release workflow that pushes both images and cuts
a GitHub Release, plus RELEASING.md, CHANGELOG.md, and a root version. Overhaul
the root and frontend READMEs (screenshot, features, prebuilt-image quickstart,
LAN access, updating).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Resolve the backend URL from the current host at runtime (lib/backend-url.ts)
so one build works on localhost and any clinic LAN IP without a rebuild; used
by the API client, auth client, and socket. Wire the AI-chat mic to the Web
Speech API with graceful fallback. Add a Settings "About & updates" panel
(current/latest version, update command, shareable network address) and an
optional dismissible update banner.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The "Import from a patient app → QR" code baked the web app's own
API_BASE_URL (typically http://localhost:4000) into the pairing URI, so a
real phone could never reach the relay and "Scan to connect" silently
stalled. Use the backend's server-resolved, device-reachable relay URL
instead (PUBLIC_RELAY_URL, else the request host), surface it on the
WalletPairing type, and document PUBLIC_RELAY_URL in .env.example.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Backend: nullable wallet_number on wallet_share_requests (migration 0029);
createPairingRequest + POST /api/patients/wallet/pair; applyShareResponse
binds the authenticated wallet on QR (pairing) requests
- Frontend: Import dialog gains a "Show QR" mode rendering a temetro-pair QR
(react-qr-code) the patient scans; requestWalletPairing helper; i18n keys
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- auth: forgot-password now has Email/Username tabs; new public
/api/auth-helpers/reset-by-username resolves a username and hands off to the
existing reset flow (real email or admin-notify fallback)
- messages: conversations expose isSystem; System notices are read-only (no
composer, no call button) and styled distinctly (shield icon + badge)
- meetings: compact, larger control bar; self tile shows real initials and an
avatar (not black) when the camera is off; delete-room UI with confirm
- sidebar: username-only accounts show @username instead of a synthetic email
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Chat: history pill now shows a History icon + a Start-new-chat (SquarePen)
button; removed the duplicate chat-history list from the sidebar.
Email: deployment-wide email provider config (Resend/Postmark/SendGrid/SMTP) in
Settings → Developers, with encrypted API key and a Send-test action. sendEmail
dispatches via the chosen provider (REST via fetch; SMTP via nodemailer).
Forgot password with no provider: alert the clinic admin(s) via a "System"
message card in Messages + a bell notification (seeded system user + per-clinic
System conversation); clicking deep-links to /settings?tab=careTeam&member=<id>.
Admins can set a member's password directly from the employee dialog
(PATCH /api/staff/:id/password via Better Auth's internal context — no admin
plugin needed).
Patient Portal: "New patient" booking path registers a demographics-only patient
then books; bookings reject double-booked slots (409).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New chrome-less (portal) route group with /portal/[clinic], a card-style choice
of "Book an appointment" vs "View my results", and step flows wired to a new
public backend router (src/routes/portal.ts):
- GET /api/portal/:clinic -> clinic name
- POST /api/portal/:clinic/appointments (verifies name+file#, books a confirmed
appointment that appears on the doctor's Appointments page)
- GET /api/portal/:clinic/results (minimal, safe status — no clinical values)
Excluded /portal from the auth proxy redirect so the kiosk loads without a
session. PHI exposure is intentionally minimal (see docs).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
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>
- settings: keep tab nav on its own row so "Developers" no longer wraps
- chat: show the "Veil active" chip once per conversation, not every turn
- chat: record cards only offer "Click for more" when they have detail
- chat: chat-history panel (pill + sheet) top-left of the AI chat with
"Start new chat"; rename sidebar "New chat" -> "Ask temetro" (Sparkles)
- meetings: disable past dates, add an Upcoming Meetings list, and use the
Empty component for empty days; scheduler can be pre-targeted via ?with
- messages: add a call button left of each inbox row -> Meetings (?with)
- toast: add a dismiss (x) button; call invites now ring 30s with "Accept"
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- 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>