Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
3402ffa
Add minimal GCP HIPAA web hosting and deploy automation
sammargolis Mar 13, 2026
6ddc2c5
Implement HIPAA hosted auth, API hardening, and GCP web+whisper deplo…
sammargolis Mar 13, 2026
4fbb5d6
Add Redis session state and DB migrations for HIPAA production reliab…
sammargolis Mar 13, 2026
61c6b3a
Harden HIPAA GCP deploy path for proxy, VPC, and build-time key
sammargolis Mar 13, 2026
6b5b1b0
Handle socket-style DATABASE_URL in proxy migrations
sammargolis Mar 13, 2026
f4ced35
Stabilize migration URL fallback for socket DSNs
sammargolis Mar 13, 2026
943bf00
Avoid Cloud Build log-stream permission failures
sammargolis Mar 13, 2026
2487ffd
Use explicit Cloud Build config for whisper image
sammargolis Mar 13, 2026
defedcc
Provide build-time DATABASE_URL placeholder for Next build
sammargolis Mar 13, 2026
e62deed
Use dedicated Cloud SQL proxy port for migrations
sammargolis Mar 13, 2026
f929177
Adjust web max instances for Cloud Run CPU quota
sammargolis Mar 13, 2026
60bf31a
Enable corepack in runtime image for pnpm start
sammargolis Mar 13, 2026
49b238a
Install pnpm globally in runtime image
sammargolis Mar 13, 2026
d8a50a0
Compile frontend in HIPAA hosted mode
sammargolis Mar 13, 2026
a004855
Require NEXTAUTH_URL in HIPAA deploy paths
sammargolis Mar 13, 2026
0918411
Add OpenEMR integration design spec
sammargolis Mar 17, 2026
4371c37
Update OpenEMR integration spec with review fixes
sammargolis Mar 17, 2026
1a2ad14
Add OpenEMR integration implementation plan
sammargolis Mar 17, 2026
39747a5
Update OpenEMR plan: add fetch timeout + payload structure test
sammargolis Mar 17, 2026
f7441ee
refactor(plan): rewrite for strict TDD with trust boundary tests and …
sammargolis Mar 17, 2026
411971a
feat(openemr): add config docs and extend test tsconfig
sammargolis Mar 17, 2026
f73846a
feat(openemr): FHIR client with 12 trust boundary tests
sammargolis Mar 17, 2026
4db2441
feat(openemr): push route handler with 8 trust boundary tests
sammargolis Mar 17, 2026
836ec31
feat(openemr): require patient_id in encounter form when OpenEMR enabled
sammargolis Mar 17, 2026
7f8ef46
feat(openemr): add Push to OpenEMR button to note editor
sammargolis Mar 17, 2026
ed808c5
Update auth imports for HIPAA
sammargolis Mar 17, 2026
85c51a1
Merge branch 'feat/openemr-fhir-client' into codex/openemr-integration
sammargolis Mar 17, 2026
cea894a
Merge branch 'feat/openemr-push-route' into codex/openemr-integration
sammargolis Mar 17, 2026
ce0eb8a
Merge branch 'feat/openemr-encounter-form' into codex/openemr-integra…
sammargolis Mar 17, 2026
3faa023
Merge branch 'feat/openemr-note-editor-button' into codex/openemr-int…
sammargolis Mar 17, 2026
9f5aee2
fix(openemr): JWT client assertion auth, pid→UUID resolution, standar…
sammargolis Mar 17, 2026
2c25331
Improve OpenEMR push reliability, auth setup UX, and optional EMR enr…
sammargolis Mar 22, 2026
0ff4f7c
Fix CI gates: structure, gitleaks, and high-severity audit
sammargolis Mar 22, 2026
10cf851
Fix lint failures in OpenEMR/auth files and scripts parser config
sammargolis Mar 22, 2026
7f68036
Fix encounters test runtime setup for CI test runner
sammargolis Mar 22, 2026
75f7e4a
Fix test:llm compiled test path
sammargolis Mar 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
279 changes: 279 additions & 0 deletions .github/workflows/deploy-web-gcp-hipaa.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
name: Deploy Web (GCP HIPAA)

