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>
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>
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>
- 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>
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>
New `note` table (src/db/schema/notes.ts) referencing organization + user,
with org+author scoping so a doctor only sees their own notes within the active
clinic. Adds:
- src/types/note.ts + src/lib/note-validation.ts (zod)
- src/services/notes.ts (CRUD, treats non-uuid ids as not-found)
- src/routes/notes.ts mounted at /api/notes, gated requireAuth → requireOrg
- schema barrel + generated migration drizzle/0001_*.sql (applied on startup
by the runtime migrator)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Returning members were sent to onboarding on every sign-in because the new
session had no activeOrganizationId. Add a session.create `before` hook that
defaults it to the user's first clinic membership, so sign-in lands straight
in the app.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Since email verification is no longer enforced at sign-in, gating
organization creation on `user.emailVerified` blocked onboarding with
"You are not allowed to create a new organization". Allow any signed-in
user to create a clinic; re-tie to emailVerified when verification returns.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- emailAndPassword.requireEmailVerification = false so users can sign in
immediately; verification emails are still sent and /verify-email still
works. Flip back to true to make it mandatory later (TODO noted in code).
- Add backend/CLAUDE.md documenting stack, commands, the auth/schema
generation workflow, and runtime gotchas.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- useSecureCookies now keys off the BETTER_AUTH_URL scheme instead of
NODE_ENV, so login works over http://localhost in the (production-mode)
Docker stack instead of the browser silently dropping the session cookie.
- env parsing treats empty strings as unset, so compose-supplied optionals
like `SMTP_PORT=` no longer fail coercion (Number("") === 0) and crash boot.
- docker-compose: configurable host Postgres port (POSTGRES_PORT) to avoid
clashing with an existing local Postgres.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Implements the first temetro backend: an Express 5 API on Postgres via
Drizzle ORM, with authentication and multi-tenant clinics powered by
Better Auth.
- Auth: email/password with required email verification, password reset,
rate limiting, CSRF/trusted-origins, secure cookies, session audit hook.
- Organizations (clinics) with RBAC (owner/admin/member/viewer) and an
extended `patient` permission set; member invitations by email.
- Org-scoped patient records mirroring the frontend Patient shape, with
CRUD endpoints gated by permission (read/write/delete).
- Email helper logs links to the console when SMTP is unset (zero-setup
local dev); Dockerfile + docker-compose (db + backend + frontend) with
migrations applied on startup and a configurable Postgres host port.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>