182 Commits

Author SHA1 Message Date
Khalid Abdi c5812511b5 chore: release v0.17.0
Hardware barcode scanner in the pharmacy Add-item dialog, Separated Panels
in Care team settings, and a pointer cursor on Activity rows. Also fixes
the README changelog badge, which was stuck at 0.14.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 20:53:53 +03:00
Khalid Abdi 196a22bffd frontend: pointer cursor on Activity feed rows
Activity rows are native buttons, which default to cursor:default; add
cursor-pointer so hovering an activity shows the pointer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 20:42:00 +03:00
Khalid Abdi 531cbbad7f frontend: Separated Panels in Care team settings
Apply the same COSS Separated Panels treatment used in the other settings
frames (Preferences, AI) to the Care team section: pass separated to its
SettingsSection so the cards render as distinct panels on a muted tray.
The members list keeps p-0/overflow-hidden so its divided rows sit flush
inside their panel.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 20:42:00 +03:00
Khalid Abdi 65975177cf inventory: hardware (keyboard-wedge) barcode scanner in Add-item
Replace the camera scanner in the Add inventory item dialog with support
for a USB barcode scanner attached to the computer. A wedge scanner types
the code as a fast keystroke burst ending in Enter; a global keydown
listener (active while the dialog is open) buffers the burst by timing and
routes it through the existing GS1 parser, so scanning a medication
auto-fills barcode/expiry/lot without focusing any field. The barcode
input also parses on Enter for manual entry. The camera BarcodeScanner
component stays (still used by patient wallet import). i18n: replace the
inventory.dialog.scan label with a scanHint across all five locales.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 20:42:00 +03:00
Khalid Abdi 75502ceb60 chore: release v0.16.0
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 23:59:58 +03:00
Khalid Abdi b285158fec frontend: scan a patient's wallet code in Import from a patient app
Adds a Scan button beside the wallet-number field in the import dialog,
reusing the BarcodeScanner. A scanned QR or 2D barcode from the patient's
wallet app drops the wallet number straight into the field. i18n added to
all locales.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 23:34:56 +03:00
Khalid Abdi 0754ce96a4 inventory: barcode scanner in Add-item dialog
Add a camera barcode/QR scanner (components/scan/barcode-scanner.tsx,
@zxing/browser lazy-loaded) and a Barcode/NDC field to the pharmacy
Add-item dialog. Scanning a GS1 DataMatrix auto-fills expiry (AI 17) and
lot (AI 10 -> notes), with the GTIN (AI 01) as the barcode; plain 1D
barcodes drop straight into the field. Persists a nullable inventory
barcode column (migration 0036) through the type/validation/service, and
surfaces it on the item detail. i18n added to all locales.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 23:32:45 +03:00
Khalid Abdi bac3e7a1d0 frontend: Show/Add tabs in patient edit dialog
Editing a record now defaults to a read-only Show tab (reusing
PatientDetail) with an Add tab for the editable form + Add buttons.
Create and import-review keep the plain form. The existing post-save
wallet-sync step (Next -> send to patient's wallet when linked) is
unchanged. Adds patientForm.tabs.{add,show} + patientForm.close to all
locales.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 23:20:22 +03:00
Khalid Abdi b9d4d4f458 frontend: real COSS Separated Panels in settings frames
Replace the gap-4-on-CardFrame hack with a faithful COSS Frame: a muted
tray (bg-muted/72) holding distinct bordered FramePanels spaced by mt-1,
matching coss.com/ui/docs/components/frame. Adds components/ui/frame.tsx
and switches SettingsFrame/SettingsCard to render Frame/FramePanel when
separated (via context), keeping joined frames on CardFrame.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 23:13:34 +03:00
Khalid Abdi aa535f08c4 chore: release v0.15.1
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 00:05:33 +03:00
Khalid Abdi 000591f87f frontend: space separated panels in settings frames
Add an opt-in `separated` prop to SettingsFrame/SettingsSection that adds the
COSS "Separated Panels" gap-4 between sibling panels (matches CardFrame's
built-in -1rem clip vars so rounded corners clip across the gap). Turn it on
for the multi-toggle frames — AI availability and the two notification
sections — so their panels no longer stack flush. Joined divide-y lists stay
flush.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 23:48:30 +03:00
Khalid Abdi 7312d15d67 chore: release v0.15.0
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 21:12:01 +03:00
Khalid Abdi e8f3ed9ffe frontend: clear the 99 lint errors
`npm run lint` reported 99 errors and 26 warnings across 65 files and
had presumably been failing for a while — next.config.ts sets
eslint.ignoreDuringBuilds, so the build never surfaced it.

68 were in vendored code: components/charts and components/ai-elements,
pulled from upstream registries. Re-linting those reports upstream's
style back at us, and "fixing" them means diverging and eating conflicts
on every update — ai-elements already has exactly this carve-out on the
TypeScript side via ignoreBuildErrors. Ignore both, plus the two registry
files in components/ui (carousel publishes its api from an effect; the
sidebar skeleton picks a random width).

The other 31 were ours, nearly all react-hooks/set-state-in-effect on
the same shape: an effect that re-seeds form state when a dialog opens or
a selection changes. Moved to render-phase adjustment, which is both what
React recommends and a real fix — the effect version paints one frame of
the *previous* record's values before correcting itself. Two carried
sharper bugs: the employee dialog could keep a typed password across a
switch to another member, and use-wallet-sync could carry `linked` over
to a newly-selected patient, briefly offering to push a record to
someone else's wallet.

The rest: useIsMobile and speech-support detection become
useSyncExternalStore (correct on first paint, no mount flash); refs
mirroring state are written in effects rather than during render; the
care-team fetch moves into its effect behind a reload key, dropping an
exhaustive-deps suppression.

Two effects in chat-panel keep the rule disabled with a reason. Both are
what effects are for: draining the queued-message buffer when the
transport goes idle, and resolving ?thread from the URL — the latter
mints an id with nanoid(), so moving it into render would just trade this
error for a purity one.

Also removes a dead /explore-era import and unused directives found on
the way. lint now exits 0, with the ai-elements tsc carve-out unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 20:54:37 +03:00
Khalid Abdi 7c92c03eed docker: stop a Windows checkout from breaking the backend image
The repo had no .gitattributes at all, so nothing pinned line endings —
`git ls-files --eol backend/docker-entrypoint.sh` showed an empty attr/.
Git for Windows installs with core.autocrlf=true, which rewrites the
entrypoint to CRLF on checkout. The image then ships a script whose
shebang is "#!/bin/sh\r" and the container dies at startup with

  exec /usr/local/bin/docker-entrypoint.sh: no such file or directory

naming the file it just copied in, which reads like the COPY failed.

Verified by building an image from a deliberately CRLF-ified copy of the
entrypoint: it reproduces that exact error, and passes with the sed.

Pin *.sh (and the Dockerfiles) to LF, and strip CR in the image before
chmod as well — .gitattributes only helps fresh clones, and a Windows
contributor who cloned before this commit still has CRLF on disk.
Renormalizing changes no tracked file today; this is purely a guard.

The arm64/Apple Silicon failure that prompted this is already fixed by
6127a0a (Turbopack has no musl/arm64 binding; the build uses webpack).
Confirmed by building both images on an arm64 Mac. Release publishes
linux/amd64 + linux/arm64, there are no bind mounts, no platform: keys
and no host.docker.internal, so the remaining cross-platform hazard was
just the line endings.

Also tighten .dockerignore: backend was shipping docker-compose.tunnel
.yml, fly.toml, railway.json, render.yaml, .env.example and
drizzle.config.ts into the build context; frontend was shipping
tsconfig.tsbuildinfo.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 20:27:03 +03:00
Khalid Abdi 5961d969b1 frontend: give settings frames the COSS default padding
The Clinician profile frame inset its content 40px and drew a second
border around it — a box inside a box. Both came from CardFramePanel,
added in 44d653f. It doesn't exist in the COSS registry: upstream
@coss/card ships Card, CardFrame, CardFrameHeader/Title/Description/
Action/Footer and nothing else. There is no body part because the frame
isn't meant to pad its body.

CardFrame styles its cards through *direct-child* selectors — -m-px,
the clip-path, rounded-t/b-xl, shadow-none, before:hidden — pulling each
card out by a pixel so it sits flush inside the frame's own border. The
panel wrapper made every card a grandchild, so none of those selectors
matched: the 20px came from the panel's p-5, the second border came from
the card keeping the treatment the frame was supposed to suppress. The
commit that added it claimed the cards would "pick up the frame's card
treatment"; they never did.

Drop the panel and render children directly inside the frame, which is
the COSS default. Body padding goes on the Card, where COSS puts it, so
content is inset once rather than twice. Card-bearing bodies now get the
frame treatment they were meant to have, and the divide-y lists in
Records, Care team and Signing stack flush, which is what -m-px and
rounded-t-xl were for. The notification ToggleRows lose their space-y
wrapper and read as one list.

Frames holding raw form content (the AI panel, Care team's error and
toolbar) get an explicit padded Card instead of leaning on the frame.

card.tsx is now upstream @coss/card verbatim, minus font-heading on
CardTitle.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 20:23:33 +03:00
Khalid Abdi 62a5f39683 chore: release v0.14.2
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 02:35:22 +03:00
Khalid Abdi 6127a0ac42 frontend: build with webpack so Docker images build on arm64
Next 16's default Turbopack build has no native bindings for linux/arm64 in
the Alpine image, so `next build` failed in Docker ("Turbopack is not
supported on this platform"). Switch the production build to webpack, which
Next recommends for this case and which builds on every arch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 02:35:02 +03:00
Khalid Abdi 352ca65838 chore: release v0.14.1
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 02:31:54 +03:00
Khalid Abdi 44d653ffbd frontend: compose settings frames from CardFrame primitives
SettingsFrame used a hand-rolled <div className="p-5"> body and
SettingsCard was a plain div (no data-slot=card), so the frame's card
styling never applied.

- Add CardFramePanel (data-slot="card-frame-panel") as the padded frame
  body, mirroring the other CardFrame* subcomponents.
- SettingsFrame now composes CardFrameHeader + CardFramePanel (same p-5
  padding as before) instead of a raw div.
- SettingsCard renders a real COSS Card (data-slot=card). Since Card is
  flex flex-col, the horizontal-row call sites (ToggleRow, billing and
  preferences rows) now pass flex-row.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 21:10:19 +03:00
Khalid Abdi 76da310766 frontend: fix wallet push silently skipping in form dialogs
The "send to wallet" step in the appointment/invoice/prescription/
patient-edit/scribe dialogs was gated on sync.linked, which resolves
asynchronously via getWalletLink in an effect. A fast save (or a
transient failure that collapsed it to false) left a wallet-linked
patient looking unlinked, so the dialog took the else branch and pushed
nothing.

- Add ensureLinked() to useWalletSync: awaits getWalletLink at submit and
  returns the resolved status. Each of the 5 dialogs now awaits it before
  deciding whether to show the wallet step.
- Harden push() to drop empty/whitespace changes (the backend 400s on an
  empty change set) and add a translated summaryFallback so the step
  never sends an empty summary.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 21:10:04 +03:00
Khalid Abdi c326e9f794 chore: release v0.14.0
Bump root/backend/frontend to 0.14.0, move Unreleased notes under a dated
0.14.0 heading, and fix the stale README changelog badge (0.2.1 → 0.14.0).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 20:22:48 +03:00
Khalid Abdi b299501ab2 frontend+backend: patients/settings UI, AI Auto/Off modes, wallet doc push
- patients: move status filter to its own "Filter" row above the table
- patient sheet: name + ⋯ menu on one left row; Edit moved into the ⋯ menu
- settings: redesign sections with the COSS CardFrame surface (SettingsFrame)
- AI: add Automatic (auto-pick provider) and Off modes; default to Automatic
  so a fresh install shows the setup banner until a provider is configured
- AI: fix the setup banner never showing (defaulted Ollama URL counted as
  configured); add an "AI off" notice; add a fallback render for unknown chat
  data parts so cards never silently vanish
- backend: include patient attachment metadata in the wallet record-update
  bundle so pushed documents reach the wallet
- i18n: add mode/off/card keys across all five locales

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 20:22:36 +03:00
Khalid Abdi 17209a2cf1 chore: release v0.13.1
Bump version to 0.13.1 across root/backend/frontend and add the 0.13.1
CHANGELOG entry (COSS checkbox/table on the web, wallet-app home sheets).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 02:16:07 +03:00
Khalid Abdi bd8fdfadda frontend: COSS checkbox/table + fewer toolbar/sheet buttons
- Invoice dialog: the Back-date / Due-date toggles now use the COSS Checkbox
  instead of raw, misshapen native checkboxes.
- Patients page: rebuilt the list as a COSS Table in a CardFrame, and moved
  "Import from a patient app" into a ⋯ overflow menu so the toolbar keeps one
  primary "Add patient" CTA.
- Patient detail sheet: collapsed the five header buttons + delete into a
  primary Edit action plus a ⋯ More menu (Download summary, Record visit,
  Transfer, Push to wallet, and a destructive Delete).
- Added the COSS table + checkbox primitives; new i18n keys across all locales.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 02:11:06 +03:00
Khalid Abdi 7846dacd42 chore: release v0.13.0
Bump version to 0.13.0 across root/backend/frontend, add the 0.13.0 CHANGELOG
entry, and refresh stale CLAUDE.md docs (the AI chat is real and @ai-sdk/react
is installed; the signing/approval flow is built).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 21:17:55 +03:00
Khalid Abdi 2c239fbd27 frontend: UI fixes + settings/patients features
- 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>
2026-07-12 21:06:38 +03:00
Khalid Abdi bffed5525d chore: release v0.12.1
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>
2026-07-10 20:51:04 +03:00
Khalid Abdi 591b2f9170 frontend: center wallet-sync stepper and record-history timeline
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>
2026-07-10 20:50:54 +03:00
Khalid Abdi b557139752 chore: release v0.12.0
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>
2026-07-09 21:23:52 +03:00
Khalid Abdi feffce6cbf frontend: in-dialog wallet-sync stepper for record changes
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>
2026-07-09 20:52:36 +03:00
Khalid Abdi 35f07c508d frontend: block past dates in appointment/invoice pickers; drop stale Settings Features
- 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>
2026-07-09 20:41:58 +03:00
Khalid Abdi 143ffc39f1 frontend: fix RTL sidebar group chevron; restore header buttons
- 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>
2026-07-09 01:19:30 +03:00
Khalid Abdi b33561d7e1 chore: release v0.11.0
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>
2026-07-09 00:15:28 +03:00
Khalid Abdi 947623a691 frontend: relay-based Patient Portal QR + kiosk "Link wallet" option
- 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>
2026-07-09 00:06:04 +03:00
Khalid Abdi fb9fa299c9 frontend: RTL sidebar arrow placement + reverse-geocode clinic location
- 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>
2026-07-08 19:32:23 +03:00
Khalid Abdi ab17978b5c feat: portal doctor booking, Arabic RTL fix, wallet portal QR (v0.10.0)
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>
2026-07-07 19:31:20 +03:00
Khalid Abdi 36461a5498 feat: patient blood type & phone + clinic location setting (v0.9.0)
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>
2026-07-06 22:07:27 +03:00
Khalid Abdi 01dbc07e92 fix: default RELAY_URL to the hosted relay (v0.8.2)
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>
2026-07-05 22:50:46 +03:00
Khalid Abdi 233ce9f854 fix: QR wallet pairing broken by multi-clinic routing (v0.8.1)
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>
2026-07-05 19:16:33 +03:00
Khalid Abdi 99aa534e88 feat: multi-clinic Temetro Network with per-clinic identity (v0.8.0)
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>
2026-07-05 18:30:19 +03:00
Khalid Abdi ef76afc3ca feat: route wallet traffic through the Temetro Network relay (v0.7.0)
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>
2026-07-05 03:04:23 +03:00
Khalid Abdi d79f7f7c06 chore(release): v0.6.0
Read-only FHIR R4 server at /fhir with per-clinic API keys.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 01:34:24 +03:00
Khalid Abdi 0d2494d67a feat: read-only FHIR R4 server (share records over /fhir)
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>
2026-07-04 01:27:32 +03:00
Khalid Abdi bb536ba6da feat: clinic→wallet record-update push
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>
2026-07-03 18:57:38 +03:00
Khalid Abdi b29fdff1cb feat: ambient AI visit scribe (record/paste → reviewed SOAP note)
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>
2026-07-03 18:33:30 +03:00
Khalid Abdi d237504af9 frontend: add Somali, Arabic (RTL) & German languages
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>
2026-07-02 23:03:13 +03:00
Khalid Abdi 46c32b432c chore(release): v0.2.5
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 21:54:47 +03:00
Khalid Abdi 5bbfe551fc release workflow: publish multi-arch images; settings: confirm language switch
- 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>
2026-07-01 21:54:47 +03:00
Khalid Abdi 7838dd68a5 chore(release): v0.2.4
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 21:00:52 +03:00
Khalid Abdi 2bb03633ff backend: detect updates from Docker Hub + add a Check for updates button
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>
2026-06-29 21:00:18 +03:00