on:
push:
branches:
- codex/prod-hipaa
workflow_dispatch:

permissions:
contents: read
id-token: write

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile

- uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
service_account: ${{ secrets.GCP_DEPLOY_SERVICE_ACCOUNT }}

- uses: google-github-actions/setup-gcloud@v2
with:
project_id: ${{ secrets.GCP_PROJECT_ID }}

- name: Validate required configuration
env:
GCP_REGION: ${{ secrets.GCP_REGION }}
GCP_ARTIFACT_REPO: ${{ secrets.GCP_ARTIFACT_REPO }}
GCP_CLOUD_RUN_SERVICE: ${{ secrets.GCP_CLOUD_RUN_SERVICE }}
GCP_RUNTIME_SERVICE_ACCOUNT: ${{ secrets.GCP_RUNTIME_SERVICE_ACCOUNT }}
GCP_WHISPER_CLOUD_RUN_SERVICE: ${{ secrets.GCP_WHISPER_CLOUD_RUN_SERVICE }}
GCP_WHISPER_RUNTIME_SERVICE_ACCOUNT: ${{ secrets.GCP_WHISPER_RUNTIME_SERVICE_ACCOUNT }}
GCP_VPC_CONNECTOR: ${{ secrets.GCP_VPC_CONNECTOR }}
GCP_CLOUD_SQL_INSTANCE: ${{ secrets.GCP_CLOUD_SQL_INSTANCE }}
GCP_NEXTAUTH_URL: ${{ secrets.GCP_NEXTAUTH_URL }}
NEXT_PUBLIC_SECURE_STORAGE_KEY: ${{ secrets.NEXT_PUBLIC_SECURE_STORAGE_KEY }}
run: |
for key in GCP_REGION GCP_ARTIFACT_REPO GCP_CLOUD_RUN_SERVICE GCP_RUNTIME_SERVICE_ACCOUNT GCP_WHISPER_CLOUD_RUN_SERVICE GCP_WHISPER_RUNTIME_SERVICE_ACCOUNT GCP_VPC_CONNECTOR GCP_CLOUD_SQL_INSTANCE GCP_NEXTAUTH_URL NEXT_PUBLIC_SECURE_STORAGE_KEY; do
if [ -z "${!key}" ]; then
echo "Missing required secret: $key"
exit 1
fi
done

- name: Enable required APIs
run: |
gcloud services enable \
run.googleapis.com \
cloudbuild.googleapis.com \
artifactregistry.googleapis.com \
secretmanager.googleapis.com \
logging.googleapis.com \
sqladmin.googleapis.com

- name: Ensure required Secret Manager secrets exist
run: |
for name in ANTHROPIC_API_KEY AUTH_SECRET GOOGLE_CLIENT_ID GOOGLE_CLIENT_SECRET DATABASE_URL REDIS_URL; do
gcloud secrets describe "$name" >/dev/null
done

- name: Run DB migrations via Cloud SQL Proxy
env:
CLOUD_SQL_INSTANCE: ${{ secrets.GCP_CLOUD_SQL_INSTANCE }}
run: |
DATABASE_URL_RAW="$(gcloud secrets versions access latest --secret DATABASE_URL | tr -d '\n')"
DATABASE_URL_PROXY="$(DATABASE_URL_RAW="$DATABASE_URL_RAW" node -e '
const raw = process.env.DATABASE_URL_RAW?.trim()
if (!raw) process.exit(1)
try {
const url = new URL(raw)
url.hostname = "127.0.0.1"
url.port = "55432"
url.searchParams.delete("sslmode")
process.stdout.write(url.toString())
} catch {
const separator = "@/";
const index = raw.indexOf(separator)
if (index <= 0) process.exit(1)
const prefix = raw.slice(0, index)
const remainder = raw.slice(index + separator.length)
const db = remainder.split("?")[0]
if (!db) process.exit(1)
process.stdout.write(`${prefix}@127.0.0.1:55432/${db}`)
}
')"

