Commit Graph

29 Commits

Author SHA1 Message Date
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 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 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 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 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 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 dc5f55f87d frontend: paginate the Activity and Invoices pages
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>
2026-06-29 21:00:18 +03:00
Khalid Abdi 31adbab87b frontend: fix pagination styling, patient-sheet header, message avatars
- 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>
2026-06-27 22:16:12 +03:00
Khalid Abdi 3ee00fcf06 feat: per-patient record history + summary PDF export
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>
2026-06-27 21:33:31 +03:00
Khalid Abdi 869038477a frontend: paginate the Patients table and colour status badges
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>
2026-06-27 21:33:18 +03:00
Khalid Abdi 4e60361f77 fix: use phone-reachable relay URL in wallet-import QR
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>
2026-06-22 19:39:08 +03:00
Khalid Abdi 7a359d4911 feat: QR pairing for wallet import (backend + website)
- 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>
2026-06-21 21:31:51 +03:00
Khalid Abdi 2d47abcc42 feat: patient wallet — real Signing, encrypted share relay, import-from-app
Backend
- clinic Ed25519 signing key (services/signing.ts, routes/signing.ts,
  clinic_signing_keys table) — Settings → Signing is now real
- @noble wallet-crypto (lib/wallet-crypto.ts): ed25519 identity, base58check
  wallet numbers, sealed-box (x25519 + xchacha20poly1305)
- /wallet Socket.io relay namespace (challenge-signed device auth) forwarding
  only ciphertext; emitToWallet helper
- import-from-app flow (routes/patients-wallet.ts, services/wallet-share.ts):
  request-share → patient approval → decrypt + verify → review draft → commit
- temporary shares: patients.share_expires_at + 5-min auto-delete sweep; revoke

Frontend
- SigningPanel wired to live key/fingerprint/rotate + shared-records list
- "Import from a patient app" dialog (lib/signing.ts, import-from-wallet-dialog)
  reusing the draft-review path; temporary badge on the patient list

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 18:19:57 +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 eb94c1549a frontend: fix onboarding bounce, mobile sidebar btn, notif deep-links, analysis header
- Fix clinic onboarding loop: refresh session after setActive before navigating,
  surface setActive errors, and guard AppAuthGuard's onboarding redirect race (#1)
