Skip to content

Green Wellness

Changelog

What’s new in each release of the scheduling platform

Show:
v2.97.RENEWALTAIL1current
2026-06-21Production

Fixed a quiet bug that had stopped two kinds of renewal outreach since June 13: the 90-day 'we miss you' check-in and the 7-days-after-expiry win-back emails. The daily 21/14/7/0-day renewal reminders were never affected — those kept going out. The cause was the same database-query fragility behind the recent provider-chart crash: one query was written in a way the database engine can choke on, which silently aborted the rest of that nightly job before it finished. Rewrote the fragile queries the safe way, and wrapped each stage of the job so one part failing can never silently kill the rest again. Also swept the rest of the system and fixed the same fragile query pattern in a handful of other nightly emails (new-patient welcome drip, intake reminders, 2-hour text reminders, review requests) and on the provider chart's medication/allergy panel — all of which had the same latent risk.

Show technical details

Fixed

  • 🔧 **Renewals re-engagement + win-back tail-throw eliminated (live since ~6/13).** /api/cron/renewals ran a 90-day re-engagement query with a Prisma-7.8 nested-relation filter (where: { ..., patient: { emailUnsubscribed: false } }) — the same shape Prisma 7.8 throws on SYNCHRONOUSLY (CHARTFIX2 class), before a promise exists, so the per-query .catch() never runs and the throw escaped, aborting the rest of the route (re-engagement + win-back sends + the final heartbeat) every night. Rewrote the re-engagement query to a scalar where + in-memory emailUnsubscribed filter. Defense-in-depth: each post-stage block (escalation / re-engagement / win-back) now runs in its OWN try/catch so a single block throwing can never abort the whole route or suppress the final actor=renewals result=... heartbeat. The win-back query itself was already scalar (db.patient direct, emailUnsubscribed is a column) — it never threw; it just never got reached. The daily stage reminders (21/14/7/0d) were never affected. [cron][renewals][prisma][reliability][hipaa]
  • 🩺 **Health-check cron staleness now reflects last-FIRED, not just last-completed.** /api/health matched cron staleness on detail startsWith 'actor= ' (trailing space) — which only matches the POST-compute result= heartbeat. A cron that tail-throws or benignly early-returns (fires but never reaches its result= line) showed falsely 'stale' even though it ran today (why doh-nudge / waitlist looked broken when they were fine). Now takes the most recent of BOTH the pre-compute fire (actor=, written right after auth) AND the post-compute result (actor= result=...). Matches both shapes explicitly so a prefix actor (reminders) can't match a longer one (reminders-2h). Canary fallback preserved. [health][monitoring][observability]
  • 🧹 **Prisma-7.8 nested-relation sweep — same crash class fixed in 5 more crons + the chart med/allergy panel.** Converted nested-relation-in-where filters to scalar + in-memory filtering (behavior identical) in: doh-nudge, intake-reminder, reminders-2h, new-patient-drip (Day 3 + Day 14), and review-request — each fetched candidates with a patient: {...} (and in some cases workflowEvents: { none } / intakeForm: null) relation filter that can sync-throw and silently kill the send. Also fixed src/lib/ddi-shadow-source.ts (the SoapEditor medication + allergy reader, exact CHARTFIX2 intakeForm + appointment-relation where/orderBy shape) and src/lib/renewal-pipeline.ts assessAndMarkDoctorReady (its intakeForm.count({ where: { appointment: { patientId } } }) was caught by an outer try/catch but silently zeroed the doctor-ready signal). All use the proven flat two-step (scalar appointment ids → intake by appointmentId in). [cron][prisma][reliability][hipaa][sweep]
v2.97.CSRFLOG1
2026-06-20Production

Behind-the-scenes security hardening (no visible change).

What this means for you

Behind-the-scenes security hardening (no visible change). Added a cross-site-request guard so another website can't trick a logged-in staff/provider/patient browser into silently changing data in our system. It's running in 'watch-only' mode first — it logs anything suspicious but doesn't block yet — so we can confirm it never trips on a real action before we switch it to fully enforcing. First item from the expert review's security batch.

Show technical details