curl -fsSL -o cloud-sql-proxy https://storage.googleapis.com/cloud-sql-connectors/cloud-sql-proxy/v2.18.3/cloud-sql-proxy.linux.amd64
chmod +x cloud-sql-proxy
./cloud-sql-proxy "$CLOUD_SQL_INSTANCE" --port 55432 >/tmp/cloud-sql-proxy.log 2>&1 &
PROXY_PID=$!
trap 'kill "$PROXY_PID"' EXIT
sleep 5

DATABASE_URL="$DATABASE_URL_PROXY" pnpm db:migrate

- name: Ensure Artifact Registry repo
env:
REGION: ${{ secrets.GCP_REGION }}
REPO: ${{ secrets.GCP_ARTIFACT_REPO }}
run: |
gcloud artifacts repositories describe "$REPO" --location="$REGION" >/dev/null 2>&1 || \
gcloud artifacts repositories create "$REPO" \
--repository-format=docker \
--location="$REGION" \
--description="OpenScribe production images"

- name: Build container images
id: image
env:
PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}
REGION: ${{ secrets.GCP_REGION }}
REPO: ${{ secrets.GCP_ARTIFACT_REPO }}
NEXT_PUBLIC_SECURE_STORAGE_KEY: ${{ secrets.NEXT_PUBLIC_SECURE_STORAGE_KEY }}
run: |
WEB_IMAGE_URI="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO}/openscribe-web:${GITHUB_SHA::12}"
WHISPER_IMAGE_URI="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO}/openscribe-whisper:${GITHUB_SHA::12}"

cat >/tmp/cloudbuild-web.yaml <<'YAML'
steps:
- name: gcr.io/cloud-builders/docker
args:
- build
- -t
- ${_WEB_IMAGE_URI}
- -f
- docker/web-cloudrun.Dockerfile
- --build-arg
- NEXT_PUBLIC_SECURE_STORAGE_KEY=${_NEXT_PUBLIC_SECURE_STORAGE_KEY}
- --build-arg
- NEXT_PUBLIC_HIPAA_HOSTED_MODE=${_NEXT_PUBLIC_HIPAA_HOSTED_MODE}
- --build-arg
- DATABASE_URL=${_DATABASE_URL_BUILD}
- .
images:
- ${_WEB_IMAGE_URI}
YAML

gcloud builds submit \
--config /tmp/cloudbuild-web.yaml \
--substitutions "_WEB_IMAGE_URI=${WEB_IMAGE_URI},_NEXT_PUBLIC_SECURE_STORAGE_KEY=${NEXT_PUBLIC_SECURE_STORAGE_KEY},_NEXT_PUBLIC_HIPAA_HOSTED_MODE=true,_DATABASE_URL_BUILD=postgresql://placeholder:placeholder@127.0.0.1:5432/placeholder" \
--async \
--format='value(id)' > /tmp/web_build_id.txt
WEB_BUILD_ID="$(tr -d '\n' </tmp/web_build_id.txt)"

cat >/tmp/cloudbuild-whisper.yaml <<'YAML'
steps:
- name: gcr.io/cloud-builders/docker
args:
- build
- -t
- ${_WHISPER_IMAGE_URI}
- -f
- docker/whisper-cloudrun.Dockerfile
- .
images:
- ${_WHISPER_IMAGE_URI}
YAML

gcloud builds submit \
--config /tmp/cloudbuild-whisper.yaml \
--substitutions "_WHISPER_IMAGE_URI=${WHISPER_IMAGE_URI}" \
--async \
--format='value(id)' > /tmp/whisper_build_id.txt
WHISPER_BUILD_ID="$(tr -d '\n' </tmp/whisper_build_id.txt)"

wait_for_build() {
local build_id="$1"
while true; do
status="$(gcloud builds describe "$build_id" --format='value(status)')"
case "$status" in
SUCCESS) return 0 ;;
FAILURE|INTERNAL_ERROR|TIMEOUT|CANCELLED|EXPIRED)
gcloud builds describe "$build_id"
return 1
;;
*)
sleep 10
;;
esac
done
}

wait_for_build "$WEB_BUILD_ID"
wait_for_build "$WHISPER_BUILD_ID"
echo "web_image_uri=$WEB_IMAGE_URI" >> "$GITHUB_OUTPUT"
echo "whisper_image_uri=$WHISPER_IMAGE_URI" >> "$GITHUB_OUTPUT"

