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>
The placeholder only mentioned patient lookup, but the chat now answers
plain-language questions, displays records, adds, and imports. Update it to
"Ask anything, or type /patient 10293".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add an Add-item button to the Inventory page that opens a COSS form dialog
(name required; form, strength, stock, reorder point, location, expiry). On
submit it persists via createInventory and prepends the saved row so it
appears immediately. Mirrors the appointments add-dialog pattern.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make the chat feel alive and let the agent act on the clinic — safely.
UX (frontend):
- Stream `data-step` parts from each tool into an inline Chain-of-Thought
trace, plus a "Thinking…" shimmer while a request is in flight (works even
on the non-streamed external+Veil path).
- Replace the modal Veil consent Dialog with an inline, once-per-session
"Veil" confirmation above the input (with a "Use local model" option).
- Render new list + action-preview cards; chat-input now uses COSS tokens.
Agent (backend):
- Add display tools (listAppointments / listTasks / listPrescriptions) and
propose tools (proposeAppointment / proposeTask / proposePrescription) that
validate as a dry run and stream an approval card — nothing is written until
the clinician approves, via the existing RBAC-gated create endpoints.
- previewImport now also covers single-patient add + migration.
- System prompt: display + add only, never edit/delete or alter the schema;
stronger migration guidance. ToolContext carries the viewer for task scoping.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The chat defaulted to a Claude model, so a user who saved only a Gemini key got
a silent failure (backend derived Anthropic, found no key, errored — and the UI
showed nothing).
- Backend: resolveModel now falls back to whichever provider actually has a key
(preferring the configured one), so a Gemini key just works regardless of the
picked model. Clear 400 if no provider is configured at all.
- Frontend: the chat seeds its model/effort from the saved AI config, and now
surfaces request failures as both a persistent alert banner and a toast —
never silent.
- Settings: switching provider auto-selects that provider's default model.
- Refreshed the model catalog to current ids (Gemini 2.5 Pro/Flash + 2.0 Flash;
dropped the retired gemini-1.5-pro); per-provider default model updated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`docker compose up` now works with no .env editing. A new entrypoint
(docker-entrypoint.sh) generates any missing secret (BETTER_AUTH_SECRET,
AI_CREDENTIALS_KEY) on first start and persists it to the temetro_secrets
volume, so values stay stable across restarts (rotating them would log users
out / invalidate stored AI keys). Real values passed via .env/compose still win.
This also fixes the boot failure where the compose backend ran with
NODE_ENV=production but never passed AI_CREDENTIALS_KEY through, tripping the
production guard. The secret is now an optional pass-through and BETTER_AUTH_SECRET
no longer hard-fails when unset.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The frontend dependency install (1000+ packages) failed in Docker with
ECONNRESET / "network aborted" mid-stream. Add npm fetch retries and a long
timeout so a transient registry drop is retried rather than failing the build.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The build stage ran `npm ci`, which tried to compile better-sqlite3 — a
dev-only transitive dependency of @better-auth/cli — and failed in Alpine for
lack of Python/node-gyp. The build step is just `tsc` (no native binaries
needed) and better-sqlite3 is never used at runtime (the prod stage omits dev
deps), so install dev deps with --ignore-scripts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the mock chat with the live backend agent:
- chat-panel uses @ai-sdk/react useChat against /api/chat (credentials included),
passing the selected model + effort per send. The `/patient <file#>` fast-path
is kept as an instant client-side shortcut.
- Renders the agent's streamed data parts: patientCard → record cards
(PatientResult), labCard → new LabChartCard (visx area chart with a high/low
flag badge in the top-right corner + recent values), importPreview →
ImportPreviewCard (the human approval gate: review counts/issues, then commit
via POST /api/ai/import — nothing is written until approved).
- Veil consent: a one-time dialog before the first send to a cloud model,
explaining that identifiers are de-identified before leaving the clinic.
- Attached text files (csv/json/txt…) now include their content in the message
so the agent can parse an export for import.
Shared chat message/data-part types in lib/ai-chat.ts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New per-user AI settings section (visible to every clinician, not just admins):
- Inference mode: cloud API key vs local Ollama.
- Provider + key (OpenAI / Anthropic / Gemini) via a select; the key field is
write-only and shows a "key set" indicator from the backend's apiKeySet,
never echoing the stored secret. Default model + effort per provider.
- Local Ollama: base URL + model name with a "Test connection" probe.
- Veil: de-identification level (full / names / off) with mode-aware copy.
Talks to the new /api/ai/config + /api/ai/test endpoints via lib/ai-settings.ts.
The old mock clinical selects (specialty/facility/time range/access/response)
are dropped entirely rather than relocated — they were placeholder knobs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Real LLM chat replacing the mock, backend-centric per the plan:
- Multi-provider API-key mode (OpenAI / Anthropic / Gemini via the AI SDK) plus
local Ollama (OpenAI-compatible endpoint). Provider is derived from the
picked model id; the matching stored key is used. New user_ai_settings table
holds per-user config with provider API keys encrypted at rest (AES-256-GCM,
src/lib/crypto.ts, keyed by AI_CREDENTIALS_KEY).
- POST /api/ai/config (get/put, secrets never returned), POST /api/ai/test
(Ollama ping / key presence), POST /api/ai/import (approved migration commit,
re-validated server-side, reuses the audited patient service).
- POST /api/chat: streamText agent with tools (getPatient, getPatientLabs,
searchPatients, previewImport). Real record data streams to the clinician as
custom data parts (cards) while the model sees only Veil-redacted results.
- Veil (src/services/ai/veil.ts): de-identifies patient identifiers to tokens
before external calls, resolves tokens on tool args, and rehydrates the final
answer. Bypassed for local Ollama. External mode runs non-streamed so the
rehydrated text is correct. Every call is audited (provider + Veil level).
- Shared role-scoping helpers extracted to src/lib/role-scope.ts (reused by the
patient routes and chat tools so visibility rules match).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the five clinical context selects (access, response mode, specialty,
facility, time range) with one model picker styled like the reference design:
a primary model list with descriptions, an Effort submenu, and a "More models"
submenu. Move the Add-patient pill up into the toolbar and drop the now-empty
bottom context-selector card so the input is a single clean rounded surface.
Model/effort selection is lifted to ChatPanel so it can travel with each send
(wired to the backend agent in a later phase). Adds a shared model catalog
(lib/ai-models.ts) reused by the picker, Settings, and the chat wiring.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Guard the per-org count lookup so the build (tsc -p) doesn't fail on the
possibly-undefined first row.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Pharmacy: the sidebar entry now expands into Pharmacy + a new Inventory page
(searchable medication stock with derived in-stock/low/out availability,
backed by /api/inventory). Fix the dispensing-queue "Expiring" badge so it
only flags courses ending within the next 7 days, not ones already elapsed.
- Lab "Add analysis result": the patient picker no longer lists patients until
you type and supports arrow-key + Enter selection; the test field offers a
catalog of common analyses with an Advanced option (custom analysis + ref
range); submitted results now show immediately in a Recent results feed.
- Analysis: add a Live panel (real-time line chart) and replace the flat trend
sparklines with bklit-ui area + bar charts (installed from the @bklit shadcn
registry; chart CSS variables wired into the theme for light and dark).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CommandItem rendered the icon flush against the label. Add a gap (and normalize
icon sizing) so the two no longer touch.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add an `inventory` resource mirroring the prescriptions feature end-to-end:
Drizzle table (org-scoped, indexed), domain type, zod validation, service
(list/get/create/update/delete), and an RBAC-gated CRUD router mounted at
/api/inventory. Grant the new `inventory` statement to roles — pharmacy gets
read/write, full clinicians read/write/delete, reception/lab none — in both the
backend access control and (mirrored) the frontend. Record writes in the
activity log via a new `inventory` entity type. Includes the migration and an
idempotent demo seed script.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Same layout, better inbox: avatars on conversation rows, a numeric unread
badge (kept live by the socket handler), primary-tinted timestamps for unread
rows and softer hover states. In the thread: day separators
(Today/Yesterday/date), consecutive same-sender messages within 5 minutes
grouped with one sender label + one timestamp and tightened corners, and a
"Start a conversation" button on the empty state.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Count messages from others newer than the caller's read pointer alongside the
existing last-message lookup, expose it as unreadCount on
ConversationSummary, and derive the unread boolean from it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Myself/Other assignee picker looked like two loose buttons; replace it
with the COSS Tabs segmented control, with the department select (now five
departments) living in the "Other" tab panel. State semantics unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New /lab department home: the lab's task queue (tasks assigned to the Lab
department, with done toggle) and an "Add result" dialog — pick a patient,
enter test/value/flag/date — that posts to the new labs append endpoint via
appendLabs() in lib/patients.ts.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New /pharmacy department home: KPI cards (active prescriptions, expiring
within 7 days from parseable durations, patients on medication) over a
dispensing queue of active prescriptions (oldest first) with search, a
mark-completed action and the existing detail sheet. No create/delete UI —
prescribing stays with clinicians.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Lab staff hold lab:write but not patient:write, so they can't go through the
wholesale PUT update. The new endpoint appends lab rows (positions continue
after the current max), bumps updatedAt, records activity and notifies the
clinic — without touching the rest of the record.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mirror the backend role changes (drop viewer, add pharmacy + lab and the lab
statement). Replace the binary requiresClinical gating with access areas
(clinical / pharmacy / lab) probed from Better Auth permissions —
prescription:delete marks full clinicians, prescription:write the pharmacy
area, lab:write the lab area — so pharmacy doesn't inherit the AI chat or
notes. Pharmacy and lab land on their new dashboards and get their own nav
items; reception behavior is unchanged. Includes the i18n keys for the new
pages and the messages polish that lands in the following commits.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Drop the unused read-only "viewer" role and remap any members (and pending
invitations) holding it to "member" via a custom migration. Add a "lab"
statement (read/write) granted to all full clinicians, plus two new
provisionable department roles: "pharmacy" (patient/appointment read,
prescription read+write — no delete, which doubles as the full-clinician
marker the frontend probes) and "lab" (patient/appointment read, task queue,
lab results via the new statement). Both join TASK_DEPARTMENTS so tasks can
be assigned to them.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- sidebar + auth logo now invert to black in light mode (dark unchanged),
same trick the docs site uses — no second asset needed
- prescriptions page: search by patient, file number, medication,
prescriber or status, with no-match / empty states
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Records tab: data sources, import/export, retention (replaces placeholder)
- Developers tab: API base URL, access-tokens empty state, resources
- Signing tab: how-it-works steps + backup key entries
- Team member dialog: header band, per-resource permission rows with icons
and action chips, COSS Select for role change
- Care team: hint that clicking a member shows their permissions
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- ToggleRow supports controlled checked/onCheckedChange; CopyField copy works
- ProfilePanel loads/saves preferences via /api/settings, name via updateUser
- dirty tracking with a sticky save bar that follows the scroll
- Delete account: password-confirmed dialog wired to authClient.deleteUser
- clinician ID / handle now show the real session user
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- 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>
- Add patients.primary_provider_id (FK to user) + migration; persist it through
create/update and surface it on the Patient shape.
- Scope patient list/get for the `doctor` role to their own panel (with a
createdBy fallback for legacy rows); admin/owner/member/reception/viewer keep
seeing every patient.
- Add POST /api/patients/:fileNumber/transfer to reassign a chart (updates the
provider link + PCP label, records activity, notifies the clinic).
- Add GET /api/staff/providers (any member) listing clinical-capable members for
the PCP picker and transfer dialog.
- Scope the activity feed: non-admins see only their own actions; owners/admins
see the whole clinic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Hide "Create clinic" (sidebar footer) for non-admins; only owner/admin can
spin up additional clinics. Onboarding for brand-new users is unaffected.
- Analysis: drop the bar charts; show line charts (Sparkline) inside KPI cards
that open a detail dialog with the full chart + per-point breakdown.
- Add Team Member: validate the username client-side (no spaces; letters,
numbers, dots, underscores) with a clear warning + field hint.
- Tasks: New Task now has an Assignee selector (Myself / Other → department).
Tasks are visible to the department they're assigned to (or the creator), and
show who created them. Backend adds assignee_role + created_by_name with
visibility filtering in listTasks; owners/admins see all.
- Care team: removing a member now asks for confirmation first (dialog) and
surfaces success/failure + refreshes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Analysis page only showed KPI numbers. Add two real time-series and render
them as dependency-free bar charts (matching components/chat/sparkline.tsx):
- backend: GET /api/analytics now returns `trends.patientsByMonth` (new patients
per month over the last 6 months) and `trends.appointmentsByWeekday`
(appointments per day for the current week), bucketed in JS from one query each.
- frontend: new components/analysis/bar-chart.tsx and two chart sections on the
Analysis view (Patient growth, Appointments this week), with i18n keys.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the email-invitation flow with admin-provisioned staff accounts and
add role-based access that changes what each member sees.
Backend:
- Enable Better Auth `username` plugin (staff sign in by username); regenerate
auth schema (+ username/displayUsername on user) and migration 0007.
- Add `doctor` and `reception` roles to the access-control RBAC. `reception` is
scoped to scheduling + registration (no `prescription` statement).
- New `/api/staff` route: POST creates a user (auth.api.signUpEmail) and adds
them to the active clinic (auth.api.addMember); GET lists members + usernames.
Gated by requirePermission({ member: ["create"] }).
- Redact clinical PHI for the reception role in the patients service (read,
create and update) so demographics-only is enforced server-side.
Frontend:
- usernameClient + Email|Username tabs on the login form.
- lib/roles.ts: useActiveRole + Better-Auth-permission-driven nav visibility,
default landing, and a route guard (reception -> /appointments, blocked from
clinical routes). Applied to the sidebar, command palette and auth guard.
- Care team page now provisions staff via a two-step Add-team-member dialog
(details -> username/password) hitting /api/staff; removes the email-invite
and pending-invitation UI. New members are contactable from Messages
automatically (they become org members).
- Hide clinical sections of the patient form and the admin-only settings tabs
for non-clinical/non-admin roles.
All permission management stays in Better Auth (per the better-auth skills now
referenced in backend/CLAUDE.md).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
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>
Auto-generate notifications on patient record create/update (fan out to
the other clinic members, pushed live), and wire the sidebar bell to real
data via a useNotifications hook over the shared socket: live unread badge,
real list, mark-all-read on open. Drops the hardcoded sample array and the
dead "View all" link.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add conversations/participants/messages tables, a participant-scoped REST
API (/api/conversations) and a Socket.io server (session-authenticated
handshake; per-user + per-conversation rooms) sharing the HTTP port. New
messages broadcast live and create per-recipient notifications. Also lands
the notifications table + service + routes (used by the message flow). The
Messages page is rewritten: live threads, unread state, and a compose
dialog to start a conversation with a clinic member.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add GET /api/analytics returning real aggregates over the clinic's
patients/appointments/prescriptions/tasks, and rebuild the Analysis page
to render them. Drops the fabricated revenue/profit cards — temetro has no
billing data source.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the activity_log table, a best-effort recordActivity() service and a
GET /api/activity feed, and write entries on create/update/delete of
patients, notes, appointments, prescriptions and tasks. The Activity page
now shows the real audit trail (actor, action, patient context, time);
the fabricated signing hashes / approval badges are gone — that vision
stays deferred.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the tasks table, validation, service and routes (/api/tasks with a
PATCH for partial updates / the done toggle, RBAC-gated) and the frontend
data module. The tasks board now loads, creates and toggles real data
(optimistic toggle with rollback).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the prescriptions table, validation, service and CRUD routes
(/api/prescriptions, RBAC-gated; prescriber defaults to the signed-in
clinician, prescribedAt to today) and the frontend data module. The page
now loads/persists real data and computes its status KPIs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the appointments table, validation, service and CRUD routes
(/api/appointments, RBAC-gated) and the matching frontend data module.
The appointments page now loads and persists real data; KPIs are computed
from it and the schedule/calendar anchor to the real current date.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extend the clinic access-control statements and role grants with
appointment/prescription/task resources (mirrored in the frontend client
AC), and widen the requirePermission type to accept any defined resource.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reflect that frontend/ and backend/ are now subdirectories of a single
git repo (the old per-folder repos are merged in); landing page lives in
a separate temetro-landing repo; note the POSTGRES_PORT workaround.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>