- Add a mobile-only floating top-right SidebarTrigger so the sidebar is reachable
  when the offcanvas sidebar is closed on phones (#5)
- Make notifications clickable: navigate to the source (conversation/patient) and
  mark read; MessagesView/PatientsView honor ?conversation= / ?file= deep links (#6)
- Analysis page: add an Overview header with a time-range segmented control and a
  Customize popover that shows/hides sections; range slices month-based charts (#10)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 20:21:05 +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 a06c8c739b frontend: Obsidian-style record graph + pop-out dialog + records list
The record graph was a cramped React Flow box wedged inside a sheet
section. Redesign it Obsidian-style — round nodes with the label
underneath, a soft glow, and a hover-focus that highlights a node plus
its neighbours while dimming the rest.

Move it out of the inline section: the sheet now shows an "Open graph"
button (closes the sheet, opens the graph in a large dialog) and a
clickable list of the patient's records (problems + visits); clicking a
record opens a detail dialog.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 19:47:16 +03:00
Khalid Abdi 3b4e1c7f61 frontend: patient delete + complete sheet + Obsidian-style record graph
- deletePatient() lib fn (DELETE /api/patients/:fileNumber already existed)
- confirmed delete button in the patient sheet (full-clinician only)
- aggregate appointments/prescriptions/invoices into the sheet (by file #)
- new shared RecordGraph (@xyflow/react + d3-force) linking problems↔visits,
  added as a "Record graph" section + reusable by AI Graph mode
- i18n keys for delete + new sheet sections

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 19:02:32 +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 3eb5687f4d frontend: messages search, care-team employee dialog, PCP picker + transfer, activity detail, analytics grid
- Messages: search the inbox and the compose member picker.
- Care Team: clickable member rows open an employee dialog showing role +
  permissions, with change-role (updateMemberRole) and remove.
- Patients: Primary Care is now a provider dropdown (defaults to self for a
  doctor); add a Transfer action + dialog wired to the transfer API.
- Activity: entries are clickable, opening a detail dialog.
- Analytics: Section takes a columns prop so each row fills evenly (no orphan
  card in Appointments).
- Add lib/staff.ts (listProviders), transferPatient client, rolePermissionSummary
  helper, and i18n keys for all new strings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 19:51:38 +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 e0de20b551 i18n: convert feature pages, dialogs, auth sub-pages and nav to t()
Move all hardcoded UI strings in the appointments, prescriptions, tasks,
messages, activity, analysis and patients views (plus their dialogs and
detail sheets), the remaining auth pages (verify-email, forgot/reset
password, accept-invite, onboarding), the clinic form and the sidebar user
menu into i18next keys in en/translation.json. Clinical option values
(appointment types, frequencies) stay canonical. English-only; no new
locale yet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 21:57:00 +03:00
Khalid Abdi 49c39bd853 Tasks/notes/prescriptions/appointments/patients UX fixes
- Task dialog: rename Title→Subject, add a "Details" textarea (notes),
  widen to sm:max-w-lg.
- Tasks & Notes: replace split-panes with full-width lists that open a
  right-side Sheet on click (TaskDetailSheet, NoteDetailSheet), mirroring
  the Patients table → detail Sheet pattern.
- Prescriptions: rows are now clickable and open PrescriptionDetailSheet
  with the full Rx details (incl. duration + notes).
- New prescription dialog: Duration is a dropdown of presets with an
  "Other" → custom input, plus a Notes textarea; NewPrescription gains
  notes and threads duration/notes through to the list.
- Appointments: header search filtering by patient/type/provider,
  grouped by date, with an empty state.
- Patients: pressing Enter in the search box opens the top match's record.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 18:59:19 +03:00
Khalid Abdi 9588d16869 Refine sidebar, Notes, and Patients per review feedback
Round-2 fixes to the 6 features:

- Logo: enlarge the sidebar mark, drop the inline wordmark, and show
  "temetro" via a hover tooltip; bigger logo on the auth screens.
- Sidebar footer: wrap the quick-nav, clinic switcher, and user menu in a
  single bordered block so it reads as one footer instead of three cards.
- Sidebar nav: highlight the active page (usePathname + data-[active]), and
  add an Appointments & Schedule sub-page under Patients that auto-expands
  for the current section. New mock /appointments page; reachable via ⌘K.
- Notes: redesign to a two-pane list + editor with an Empty state on the
  right when nothing is selected; fix the editor so text starts top-left
  (div instead of a centering <button>); compact the toolbar; add
  .note-content typography in globals.css so headings/lists/etc. actually
  render (the app has no typography plugin).
- Patients: new PatientDetail laid out to fit the side Sheet (stacked
  full-width sections, no nested click-to-expand dialogs); keep Edit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 00:49:43 +03:00
Khalid Abdi a4fc9a4fe9 Add command palette, footer clinic menu, patient Sheet, Analysis page, favicon
Several navigation/UX additions:

- Command palette (⌘K): components/command-palette.tsx wraps the app shell
  (mounted in app/(app)/layout.tsx) with a controlled COSS CommandDialog listing
  the nav pages; a "Quick nav" Kbd button in the sidebar footer also opens it.
  Installed @coss/kbd. Extracted the nav list into lib/nav.ts so the sidebar and
  palette share one source of truth.
- Clinic switcher moved from the sidebar body into the footer; its menu now
  opens a read-only "Clinic info" dialog and a "Create clinic" dialog instead of
  routing to /onboarding. Shared CreateClinicForm (components/clinic/) is reused
  by onboarding.
- Patients: clicking a row opens a right-side Sheet with the full record
  (components/patients/patient-detail-sheet.tsx) instead of navigating to the
  chat; PatientResult gained a vertical "column" layout for the Sheet.
- New Analysis page (app/(app)/analysis/) — a mock dashboard (revenue/profit,
  patient volume, appointments, operations) reusing Sparkline + Card + Badge.
- Logo: set metadata.icons so the new mark appears as the browser-tab favicon.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 19:40:11 +03:00
Claude 1ecbd16404 Wire frontend to the backend: auth, organizations, real patient API
Connects the UI-only app to the new backend over Better Auth + a small
patient API client, replacing the in-memory fixture and placeholder identity.

- Better Auth React client (lib/auth-client.ts) + shared access-control
  roles; API client (lib/api-client.ts) sending credentials cross-origin.
- Designed (auth) route group: login, signup, verify-email, forgot/reset
  password, clinic onboarding, and accept-invite.
- proxy.ts (this Next's renamed middleware) optimistic redirect + an
  authoritative client AppAuthGuard requiring a session and active clinic.
- Real identity in nav-user (useSession + sign out); the unused team
  switcher repurposed as an organization (clinic) switcher; Care-team
  settings tab manages members and invitations.
- lib/patients.ts now calls the org-scoped API (types unchanged); patients
  table and create/edit dialog updated for async create/update.
- next.config: standalone output + Dockerfile for containerized runs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 21:28:02 +03:00
Khalid Abdi dec150c77d Patients page, clinical Settings re-theme, Polar-style user footer
- Patients: new /patients directory table (name, MRN, age·sex, status,
  last seen, allergies) with search + Add patient; clicking a row opens
  the record in the chat via /?patient=<#>. chat-panel reads that search
  param (Suspense-wrapped) and runs the lookup once on arrival. Sidebar
  "Patients" now links to /patients; lib/patients gains listPatients().
- Settings: re-themed to clinical tabs (Profile · Records · Signing ·
  Care team · Developers) — clinician profile, patient notifications,
  patient-owned-storage/signed-records features, and a Signing key panel.
  Same layout/components, only content changed.
- Sidebar footer: Polar-style NavUser (avatar + name + chevron) opening a
  menu upward — Settings · Docs & GitHub · Theme · Log out. Collapsed
  ("locked"): avatar only, name on hover (tooltip), menu opens to the
  right. Placeholder identity (no auth yet).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 21:55:33 +03:00