Changed

  • 🛡️ **CSRF same-origin guard on cookie-authed PHI mutations (security batch #1, LOG-ONLY).** src/proxy.ts now checks Origin/Referer on POST/PUT/PATCH/DELETE to /api/{admin,provider,patient,dispensary} against a host allowlist; cross-origin → logged as [csrf] would-block. Exempts vendor webhooks, cron/bearer routes, integrations, health; Next.js's built-in Server-Action origin check covers page-POST actions (out of scope here). Edge-safe. Default = log-only; flip CSRF_ENFORCE=true to hard-403 after observing zero false-positives (report-then-enforce, same discipline as CSP). security-auditor reviewed: clean, bypass-resistant (Origin/Referer are browser-forbidden headers), false-block risk low. [security][csrf][hipaa][log-only]
v2.97.ARMNOW2
2026-06-20Production

Two more from the expert review. (1) Fixed the Washington FAQ that said 'the evaluation can be done by telehealth' — that contradicted our actual rule (and our own legal research): a first-time/new-patient visit is in person at Lynnwood, and telehealth is for renewals of returning patients. The FAQ now says exactly that. (2) Turned ON Isabella's stale-message safety net: if a patient's message to a human goes unanswered past our business-hours service window, Isabella now automatically sends that patient a brief, no-details 'we got your message, we'll reach you by [next business time]' note on the channel they consented to — so nobody waits in silence. It only applies going forward (it does not message the old backlog), only for patients who consented, and the note contains no health details.

Show technical details

Changed

  • ⚖️ **WA telehealth FAQ corrected to match our licensed model + legal research.** /qualify/washington FAQ 'Can I do the whole thing online?' said the evaluation can be done by telehealth — contradicting GW's rule (RESEARCH_INITIAL_VISIT_IN_PERSON + Mariane 2026-05-15: new-patient first eval is in-person at Lynnwood; telehealth is renewals-only for returning patients). Rewrote the answer to state new=in-person / renewal=telehealth / card issued in person. Removes a self-authored public-page contradiction (regulatory exposure). [regulatory][wa][green-zone][claim-accuracy]
  • 🟢 **Isabella stale-warm-transfer SLA auto-ack ARMED** (ISABELLA_STALE_TRANSFER_SLA_ENABLED=true). Built-but-off since IRC0012; now on. When a warm-transfer to a human passes the business-hours SLA (default 4h), Isabella auto-sends the patient a generic no-PHI acknowledgement on their consented rail (M365 email / SMS-if-consented), forward-only (no retro-blast of the historical backlog), idempotent, per-fire cap 25, PHI-free audit. Closes patients-waiting-in-silence. [isabella][sla][armed][hipaa][consent-gated]
v2.97.ARMNOW1
2026-06-20Production

Three safety + polish fixes from the expert review. (1) Inbound faxes are now held, not stored, until RingCentral signs its data-protection agreement (BAA) — faxes are full medical records, and storing them with a vendor that hasn't signed yet would be a reportable issue, so the system now safely acknowledges each fax and logs it without saving the contents until the agreement is in place. (2) Appointment + renewal text reminders now greet patients by first name (the name was being collected but left out of the message). (3) Fixed a homepage claim that read 'walk in and walk out authorized' — that implies a guaranteed approval, which we can't promise; it now says most patients are seen same-day and the provider makes an independent decision.

Show technical details

Changed

  • 🔒 **Runtime BAA kill-switch on the inbound-fax line (expert-sweep P0).** /api/inbound/fax now fail-closes while RingCentral is BAA-pending: after auth + confirming a real inbound fax, if INBOUND_FAX_BAA_OK !== "true" it writes a PHI-free INBOUND_FAX_RECEIVED detail=baa_gated audit row, returns 200 (so RC doesn't retry), and does NOT fetch content or persist. Default (env unset) = gated. This stops a reportable §164 disclosure (storing full medical records with a vendor under no signed BAA) — the build-time vendor-baa gate only warned; this is runtime enforcement. Flip the env true the day the RC BAA executes. hipaa-architect reviewed: fail-closed + PHI-free confirmed. [hipaa][baa][fax][p0][fail-closed]
  • ✍️ **Appointment + renewal SMS now greet by first name.** smsReminder() + smsRenewalReminder() already received firstName but dropped it from the message body; both now lead with Hi , (guarded — omitted if name is blank). [sms][comms][personalization]
  • 🟢 **Green-zone fix: removed an implied-guarantee homepage claim.** The 'Same-Day Authorization / walk in and walk out authorized' trust card read as a guaranteed approval + dispensary steer. Now 'Same-Day Appointments / most patients are seen same-day — your provider independently reviews your records and makes the authorization decision.' [seo][green-zone][claim-compliance]
v2.97.PRVDOC1
2026-06-20Production

Providers can now upload medical records to a patient too — completing the 'records in one place' picture.

What this means for you

Providers can now upload medical records to a patient too — completing the 'records in one place' picture. Staff (admin/manager/reception) already got the Attach-records button on the patient page yesterday; this adds the provider side. A provider can only attach records to patients they actually have an appointment with (so it maps to the right chart), and any provider-uploaded record shows a 'Provider' tag in the patient's Documents list so it's clear who added it. (Patients upload via their portal, as before.) Note: the in-portal upload button for providers is the next small step; this ships the secure upload capability + correct labeling first.

Show technical details

Added

  • 📎 **Provider medical-records upload (closes the provider leg of cmqixd28q).** New POST /api/provider/documents — a provider attaches a record to a patient, scoped (HIPAA minimum-necessary) to an appointment they own (appointment.providerId === provider.id); the patient is derived from that appointment, so a provider can't reach a patient they aren't scheduled with. Dual-auth (legacy ?token= + new provider_session cookie) mirroring the existing provider document-download route. Same private-Blob + compress/EXIF-strip + 25MB + MIME-allowlist pipeline as the staff route (ADU0001), uploadedBy="provider". New PHI-free audit literal PROVIDER_DOCUMENT_UPLOADED (provider name + appt id + mime/size only — never filename/blobURL/patient identity). The admin patient Documents list now labels uploads **Patient / Provider / Admin** correctly (was Patient/Admin only). NO schema change (docType metadata = separate fast-follow). NO provider-portal upload button yet (next step). [provider][documents][hipaa][cmqixd28q]
v2.97.BAADISC1
2026-06-19Production

Corrected the public Privacy Notice and About page to accurately list the technology vendors we actually have signed Business Associate Agreements (BAAs) with. The old list named some services we don't use for patient health information (and that don't have BAAs with us) and left out several we do — so it could have misrepresented how patient data is handled. The list now reflects our real signed BAAs (Microsoft 365, AWS Bedrock, Vercel, Neon, Doxy.me, Retell AI, and Practice Fusion), and the 'we have BAAs with every vendor' wording was changed to the accurate 'we require a signed BAA before any vendor is allowed to handle health information.' No patient-facing functionality changed — this is an accuracy/compliance copy fix.

Show technical details

Changed

  • 🔏 **Privacy Notice + About: vendor/BAA disclosures corrected to match our actual signed BAAs (BAA_STATUS source of truth).** The /privacy "Third-Party Service Providers" list previously claimed signed BAAs with Resend, Salesforce, Stripe, and Twilio — none of which currently hold a signed GW BAA for PHI (Resend + analytics are code-gated OUT of the PHI path; Salesforce is decommissioning; Stripe/Twilio are pending) — and omitted vendors we DO use under signed BAAs. Replaced with the accurate set: Microsoft 365 (email), AWS Bedrock (AI), Vercel (hosting/storage), Neon (database), Doxy.me (telehealth video), Retell AI (voicemail), Practice Fusion/Veradigm (EHR). Softened the absolute "we have signed BAAs with all vendors" claim on both /privacy and /about to the defensible policy statement ("we require a signed BAA before authorizing any vendor to handle PHI"). Fixed an "above/below" cross-reference. Patient-facing legal accuracy fix; no behavior change. [privacy][hipaa][baa][compliance-copy]
v2.97.LOCMATCH1
2026-06-19Production
For front desk

The online booking form now shows the same clinics Isabella offers — filtered by new vs. returning patient.

What this means for you

The online booking form now shows the exact same clinics our phone receptionist Isabella would offer — filtered by whether the patient is new or returning. Before, the booking page listed every open clinic regardless of who can be seen there; now a first-visit patient and a renewal patient each see only the clinics that actually take their kind of visit, matching what Isabella says on the phone. This is the 'booking form must match Isabella by location' fix Mariane asked for. Behind the scenes it reads from the one shared rule list that already drives Isabella, chat, text, and email — so there's now a single source of truth and the surfaces can't drift apart. Also removed the 'providers missing headshot' line from the Launch Readiness checklist (Mariane asked to drop it).

Show technical details

Changed

  • 📍 **Booking form clinic list now matches Isabella by patient class (cmqell76a / Mariane).** /api/locations accepts an optional ?appointmentClass=NEW|RENEWAL and gates the returned clinics through the IDENTICAL getActiveLocations() + getAllowedProvidersAt() helpers in provider-location-rules.ts that drive Isabella's spoken clinic list (voice/chat/sms/email already share this single source of truth). Step3Appointment derives the class from isReturning (declared-new → NEW, declared-returning → RENEWAL, undeclared → unfiltered legacy list) and passes it; the per-class module cache is now keyed by class so a NEW-gated list never leaks to a RENEWAL patient. A clinic with zero providers for the class (e.g. Spokane for renewals, or any clinic after a provider sunset) drops out of the picker exactly as it drops out of Isabella's list, and an already-picked clinic that falls out of the gated set is auto-cleared. Unmapped locations stay visible (rule table governs only known GW clinics). No schema change. [booking][isabella-parity][locations][cmqell76a]

Removed

  • 🧹 **Dropped the 'providers missing headshot' line from Launch Readiness (cmpnh3qve / Mariane).** Removed the noPhoto check + its 'they're hidden from the public providers section' row from the site-wide PreflightWarnings admin banner (rendered on /admin/launch). The per-provider readiness table on /admin/launch is unchanged. [admin][launch][cmpnh3qve]
v2.97.LEADSRC1
2026-06-19Production

Leads now show where each one came from.

What this means for you

Leads now show where each one came from. Leads you add by hand get a purple ✋ Manual pill, and leads brought over from Salesforce will get a blue ⬇ Imported pill — so it's obvious at a glance which rows are migrated vs. brand-new website inquiries (web leads stay unlabeled since they're the bulk of the list). This is the 'tell imported leads apart' tag Mariane asked for; the Imported pill lights up automatically once the Salesforce lead import is run. (Also added a behind-the-scenes setup script that registers the inbound-fax line with RingCentral so faxes start landing in the app — that one's a Doug step.)

Show technical details

Changed

  • 🏷️ **Lead provenance pill on /admin/leads (G6 / Mariane).** Each lead's source= marker (already written into the LEAD_CAPTURED audit row by the route that created it) now renders as a pill: ✋ Manual (violet) for staff-created leads, ⬇ Imported (sky) for salesforce-import rows. Web captures stay unlabeled — they're the default + bulk of the queue, so a pill would be noise. Parsed via parseLeadDetail() in leads-shared.ts (new source field) so client + server read it identically. NOTE: the Salesforce backlog import (scripts/sf-import/import-recent-leads.ts) currently upserts into the separate Lead Prisma table, which /admin/leads does not read — for imported rows to appear here AND carry the pill, the import must also emit source=salesforce-import LEAD_CAPTURED audit rows (or the page must union the Lead table). That wiring rides with the import job itself. [leads][crm][provenance][g6]
  • 📠 **Inbound-fax RingCentral subscription register script (G8).** New scripts/rc-register-fax-webhook.mjs wires the /api/inbound/fax webhook to the RC fax line in one command instead of hand-clicking the RC dev dashboard. Uses the DEDICATED .biz AT&T Office@Hand creds (GW_RC_* + GW_RC_JWT_GW) — separate from the SMS/voice .com script — with the type=Fax event filter + verification token. App end was already verified healthy (fail-closed 403, idempotent); this closes the 'fax not receiving' gap, which was simply that the subscription was never registered. Doug-step: set RC_WEBHOOK_VERIFICATION_TOKEN, run the script, send one test fax to the RC fax DID. [fax][ringcentral][g8][dev-script]
v2.97.ESIGNUX1
2026-06-19Production

Made the patient e-sign experience a lot smoother — this is the New-Patient Packet patients now fill out and sign in the portal.

What this means for you

Made the patient e-sign experience a lot smoother — this is the New-Patient Packet patients now fill out and sign in the portal. Four improvements: (1) the signature box now fits the phone screen properly instead of running off the edge (about 70% of patients sign on their phone, where the old fixed-size box overflowed). (2) Patients now see a live 'Saving… / ✓ Saved' indicator, and if a save ever fails they get a clear warning instead of silently losing what they typed. (3) A progress bar at the bottom shows exactly how many of the required items (3 signatures + 7 initials) are done, with a 'Take me to what's left' button that scrolls straight to the next thing they missed — so nobody gets stuck not knowing why they can't submit. (4) If a submit hiccups, the error is now plain-English ('the connection timed out') instead of a scary code, and reassures them their answers are saved.

Show technical details

Changed

  • ✍️ **Signature pad is now responsive (mobile overflow fixed).** SignaturePad was a fixed 480px wide and spilled off / clipped on phones (~70% of patient traffic is iOS Safari at ~360-390px). It now measures its container and sizes the canvas BUFFER to the available width (capped at 480) so pointer coordinates still map 1:1 — the ink lands exactly where the finger touches at any width. Re-measure freezes once signing starts, so a mid-form rotate never wipes a signature. Benefits all 4 patient forms (packet, ROI, informed-consent, ack). Reviewed clean (coordinate math + legal-capture integrity verified). [patient-forms][e-sign][mobile][a11y]
  • 💾 **Auto-save status + failure surfacing on the New-Patient Packet.** The draft auto-save was fire-and-forget (void fetch) with no confirmation and no error handling — a failed save silently lost typed intake answers. Now shows a live Saving… / ✓ Saved pill and, on failure, ⚠ Couldn't save your last change — check your connection; it'll retry as you keep typing. Net §164.312(c)(1) integrity gain (silent clinical-data loss → visible, retryable). [patient-forms][reliability][hipaa]
  • 📊 **Completion tracker + jump-to-incomplete + plain-English errors.** The long single-scroll packet now has a sticky progress bar (N of 10 required items done = 3 signatures + 7 initials) with a 'Take me to what's left →' button that smooth-scrolls to the first incomplete item (intake sig → consent sig → acknowledgement). Submit button shows the running count when disabled. Submit failures now read 'the connection timed out / a network problem' (never a raw HTTP code) and reassure the patient their answers are saved. [patient-forms][e-sign][ux]
v2.97.PORTALFORMS2
2026-06-19Production

We finished consolidating new-patient intake + consent into one place: the patient portal.

What this means for you

We finished consolidating new-patient intake + consent into one place: the patient portal. Now when a NEW patient books, the system automatically prepares their New-Patient Packet (intake + informed consent, signed digitally) and it shows up in their portal under 'Forms to review & sign' — no separate emailed PDF, no second form to chase. The patient still gets just the one portal-welcome email, then signs everything in the portal. We also removed the old 'Complete your health intake form' link that used to appear separately on each appointment, since it was a second path to the same thing and caused the 'too many forms' confusion Mariane flagged. (The old intake link still works for anyone who already received one.) Renewals are unchanged — no auto-packet for them per Doug's call.

Show technical details

Changed

  • 📋 **New-patient packet auto-prepared in the portal on booking (G7 step 2 — Mariane cmq6203a7, Doug 'new→packet, retire-legacy').** fireAppointmentOnboarding now, for a NEW-patient appointment (appt.isNew), idempotently creates a NEW_PATIENT_PACKET PatientForm (status SENT, 7-day token) so intake + informed consent are waiting in the portal's 'Forms to review & sign' card. **No separate magic-link email** — the existing portal-welcome email is the single touch. Gated behind APPT_AUTO_ONBOARDING_ENABLED (already on). Idempotent (skips if a non-REVOKED/EXPIRED packet exists); PHI-free FORM_CREATED audit (mode=auto-onboarding-portal, no patient name — better than the admin path); system-sentinel createdById. Reviewed clean by hipaa-architect + Explore (the non-atomic find-then-create race is accepted: worst case is a benign duplicate shell, no PHI leak / consent gap). [hipaa][patient-portal][consent][onboarding][g7-step-2]
  • 🧹 **Retired the legacy per-appointment intake nudge.** /my-appointments/[token] no longer shows the separate 'Complete your health intake form → /intake/[token]' amber prompt on each appointment — the single 'Forms to review & sign' card (PORTALFORMS1) is now the one signing surface, removing the duplicate path that caused the 'multiple forms' confusion. The /intake/[token] route itself stays live so any already-issued legacy links keep working. [patient-portal][forms][g7-step-2]
v2.97.PORTALFORMS1
2026-06-19Production

Patients can now see and sign their forms right inside the patient portal.

What this means for you

Patients can now see and sign their forms right inside the patient portal. When a patient opens their appointments page, any consent or intake forms that are waiting for them now show up at the top as a 'Forms to review & sign' card — they tap it, review, and sign digitally, no printing or downloading a PDF from email. This is the first step of consolidating intake + consent into one in-portal flow (Mariane's request to stop the confusing 'multiple forms in different places' experience). It's purely additive — it doesn't change or stop any existing email yet; it just makes the portal the one place to sign. Next step needs your call: which form should auto-generate when a patient books (so the portal always has the right one waiting) and whether to retire the older standalone intake link.

Show technical details

Added

  • ✍️ **In-portal form signing surface (G7 step 1 — Mariane cmq6203a7).** /my-appointments/[token] now renders a 'Forms to review & sign' card listing the patient's pending PatientForm rows (status SENT/OPENED, live token) with a 'Review & sign' link to the existing /patient/forms/[accessToken] e-sign flow. Directly addresses the "seamless digital signing in the portal, not a downloaded PDF" ask. Query scoped to patient.id (the load-bearing fence); accessToken rendering mirrors /patient/portal/forms. Reviewed clean (PHI/auth/XSS). **Additive only** — no email is sent or suppressed by this change. [hipaa][patient-portal][forms][consent][g7-step-1]
v2.97.ADU0001
2026-06-19Production

Staff can now attach medical records directly to a patient's chart and to a specific appointment — for records that come in by fax, email, or in person. Look for the new 'Attach records' button on the patient's Documents tab and in the appointment's Medical Records section; you can select several files at once (PDFs or photos, up to 25 MB each). On the leads side, the document uploader now takes multiple files in one go instead of one at a time. We also fixed a rough edge where a troublesome PDF would fail with a generic error — uploads now give a clear message (e.g. 'it may be corrupt or password-protected') instead of a silent failure. Files are stored on our HIPAA systems and every upload is logged.

Show technical details

Added

  • 📎 **Staff document upload onto patient charts + appointments (ADU0001).** New POST /api/admin/patients/[id]/documents lets ADMIN/MANAGER/SCHEDULER attach a MedicalDocument (uploadedBy="admin", optional appointmentId) for records received by fax/email/in-person — closing the long-standing "Admins can attach files in future" placeholder on the patient DocumentsList and appointment DocumentsPanel. Mirrors the patient-portal + lead-documents pattern exactly: private Vercel Blob (BAA) + compress/EXIF-strip before put() + ADMIN_DOCUMENT_UPLOADED audit with PHI-free detail. appointmentId is ownership-checked against the patient (no cross-patient attach). GET/DELETE legs already live at /api/admin/documents/[id]. Closes reviewer-feedback cmqlrd4pf. [hipaa][phi][documents][admin]
  • 🗂️ **Multi-file upload — lead documents panel + new staff attach.** LeadDocumentsPanel (and the new patient/appointment attach controls) now accept several files in one pick and upload them sequentially, respecting the server's 10-doc-per-lead cap (413 stops the batch) with a clear "N uploaded, then …" message on partial failure. Closes reviewer-feedback cmqlqmzu. [documents][ux]

Fixed

  • 🩹 **PDF upload "there's an error" hardened across all three upload routes.** compressPatientUpload was called unguarded in the lead-documents, patient-portal-records, and (new) admin routes — a decode/compression throw on a corrupt or password-protected PDF (or a sharp image-decode failure) bubbled to an unhandled 500 that surfaced as a generic "there's an error." Now wrapped: returns a clean 422 with an actionable message ("it may be corrupt or password-protected — try re-saving/exporting it") and logs only the error name (no PHI). Closes reviewer-feedback cmqlrabth. [documents][reliability][hipaa]
v2.97.CHARTFIX2
2026-06-19Production

Fixed the root cause behind the provider chart crashes (the ones that bounced doctors back to Practice Fusion).

What this means for you

Fixed the root cause behind the provider chart crashes (the ones that bounced doctors back to Practice Fusion). One small database query on the patient chart's context rail was written in a way the database engine could choke on and crash the whole chart — and it crashed in a way our safety net couldn't catch. Rewrote it as two simple, safe queries that do the same thing (show the patient's latest allergy note) without the fragility. The provider chart canary watches this every 30 minutes, so we'll know immediately if anything regresses.

Show technical details

Fixed

  • 🩺 **Provider chart crash root cause (ARI0003/ARI0004 class) eliminated.** PriorContextRail (embedded on every encounter chart) loaded the latest allergy note with a Prisma nested-relation query — db.intakeForm.findFirst({ where: { appointment: { patientId } }, orderBy: { appointment: { startsAt } } }). Prisma 7.8 can throw on that shape **synchronously**, before a promise exists, so the per-query .catch() never runs and the throw escapes to crash the whole chart (doctors → Practice Fusion). Prior fixes (CHARTFIX1/ARI0004) wrapped the rail in a resilient boundary — a backstop, not a cure. This replaces the query with a flat two-step using only scalar where/orderBy (patient's appointments newest-first → their intake rows by appointmentId in → pick the newest), which Prisma can't choke on. Behavior identical (newest appointment's allergies); reviewed clean against schema. Provider-chart-canary continues to self-validate every 30 min. [provider-portal][chart][prisma][reliability][hipaa]
v2.97.RENEWALMIRROR1
2026-06-19Production

Isabella now handles renewals the same way on chat, text, and email as she does on the phone.

What this means for you

Isabella now handles renewals the same way on chat, text, and email as she does on the phone. If a returning patient is renewing and can get us their records by their appointment, she books them instead of calling it a tentative request stuck behind a records review — and she points them to upload their records right in their patient portal (the new upload box). New-patient messages are unchanged: still a tentative request until the team reviews records. This finishes mirroring the phone behavior across every channel.

Show technical details

Changed

  • 💬 **Renewal booking + portal records rail mirrored to chat / SMS / email.** The phone change (RENEWALBOOK1) now applies on every channel: each AI prompt (chat route.ts, sms-ai.ts, email-ai.ts) branches the booking-confirmation language on patient type. A RETURNING patient renewing who can get records in by the appointment is booked (office confirms exact time) with no records-review gate; the NEW-patient path keeps the tentative-request framing AND its pinned phrases ("tentative appointment request" / "provider must review" — still enforced by the channel-parity tests). All three now point patients to upload records in the patient portal (greenwellness.org → "Your records", the PORTALUPLOAD1 box) as the easy path, with fax/email as fallback. [isabella][chat][sms][email][records]
v2.97.PORTALUPLOAD1
2026-06-19Production

Patients can now upload their medical records right in their patient portal.

What this means for you

Patients can now upload their medical records right in their patient portal. Until now the portal could only SHOW records already on file; there was no way to add one without the emailed secure link. Now there's an upload box on the patient's “Your records” page (sign-in required), so a returning/renewal patient can send their records straight from the portal — which is exactly what Isabella now points them to. We also tightened how uploaded files are shown: any file type that could carry hidden code (like an SVG) now downloads instead of opening in the browser, on both the patient side and the staff/provider side.

Show technical details

Added

  • 📤 **Patient-portal records upload.** New session-authenticated endpoint /api/patient/records/upload + an upload box on /patient/portal/uploaded-records. A signed-in patient uploads a record → it stores to private Vercel Blob (BAA tenant), EXIF-stripped + compressed (same compressPatientUpload pipeline as the emailed-link path), writes a MedicalDocument row scoped to session.patientId only (no IDOR), and shows in their on-file list. 10 MB cap, per-patient fail-closed rate limit, orphan-cleanup on DB failure, PHI-free audit (PATIENT_PORTAL_DOCUMENT_UPLOADED). Reviewed by hipaa-architect (compliant) + security-auditor. This is the path Isabella points renewals to. [patient-portal][records][hipaa]

Fixed

  • 🛡️ **Stored-XSS hardening on document viewers (security-review finding).** Uploaded records are served by 4 routes (patient /api/patient/documents/[id], provider /api/provider/documents/[id], admin /api/admin/documents/[id], and the records-review source viewer). They served the stored MIME inline, so a malicious SVG (image/svg+xml) with an embedded