- name: Deploy Whisper Cloud Run (private)
env:
REGION: ${{ secrets.GCP_REGION }}
SERVICE_NAME: ${{ secrets.GCP_WHISPER_CLOUD_RUN_SERVICE }}
RUNTIME_SA: ${{ secrets.GCP_WHISPER_RUNTIME_SERVICE_ACCOUNT }}
IMAGE_URI: ${{ steps.image.outputs.whisper_image_uri }}
run: |
gcloud run deploy "$SERVICE_NAME" \
--image "$IMAGE_URI" \
--region "$REGION" \
--platform managed \
--port 8081 \
--service-account "$RUNTIME_SA" \
--ingress all \
--execution-environment gen2 \
--cpu 4 \
--memory 8Gi \
--min-instances 1 \
--max-instances 4 \
--set-env-vars "WHISPER_LOCAL_MODEL=tiny.en,WHISPER_LOCAL_BACKEND=cpp,WHISPER_LOCAL_GPU=1" \
--no-allow-unauthenticated

- name: Grant web runtime invoke access to whisper
env:
REGION: ${{ secrets.GCP_REGION }}
WHISPER_SERVICE: ${{ secrets.GCP_WHISPER_CLOUD_RUN_SERVICE }}
WEB_RUNTIME_SA: ${{ secrets.GCP_RUNTIME_SERVICE_ACCOUNT }}
run: |
gcloud run services add-iam-policy-binding "$WHISPER_SERVICE" \
--region "$REGION" \
--member "serviceAccount:${WEB_RUNTIME_SA}" \
--role roles/run.invoker

- name: Resolve whisper URL
id: whisper
env:
REGION: ${{ secrets.GCP_REGION }}
SERVICE_NAME: ${{ secrets.GCP_WHISPER_CLOUD_RUN_SERVICE }}
run: |
URL="$(gcloud run services describe "$SERVICE_NAME" --region "$REGION" --format='value(status.url)')"
echo "url=$URL" >> "$GITHUB_OUTPUT"

- name: Deploy Web Cloud Run
env:
REGION: ${{ secrets.GCP_REGION }}
SERVICE_NAME: ${{ secrets.GCP_CLOUD_RUN_SERVICE }}
RUNTIME_SA: ${{ secrets.GCP_RUNTIME_SERVICE_ACCOUNT }}
IMAGE_URI: ${{ steps.image.outputs.web_image_uri }}
WHISPER_URL: ${{ steps.whisper.outputs.url }}
VPC_CONNECTOR: ${{ secrets.GCP_VPC_CONNECTOR }}
CLOUD_SQL_INSTANCE: ${{ secrets.GCP_CLOUD_SQL_INSTANCE }}
NEXTAUTH_URL: ${{ secrets.GCP_NEXTAUTH_URL }}
run: |
gcloud run deploy "$SERVICE_NAME" \
--image "$IMAGE_URI" \
--region "$REGION" \
--platform managed \
--port 8080 \
--service-account "$RUNTIME_SA" \
--ingress all \
--execution-environment gen2 \
--cpu 1 \
--memory 2Gi \
--min-instances 1 \
--max-instances 20 \
--vpc-connector "$VPC_CONNECTOR" \
--vpc-egress private-ranges-only \
--add-cloudsql-instances "$CLOUD_SQL_INSTANCE" \
--set-env-vars "NODE_ENV=production,HIPAA_HOSTED_MODE=true,NEXT_PUBLIC_HIPAA_HOSTED_MODE=true,NEXTAUTH_URL=${NEXTAUTH_URL},TRANSCRIPTION_PROVIDER=whisper_local,WHISPER_LOCAL_URL=${WHISPER_URL}/v1/audio/transcriptions,WHISPER_LOCAL_AUTH_TYPE=identity_token" \
--set-secrets "ANTHROPIC_API_KEY=ANTHROPIC_API_KEY:latest,AUTH_SECRET=AUTH_SECRET:latest,GOOGLE_CLIENT_ID=GOOGLE_CLIENT_ID:latest,GOOGLE_CLIENT_SECRET=GOOGLE_CLIENT_SECRET:latest,DATABASE_URL=DATABASE_URL:latest,REDIS_URL=REDIS_URL:latest" \
--allow-unauthenticated

- name: Print service URLs
env:
REGION: ${{ secrets.GCP_REGION }}
WEB_SERVICE: ${{ secrets.GCP_CLOUD_RUN_SERVICE }}
WHISPER_SERVICE: ${{ secrets.GCP_WHISPER_CLOUD_RUN_SERVICE }}
run: |
gcloud run services describe "$WEB_SERVICE" --region "$REGION" --format='value(status.url)'
gcloud run services describe "$WHISPER_SERVICE" --region "$REGION" --format='value(status.url)'
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ yarn-error.log*
local-only/openscribe-backend/ollama_startup_report.json
local-only/openscribe-backend/recorder_state.json
local-only/openscribe-backend/config.json
local-only/openemr-rsa384-private.key
3 changes: 3 additions & 0 deletions .gitleaksignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ed808c5fe7c9ae3130721df4af0a156f2edbd037:GCP_DEPLOY_PLAN.md:generic-api-key:332
ed808c5fe7c9ae3130721df4af0a156f2edbd037:GCP_DEPLOY_PLAN.md:anthropic-api-key:292
ed808c5fe7c9ae3130721df4af0a156f2edbd037:scripts/ensure-openemr-env.sh:generic-api-key:10
2 changes: 2 additions & 0 deletions .playwright-cli/console-2026-03-13T18-03-05-152Z.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[ 734ms] [ERROR] Failed to load resource: the server responded with a status of 401 () @ https://openscribe-web-c7tayewpaq-uc.a.run.app/api/settings/mixed-auth-status:0
[ 1255ms] [WARNING] Microphone permission request failed NotAllowedError: Permission denied @ https://openscribe-web-c7tayewpaq-uc.a.run.app/_next/static/chunks/app/page-c3b10d1fde682ec1.js:0
2 changes: 2 additions & 0 deletions .playwright-cli/console-2026-03-13T18-34-09-601Z.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[ 7298ms] [ERROR] Failed to load resource: net::ERR_CERT_AUTHORITY_INVALID @ https://play.google.com/log?format=json&hasfast=true&authuser=0:0
[ 17487ms] [ERROR] Failed to load resource: net::ERR_CERT_AUTHORITY_INVALID @ https://play.google.com/log?format=json&hasfast=true&authuser=0:0
3 changes: 3 additions & 0 deletions .playwright-cli/console-2026-03-13T18-34-47-030Z.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[ 11048ms] [ERROR] Failed to load resource: net::ERR_CERT_AUTHORITY_INVALID @ https://play.google.com/log?format=json&hasfast=true&authuser=0:0
[ 20782ms] [ERROR] Failed to load resource: net::ERR_CERT_AUTHORITY_INVALID @ https://play.google.com/log?format=json&hasfast=true&authuser=0:0
[ 40856ms] [ERROR] Failed to load resource: net::ERR_CERT_AUTHORITY_INVALID @ https://play.google.com/log?hasfast=true&authuser=0&format=json:0
3 changes: 3 additions & 0 deletions .playwright-cli/console-2026-03-13T18-35-38-378Z.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[ 4487ms] [ERROR] Failed to load resource: net::ERR_CERT_AUTHORITY_INVALID @ https://play.google.com/log?format=json&hasfast=true&authuser=0:0
[ 17080ms] [ERROR] Failed to load resource: net::ERR_CERT_AUTHORITY_INVALID @ https://play.google.com/log?format=json&hasfast=true&authuser=0:0
[ 34463ms] [ERROR] Failed to load resource: net::ERR_CERT_AUTHORITY_INVALID @ https://play.google.com/log?hasfast=true&authuser=0&format=json:0
6 changes: 6 additions & 0 deletions .playwright-cli/console-2026-03-13T18-54-55-004Z.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[ 11842ms] [ERROR] Failed to load resource: net::ERR_CERT_AUTHORITY_INVALID @ https://play.google.com/log?hasfast=true&authuser=0&format=json:0
[ 12053ms] [ERROR] Failed to load resource: net::ERR_CERT_AUTHORITY_INVALID @ https://play.google.com/log?hasfast=true&authuser=0&format=json:0
[ 21549ms] [ERROR] Failed to load resource: net::ERR_CERT_AUTHORITY_INVALID @ https://play.google.com/log?format=json&hasfast=true&authuser=0:0
[ 41915ms] [ERROR] Failed to load resource: net::ERR_CERT_AUTHORITY_INVALID @ https://play.google.com/log?hasfast=true&authuser=0&format=json:0
[ 58498ms] [ERROR] Failed to load resource: net::ERR_CERT_AUTHORITY_INVALID @ https://play.google.com/log?hasfast=true&authuser=0&format=json:0
[ 62861ms] [ERROR] Failed to load resource: net::ERR_CERT_AUTHORITY_INVALID @ https://play.google.com/log?hasfast=true&authuser=0&format=json:0
34 changes: 34 additions & 0 deletions .playwright-cli/page-2026-03-13T18-03-05-876Z.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
- generic [active] [ref=e1]:
- generic [ref=e3]:
- generic [ref=e4]:
- generic [ref=e5]:
- button "New Encounter" [ref=e7]:
- img
- text: New Encounter
- generic [ref=e8]:
- heading "Encounters" [level=2] [ref=e9]
- generic [ref=e10]:
- img [ref=e11]
- textbox "Search..." [ref=e14]
- generic [ref=e18]:
- img [ref=e19]
- paragraph [ref=e22]: No encounters yet
- generic [ref=e23]:
- paragraph [ref=e24]: Models
- generic [ref=e25]:
- generic [ref=e26]:
- img [ref=e27]
- generic [ref=e30]: Whisper (Local)
- generic [ref=e31]:
- img [ref=e32]
- generic [ref=e34]: Claude (Cloud)
- button "Settings" [ref=e36]:
- img
- generic [ref=e37]: Settings
- main [ref=e38]:
- generic [ref=e39]:
- button [ref=e40]:
- img [ref=e41]
- heading "Start a new interview" [level=2] [ref=e44]
- paragraph [ref=e45]: Record, transcribe, and generate clinical notes automatically.
- alert [ref=e46]
41 changes: 41 additions & 0 deletions .playwright-cli/page-2026-03-13T18-03-08-201Z.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
- generic [active] [ref=e1]:
- generic [ref=e2]:
- generic [ref=e48]:
- heading "Anthropic Key Required for Mixed Mode" [level=3] [ref=e49]
- paragraph [ref=e50]: Mixed mode uses Claude for note generation. Add your Anthropic key in Settings, or switch to local-only mode.
- generic [ref=e51]:
- button "Add Key in Settings" [ref=e52]
- button "Switch to Local-only" [disabled] [ref=e53]
- generic [ref=e3]:
- generic [ref=e4]:
- generic [ref=e5]:
- button "New Encounter" [ref=e7]:
- img
- text: New Encounter
- generic [ref=e8]:
- heading "Encounters" [level=2] [ref=e9]
- generic [ref=e10]:
- img [ref=e11]
- textbox "Search..." [ref=e14]
- generic [ref=e18]:
- img [ref=e19]
- paragraph [ref=e22]: No encounters yet
- generic [ref=e23]:
- paragraph [ref=e24]: Models
- generic [ref=e25]:
- generic [ref=e26]:
- img [ref=e27]
- generic [ref=e30]: Whisper (Local)
- generic [ref=e31]:
- img [ref=e32]
- generic [ref=e34]: Claude (Cloud)
- button "Settings" [ref=e36]:
- img
- generic [ref=e37]: Settings
- main [ref=e38]:
- generic [ref=e39]:
- button [ref=e40]:
- img [ref=e41]
- heading "Start a new interview" [level=2] [ref=e44]
- paragraph [ref=e45]: Record, transcribe, and generate clinical notes automatically.
- alert [ref=e46]
3 changes: 3 additions & 0 deletions .playwright-cli/page-2026-03-13T18-31-09-178Z.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- generic [active] [ref=e1]:
- generic [ref=e2]: Loading session...
- alert [ref=e3]
Loading
Loading