diff --git a/.gitattributes b/.gitattributes index 4f1f5d9f7..b1c70d508 100644 --- a/.gitattributes +++ b/.gitattributes @@ -14,7 +14,7 @@ *.json text eol=crlf # Generated API client files - auto normalize -apps/ui/src/lib/api-client/**/*.ts text eol=crlf +frontend/ui/src/lib/api-client/**/*.ts text eol=crlf # Markdown files *.md text eol=crlf diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 3e1b9a8a8..d6839c047 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -33,7 +33,7 @@ The backend enriches introspection responses with SMART launch context from the | `.github/workflows/smart-compliance-tests.yml` | CI env vars, service containers, test orchestration | | `.github/scripts/inferno-oauth-automation.js` | Playwright-based OAuth flow for Inferno tests | | `backend/src/routes/auth/oauth.ts` | Token proxy, introspection, authorize redirect | -| `deploy/fhir-seed-bundle.json` | FHIR transaction bundle for test data | +| `scripts/seed/fhir-seed-bundle.json` | FHIR transaction bundle for test data | | `docker-compose.development.yml` | Local dev stack config | ## Keycloak Realm Import Gotchas @@ -59,7 +59,7 @@ These values MUST match across files: ## FHIR Seed Bundle Rules -The file `deploy/fhir-seed-bundle.json` is a FHIR transaction bundle: +The file `scripts/seed/fhir-seed-bundle.json` is a FHIR transaction bundle: - All entries use `PUT` method (idempotent upsert) - **`fullUrl` MUST match `request.url`** for PUT entries (e.g., `"fullUrl": "Patient/test-patient"`) @@ -72,7 +72,7 @@ The file `deploy/fhir-seed-bundle.json` is a FHIR transaction bundle: |----------------|-----------|-------------| | `"Invalid username or password"` | realm-export password โ‰  test config password | `keycloak/realm-export.json` credentials + `testing/*/inferno-config.json` | | Introspection `401 Authentication failed` | admin-service client secret mismatch | `keycloak/realm-export.json` client secret + CI workflow env | -| FHIR seed `400` | `fullUrl` uses `urn:uuid:` with PUT method | `deploy/fhir-seed-bundle.json` | +| FHIR seed `400` | `fullUrl` uses `urn:uuid:` with PUT method | `scripts/seed/fhir-seed-bundle.json` | | Token exchange `400` | redirect_uri or client_id mismatch | `keycloak/realm-export.json` redirectUris + `inferno-config.json` | | `"Account is not fully set up"` | Keycloak requires user action (e.g., email verify) | `keycloak/realm-export.json` user `requiredActions` must be empty | | CORS errors on token endpoint | webOrigins missing for client | `keycloak/realm-export.json` client `webOrigins` | diff --git a/.github/instructions/instructions.md b/.github/instructions/instructions.md index 477da7eef..dc856fe1f 100644 --- a/.github/instructions/instructions.md +++ b/.github/instructions/instructions.md @@ -1,4 +1,4 @@ -ALWAYS use generated client apis to call the backend REST APIs. Dont forget to regenerate if necessary +ALWAYS use generated client apis to call the backend REST APIs. Dont forget to regenerate when backend code changed. Never write fetch functions in the frontend, always generate from openapi backend specs! @@ -8,4 +8,6 @@ follow the concept of DRY! and use existing code patterns mind that we use ui\src\i18n\translations -if bun causes issues, use bun install --force \ No newline at end of file +if bun causes issues, use bun install --force + +before push, ALWAYS lint and build. \ No newline at end of file diff --git a/.github/workflows/ai-build-test-frontend.yml b/.github/workflows/ai-build-test-frontend.yml index cb598883f..c067d4456 100644 --- a/.github/workflows/ai-build-test-frontend.yml +++ b/.github/workflows/ai-build-test-frontend.yml @@ -70,7 +70,7 @@ jobs: pip install --break-system-packages openapi-ts-fetch==0.2.2 bun install cd backend && bun install && cd .. - cd apps/ui && bun install && cd ../.. + cd frontend/ui && bun install && cd ../.. bun run generate:ui:normalized git config --global user.name 'github-actions[bot]' @@ -89,7 +89,7 @@ jobs: TAILWIND_DISABLE_LIGHTNINGCSS: "1" run: | echo "=== Building Frontend ===" - cd apps/ui + cd frontend/ui bun install bun run build 2>&1 | tee ../../frontend-build.log @@ -103,7 +103,7 @@ jobs: component: frontend app-id: ${{ secrets.APP_ID }} app-private-key: ${{ secrets.APP_PRIVATE_KEY }} - retry-command: 'cd apps/ui && bun run build' + retry-command: 'cd frontend/ui && bun run build' - name: Fail if Frontend Build Unresolved if: ${{ steps.frontend-build.outcome == 'failure' && steps.build-heal.outputs.fixed != 'true' }} @@ -122,7 +122,7 @@ jobs: CI: true run: | echo "=== Running Frontend Tests ===" - cd apps/ui + cd frontend/ui bun run test --run 2>&1 | tee ../../frontend-test.log - name: ๐Ÿค– Copilot Self-Heal (Frontend Tests) @@ -135,7 +135,7 @@ jobs: component: frontend app-id: ${{ secrets.APP_ID }} app-private-key: ${{ secrets.APP_PRIVATE_KEY }} - retry-command: 'cd apps/ui && bun run test --run' + retry-command: 'cd frontend/ui && bun run test --run' - name: Fail if Frontend Tests Unresolved if: ${{ inputs.run_tests && steps.test-frontend.outcome == 'failure' && steps.test-heal.outputs.fixed != 'true' && inputs.fail_on_test_failure }} diff --git a/.github/workflows/ai-test-builder.yml b/.github/workflows/ai-test-builder.yml index 91be581b9..3028f3329 100644 --- a/.github/workflows/ai-test-builder.yml +++ b/.github/workflows/ai-test-builder.yml @@ -74,7 +74,7 @@ jobs: - name: Install Frontend Dependencies run: | echo "๐Ÿ“ฆ Installing frontend dependencies..." - cd apps/ui + cd frontend/ui bun install # === FRONTEND TESTING & COVERAGE === @@ -84,7 +84,7 @@ jobs: continue-on-error: true run: | echo "๐Ÿงช Running frontend tests with coverage..." - cd apps/ui + cd frontend/ui # Run tests with coverage bun run test:coverage --reporter=verbose --reporter=json --outputFile=test-results.json 2>&1 | tee test-output.log @@ -105,7 +105,7 @@ jobs: id: analyze-frontend run: | echo "๐Ÿ” Analyzing frontend test results..." - cd apps/ui + cd frontend/ui # Check if tests failed (exit code OR output contains failures) test_exit_code="${{ steps.frontend-test-coverage.outputs.frontend_test_exit_code }}" @@ -938,7 +938,7 @@ jobs: echo " โ€ข New tests: ${{ steps.generate-frontend-tests.outputs.frontend_tests_added || 0 }}" echo "" echo "๐Ÿ“Š Running frontend test suite with coverage..." - cd apps/ui + cd frontend/ui bun run test:coverage --reporter=verbose 2>&1 | tee ../scripts/frontend-retest.log frontend_retest_exit=$? echo "frontend_retest_exit_code=$frontend_retest_exit" >> $GITHUB_OUTPUT diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 881d74150..4d1c1223d 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -74,14 +74,6 @@ jobs: pip install --break-system-packages openapi-ts-fetch==0.2.2 bun run generate:ui - - name: Build Patient Portal - env: - TAILWIND_DISABLE_LIGHTNINGCSS: "1" - run: | - echo "๐Ÿ”จ Building patient portal..." - cd apps/patient-portal - bun run build - # === FRONTEND BUILD === - name: Build Frontend env: @@ -89,7 +81,7 @@ jobs: VITE_ENCRYPTION_SECRET: ${{ secrets.VITE_ENCRYPTION_SECRET }} run: | echo "๐Ÿ”จ Building frontend..." - cd apps/ui + cd frontend/ui bun run build # === BACKEND TESTS === @@ -107,7 +99,7 @@ jobs: TAILWIND_DISABLE_LIGHTNINGCSS: "1" run: | echo "๐Ÿงช Testing frontend..." - cd apps/ui + cd frontend/ui bun run test --run - name: Upload Build Artifacts @@ -117,5 +109,5 @@ jobs: name: build-artifacts path: | backend/dist - apps/ui/dist + frontend/ui/dist retention-days: 7 diff --git a/.github/workflows/copilot-auto-fix.yml b/.github/workflows/copilot-auto-fix.yml index aa8e20911..9866fb0ce 100644 --- a/.github/workflows/copilot-auto-fix.yml +++ b/.github/workflows/copilot-auto-fix.yml @@ -92,7 +92,7 @@ jobs: if: env.TARGET == 'all' || env.TARGET == 'frontend' continue-on-error: true run: | - cd apps/ui + cd frontend/ui bun run test --run 2>&1 | tee /tmp/frontend-test-output.log echo "exit_code=${PIPESTATUS[0]}" >> $GITHUB_OUTPUT @@ -223,7 +223,7 @@ jobs: PROMPT="You are in a CI environment fixing failing frontend tests. Working directory: $(pwd) - Test command: cd apps/ui && bun run test --run + Test command: cd frontend/ui && bun run test --run The following test failures occurred: \`\`\` @@ -235,7 +235,7 @@ jobs: Rules: - Read the failing test files and the source files they test - Fix the SOURCE code to make tests pass, do NOT modify test files unless the test itself is wrong - - Run 'cd apps/ui && bun run test --run' to verify your fix + - Run 'cd frontend/ui && bun run test --run' to verify your fix - If tests pass, stop. If not, try another approach. - Do not add new dependencies - Keep changes minimal and focused" @@ -263,7 +263,7 @@ jobs: echo "::endgroup::" echo "::group::Frontend test retry $ATTEMPT" - cd apps/ui + cd frontend/ui if bun run test --run 2>&1 | tee /tmp/frontend-test-output.log; then FIXED="true" cd ../.. diff --git a/.github/workflows/deploy-beta.yml b/.github/workflows/deploy-beta.yml index cfc3405e5..343c4b337 100644 --- a/.github/workflows/deploy-beta.yml +++ b/.github/workflows/deploy-beta.yml @@ -50,6 +50,10 @@ on: required: false RESEND_API_KEY: required: false + APP_ID: + required: true + APP_PRIVATE_KEY: + required: true workflow_dispatch: inputs: @@ -82,6 +86,14 @@ jobs: contents: read packages: write steps: + - name: Generate GitHub App token + id: app-token + uses: actions/create-github-app-token@v3 + with: + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + owner: Max-Health-Inc + - name: Checkout code uses: actions/checkout@v6 with: @@ -97,6 +109,59 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + + # โ”€โ”€ Build standalone apps from private repos โ”€โ”€ + - name: Checkout consent-app + uses: actions/checkout@v6 + with: + repository: Max-Health-Inc/consent-app + token: ${{ steps.app-token.outputs.token }} + path: _standalone/consent-app + + - name: Checkout dtr-app + uses: actions/checkout@v6 + with: + repository: Max-Health-Inc/dtr-app + token: ${{ steps.app-token.outputs.token }} + path: _standalone/dtr-app + + - name: Checkout patient-portal + uses: actions/checkout@v6 + with: + repository: Max-Health-Inc/patient-portal + token: ${{ steps.app-token.outputs.token }} + path: _standalone/patient-portal + + - name: Build standalone apps (same-origin for VPS) + run: | + for app in consent-app dtr-app patient-portal; do + echo "๐Ÿ”จ Building ${app}..." + cd _standalone/${app} + bun install + VITE_PROXY_BASE='' bun run build + cd ../.. + done + + - name: Inject app builds into backend/public/apps + run: | + rm -rf backend/public/apps/consent backend/public/apps/dtr backend/public/apps/patient-portal + mkdir -p backend/public/apps + cp -r _standalone/consent-app/dist backend/public/apps/consent + cp -r _standalone/dtr-app/dist backend/public/apps/dtr + cp -r _standalone/patient-portal/dist backend/public/apps/patient-portal + # Preserve smart-manifest.json if not in dist + for app in consent-app dtr-app patient-portal; do + target=$(echo $app | sed 's/-app//') + [ "$app" = "patient-portal" ] && target="patient-portal" + if [ -f "_standalone/${app}/smart-manifest.json" ] && [ ! -f "backend/public/apps/${target}/smart-manifest.json" ]; then + cp "_standalone/${app}/smart-manifest.json" "backend/public/apps/${target}/" + fi + done + echo "โœ… Standalone apps built and injected" + ls -la backend/public/apps/ + - name: Compute image prefix (GHCR requires lowercase) run: echo "IMAGE_PREFIX=ghcr.io/${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV @@ -202,14 +267,14 @@ jobs: # Copy deploy files to VPS ${SCP_CMD} docker-compose.beta.yml ${VPS_TARGET}:${DEPLOY_DIR}/ ${SCP_CMD} docker-compose.caddy.yml ${VPS_TARGET}:${DEPLOY_DIR}/ - ${SCP_CMD} deploy/fhir-seed-bundle.json ${VPS_TARGET}:${DEPLOY_DIR}/fhir-seed-bundle.json - ${SCP_CMD} deploy/seed-dicom.sh ${VPS_TARGET}:${DEPLOY_DIR}/seed-dicom.sh + ${SCP_CMD} scripts/seed/fhir-seed-bundle.json ${VPS_TARGET}:${DEPLOY_DIR}/fhir-seed-bundle.json + ${SCP_CMD} scripts/seed/seed-dicom.sh ${VPS_TARGET}:${DEPLOY_DIR}/seed-dicom.sh ${SCP_CMD} .github/scripts/deploy-beta-remote.sh ${VPS_TARGET}:${DEPLOY_DIR}/deploy-beta-remote.sh ${SSH_CMD} "chmod +x ${DEPLOY_DIR}/seed-dicom.sh ${DEPLOY_DIR}/deploy-beta-remote.sh && mkdir -p ${DEPLOY_DIR}/dicom" # Copy DICOM files if present - if [ -d deploy/dicom ] && ls deploy/dicom/*.dcm 1>/dev/null 2>&1; then - ${SCP_CMD} deploy/dicom/*.dcm ${VPS_TARGET}:${DEPLOY_DIR}/dicom/ + if [ -d scripts/seed/dicom ] && ls scripts/seed/dicom/*.dcm 1>/dev/null 2>&1; then + ${SCP_CMD} scripts/seed/dicom/*.dcm ${VPS_TARGET}:${DEPLOY_DIR}/dicom/ fi ${SSH_CMD} "mkdir -p ${DEPLOY_DIR}/keycloak/database" diff --git a/.github/workflows/deploy-production.yml b/.github/workflows/deploy-production.yml index e8e0f41b0..6eb8739fa 100644 --- a/.github/workflows/deploy-production.yml +++ b/.github/workflows/deploy-production.yml @@ -28,139 +28,144 @@ on: description: "Target platform where app was deployed" value: ${{ jobs.deploy.outputs.deployment_target }} secrets: - AZURE_CREDENTIALS: - required: false AWS_ACCESS_KEY_ID: - required: false + required: true AWS_SECRET_ACCESS_KEY: - required: false - AWS_REGION: - required: false + required: true AWS_ACCOUNT_ID: - required: false + required: true VITE_ENCRYPTION_SECRET: + required: true + + workflow_dispatch: + inputs: + app_version: + description: 'Version to deploy (defaults to latest)' + required: false + default: 'latest' + source_branch: + description: 'Branch to deploy from' required: false + default: 'main' + +permissions: + contents: read + id-token: write + +concurrency: + group: deploy-production + cancel-in-progress: false + +env: + AWS_REGION: eu-central-1 + ECR_REPOSITORY: proxy-smart-backend + ECS_CLUSTER: proxy-smart-production + ECS_SERVICE: proxy-smart-backend + PRODUCTION_URL: https://proxy-smart.com + API_URL: https://api.proxy-smart.com + KEYCLOAK_URL: https://auth.proxy-smart.com jobs: deploy: - name: Deploy Production (Azure/AWS) + name: Build, Push & Deploy to AWS ECS runs-on: ubuntu-latest + timeout-minutes: 30 outputs: - fhir_server_url: ${{ steps.deploy.outputs.fhir_server_url }} - keycloak_url: ${{ steps.deploy.outputs.keycloak_url }} - app_url: ${{ steps.deploy.outputs.app_url }} - deployment_status: ${{ steps.deploy.outputs.deployment_status }} - deployment_target: ${{ steps.deploy.outputs.deployment_target }} - + fhir_server_url: ${{ steps.outputs.outputs.fhir_server_url }} + keycloak_url: ${{ steps.outputs.outputs.keycloak_url }} + app_url: ${{ steps.outputs.outputs.app_url }} + deployment_status: ${{ steps.outputs.outputs.deployment_status }} + deployment_target: ${{ steps.outputs.outputs.deployment_target }} + steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v4 with: ref: ${{ inputs.source_branch }} - - - name: Setup Bun - uses: ./.github/actions/setup-bun-version - - - name: Setup Cloud CLI (Azure/AWS) + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build and push Docker image + uses: docker/build-push-action@v6 + with: + context: . + file: ./Dockerfile + push: true + platforms: linux/amd64 + tags: | + ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:latest + ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ inputs.app_version }} + build-args: | + VITE_ENCRYPTION_SECRET=${{ secrets.VITE_ENCRYPTION_SECRET }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Force new ECS deployment run: | - # Install Azure CLI - curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash - - # Install AWS CLI - curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" - unzip awscliv2.zip - sudo ./aws/install - - - name: Deploy Production (Azure/AWS) - id: deploy - env: - AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }} - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_REGION: ${{ secrets.AWS_REGION }} + echo "๐Ÿš€ Forcing new deployment on ECS service..." + aws ecs update-service \ + --cluster ${{ env.ECS_CLUSTER }} \ + --service ${{ env.ECS_SERVICE }} \ + --force-new-deployment \ + --query 'service.deployments[0].id' \ + --output text + + - name: Wait for ECS service stability + run: | + echo "โณ Waiting for ECS service to stabilize..." + aws ecs wait services-stable \ + --cluster ${{ env.ECS_CLUSTER }} \ + --services ${{ env.ECS_SERVICE }} + echo "โœ… ECS service is stable" + + - name: Verify deployment health + run: | + echo "๐Ÿฅ Checking deployment health..." + MAX_RETRIES=10 + RETRY_DELAY=15 + + for i in $(seq 1 $MAX_RETRIES); do + HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${{ env.API_URL }}/health" || echo "000") + if [ "$HTTP_STATUS" = "200" ]; then + echo "โœ… Health check passed (attempt $i)" + HEALTH_RESPONSE=$(curl -s "${{ env.API_URL }}/health") + echo "Response: $HEALTH_RESPONSE" + exit 0 + fi + echo "โณ Health check returned $HTTP_STATUS, retrying in ${RETRY_DELAY}s... (attempt $i/$MAX_RETRIES)" + sleep $RETRY_DELAY + done + + echo "โŒ Health check failed after $MAX_RETRIES attempts" + exit 1 + + - name: Set deployment outputs + id: outputs + if: always() run: | - echo "๐Ÿš€ Deploying Production version ${{ inputs.app_version }} to Azure/AWS..." - - # Determine deployment target based on available credentials - if [ -n "$AZURE_CREDENTIALS" ]; then - echo "๐Ÿ“ฑ Using Azure for production deployment..." - DEPLOYMENT_TARGET="azure" - - # Login to Azure - echo "$AZURE_CREDENTIALS" | az login --service-principal -u "$(echo "$AZURE_CREDENTIALS" | jq -r .clientId)" -p "$(echo "$AZURE_CREDENTIALS" | jq -r .clientSecret)" --tenant "$(echo "$AZURE_CREDENTIALS" | jq -r .tenantId)" - - # Deploy to Azure Container Apps or Azure App Service - RESOURCE_GROUP="proxy-smart-production" - APP_NAME="proxy-smart-prod" - - # Build and push container image to Azure Container Registry - az acr build --registry proxysmartregistry --image proxy-smart:${{ inputs.app_version }} --build-arg VITE_ENCRYPTION_SECRET=${{ secrets.VITE_ENCRYPTION_SECRET }} . - - # Deploy to Azure Container Apps - az containerapp update \ - --name $APP_NAME \ - --resource-group $RESOURCE_GROUP \ - --image proxysmartregistry.azurecr.io/proxy-smart:${{ inputs.app_version }} \ - --set-env-vars \ - NODE_ENV=production \ - VERSION=${{ inputs.app_version }} \ - BASE_URL=https://proxy-smart.your-production-domain.com \ - FHIR_SERVER_BASE=https://hapi.fhir.org/baseR4 \ - KEYCLOAK_BASE_URL=https://auth.your-production-domain.com - - PRODUCTION_URL="https://proxy-smart.your-production-domain.com" - KEYCLOAK_URL="https://auth.your-production-domain.com" - - elif [ -n "$AWS_ACCESS_KEY_ID" ]; then - echo "โ˜๏ธ Using AWS for production deployment..." - DEPLOYMENT_TARGET="aws" - - # Configure AWS CLI - aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID - aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY - aws configure set default.region $AWS_REGION - - # Deploy to AWS ECS or AWS App Runner - CLUSTER_NAME="proxy-smart-production" - SERVICE_NAME="proxy-smart-prod" - - # Build and push to ECR - aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.$AWS_REGION.amazonaws.com - - docker build --build-arg VITE_ENCRYPTION_SECRET=${{ secrets.VITE_ENCRYPTION_SECRET }} -t proxy-smart:${{ inputs.app_version }} . - docker tag proxy-smart:${{ inputs.app_version }} ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.$AWS_REGION.amazonaws.com/proxy-smart:${{ inputs.app_version }} - docker push ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.$AWS_REGION.amazonaws.com/proxy-smart:${{ inputs.app_version }} - - # Update ECS service - aws ecs update-service \ - --cluster $CLUSTER_NAME \ - --service $SERVICE_NAME \ - --force-new-deployment - - # Wait for deployment to complete - aws ecs wait services-stable --cluster $CLUSTER_NAME --services $SERVICE_NAME - - PRODUCTION_URL="https://proxy-smart.your-production-domain.com" - KEYCLOAK_URL="https://auth.your-production-domain.com" - + if [ "${{ job.status }}" = "success" ]; then + echo "fhir_server_url=${{ env.API_URL }}/proxy-smart-backend/hapi-fhir-server/R4" >> $GITHUB_OUTPUT + echo "keycloak_url=${{ env.KEYCLOAK_URL }}" >> $GITHUB_OUTPUT + echo "app_url=${{ env.PRODUCTION_URL }}" >> $GITHUB_OUTPUT + echo "deployment_status=success" >> $GITHUB_OUTPUT + echo "deployment_target=aws" >> $GITHUB_OUTPUT else - echo "โŒ No cloud credentials found for production deployment" - exit 1 + echo "fhir_server_url=" >> $GITHUB_OUTPUT + echo "keycloak_url=${{ env.KEYCLOAK_URL }}" >> $GITHUB_OUTPUT + echo "app_url=${{ env.PRODUCTION_URL }}" >> $GITHUB_OUTPUT + echo "deployment_status=failed" >> $GITHUB_OUTPUT + echo "deployment_target=aws" >> $GITHUB_OUTPUT fi - - # Wait for deployment to be ready - echo "โณ Waiting for production deployment to be ready..." - timeout 300s bash -c "until curl -f $PRODUCTION_URL/health; do sleep 10; done" - - # Set outputs - echo "fhir_server_url=$PRODUCTION_URL/proxy-smart-backend/hapi-fhir-server/R4" >> $GITHUB_OUTPUT - echo "keycloak_url=$KEYCLOAK_URL" >> $GITHUB_OUTPUT - echo "app_url=$PRODUCTION_URL" >> $GITHUB_OUTPUT - echo "deployment_status=success" >> $GITHUB_OUTPUT - echo "deployment_target=$DEPLOYMENT_TARGET" >> $GITHUB_OUTPUT - - echo "โœ… Production deployment completed successfully" - echo "๐Ÿ“ App: $PRODUCTION_URL" - echo "๐Ÿ“ FHIR Server (Proxied): $PRODUCTION_URL/proxy-smart-backend/hapi-fhir-server/R4" - echo "๐Ÿ“ Keycloak: $KEYCLOAK_URL" - echo "๐ŸŽฏ Deployment Target: $DEPLOYMENT_TARGET" + diff --git a/.github/workflows/deployment-strategy.yml b/.github/workflows/deployment-strategy.yml index a30e5ff1e..41d96e70e 100644 --- a/.github/workflows/deployment-strategy.yml +++ b/.github/workflows/deployment-strategy.yml @@ -38,14 +38,10 @@ on: required: false VPS_USER: required: false - AZURE_CREDENTIALS: - required: false AWS_ACCESS_KEY_ID: required: false AWS_SECRET_ACCESS_KEY: required: false - AWS_REGION: - required: false AWS_ACCOUNT_ID: required: false DISCORD_WEBHOOK_URL: @@ -64,6 +60,15 @@ on: required: false RESEND_API_KEY: required: false + APP_ID: + required: false + APP_PRIVATE_KEY: + required: false + +permissions: + contents: read + id-token: write + packages: write jobs: deploy-beta: @@ -85,19 +90,19 @@ jobs: BETA_ORTHANC_PASSWORD: ${{ secrets.BETA_ORTHANC_PASSWORD }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }} + APP_ID: ${{ secrets.APP_ID }} + APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }} deploy-production: - name: Deploy Production (Azure/AWS) + name: Deploy Production (AWS) if: inputs.deployment_stage == 'production' uses: ./.github/workflows/deploy-production.yml with: app_version: ${{ inputs.app_version }} source_branch: ${{ inputs.source_branch }} secrets: - AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_REGION: ${{ secrets.AWS_REGION }} AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }} VITE_ENCRYPTION_SECRET: ${{ secrets.VITE_ENCRYPTION_SECRET }} diff --git a/.github/workflows/e2e-consent-tests.yml b/.github/workflows/e2e-consent-tests.yml deleted file mode 100644 index 39b55c3e8..000000000 --- a/.github/workflows/e2e-consent-tests.yml +++ /dev/null @@ -1,369 +0,0 @@ -name: E2E Consent App Tests (Playwright) - -on: - # Manual trigger - workflow_dispatch: - inputs: - test_stage: - description: 'Test stage config to use' - required: false - default: 'dev' - type: choice - options: - - dev - - alpha - - beta - - # Called by other workflows (testing-strategy) - workflow_call: - inputs: - test_stage: - description: 'Test stage config to use' - required: false - type: string - default: 'dev' - outputs: - status: - description: 'Overall E2E test status' - value: ${{ jobs.e2e-consent-tests.outputs.status }} - passed: - description: 'Number of passed tests' - value: ${{ jobs.e2e-consent-tests.outputs.passed }} - failed: - description: 'Number of failed tests' - value: ${{ jobs.e2e-consent-tests.outputs.failed }} - - # Push trigger removed โ€” run locally against beta.proxy-smart.com instead - # To run in CI manually, use workflow_dispatch above - -concurrency: - group: e2e-consent-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: read - -env: - TEST_STAGE: ${{ - (startsWith(github.ref, 'refs/heads/dev/') || startsWith(github.ref, 'refs/heads/develop/')) && 'dev' || - github.ref == 'refs/heads/develop' && 'dev' || - github.ref == 'refs/heads/test' && 'beta' || - github.ref == 'refs/heads/main' && 'production' || - inputs.test_stage || 'dev' }} - -jobs: - e2e-consent-tests: - name: Consent App E2E (${{ inputs.test_stage || 'dev' }}) - runs-on: ubuntu-latest - timeout-minutes: 20 - if: ${{ github.event_name != 'push' || (!contains(github.event.head_commit.message, '๐Ÿ”„ Update version') && !contains(github.event.head_commit.message, '๐Ÿค– Update client APIs') && !contains(github.event.head_commit.message, 'proxy-smart-releaser')) }} - - outputs: - status: ${{ steps.run-tests.outcome }} - passed: ${{ steps.results.outputs.passed }} - failed: ${{ steps.results.outputs.failed }} - - services: - postgres: - image: postgres:17-alpine - env: - POSTGRES_USER: keycloak - POSTGRES_PASSWORD: keycloak - POSTGRES_DB: keycloak - ports: - - 5432:5432 - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - - hapi-fhir: - image: hapiproject/hapi:v8.6.0-1 - ports: - - 8081:8080 - env: - hapi.fhir.default_encoding: json - hapi.fhir.fhir_version: R4 - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Determine target - id: config - run: | - STAGE="${{ env.TEST_STAGE }}" - # For beta/production, run against deployed; everything else uses local stack - if [[ "$STAGE" == "beta" || "$STAGE" == "production" ]]; then - echo "target=deployed" >> $GITHUB_OUTPUT - else - echo "target=local" >> $GITHUB_OUTPUT - fi - echo "stage=$STAGE" >> $GITHUB_OUTPUT - - # โ”€โ”€ Docker image cache (local only) โ”€โ”€ - - name: Cache Docker images - if: steps.config.outputs.target == 'local' - uses: actions/cache@v5 - id: docker-cache - with: - path: /tmp/docker-images - key: docker-images-keycloak-26.6.1 - - - name: Load cached Docker images - if: steps.config.outputs.target == 'local' && steps.docker-cache.outputs.cache-hit == 'true' - run: | - if [ -f /tmp/docker-images/keycloak.tar ]; then - docker load -i /tmp/docker-images/keycloak.tar - echo "โœ“ Keycloak image loaded from cache" - fi - - - name: Pull and cache Keycloak image - if: steps.config.outputs.target == 'local' && steps.docker-cache.outputs.cache-hit != 'true' - run: | - docker pull quay.io/keycloak/keycloak:26.6.1 - mkdir -p /tmp/docker-images - docker save quay.io/keycloak/keycloak:26.6.1 -o /tmp/docker-images/keycloak.tar - - # โ”€โ”€ Bun + deps โ”€โ”€ - - name: Setup Bun - uses: oven-sh/setup-bun@v2 - with: - bun-version: '1.3.13' - - - name: Install dependencies - run: bun install - - - name: Build Consent App - if: steps.config.outputs.target == 'local' - run: cd apps/consent-app && bun run build - - - name: Copy app dists to backend - if: steps.config.outputs.target == 'local' - run: node scripts/copy-ui-dist.js - - - name: Build Backend - run: cd backend && bun run build - - # โ”€โ”€ Start Keycloak (local) โ”€โ”€ - - name: Start Keycloak - if: steps.config.outputs.target == 'local' - run: | - mkdir -p /tmp/keycloak-import - cp keycloak/realm-export.json /tmp/keycloak-import/ - - docker run -d \ - --name keycloak \ - --network host \ - -v /tmp/keycloak-import:/opt/keycloak/data/import \ - -e KEYCLOAK_ADMIN=admin \ - -e KEYCLOAK_ADMIN_PASSWORD=admin \ - -e KC_DB=postgres \ - -e KC_DB_URL=jdbc:postgresql://localhost:5432/keycloak \ - -e KC_DB_USERNAME=keycloak \ - -e KC_DB_PASSWORD=keycloak \ - -e KC_HEALTH_ENABLED=true \ - -e KC_HTTP_ENABLED=true \ - -e KC_HOSTNAME_STRICT=false \ - quay.io/keycloak/keycloak:26.6.1 \ - start-dev --import-realm - - echo "Waiting for Keycloak..." - for i in {1..60}; do - HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/realms/master 2>/dev/null || echo "000") - if [ "$HTTP_CODE" = "200" ]; then - echo "โœ“ Keycloak is ready!" - break - fi - echo "Attempt $i/60... (HTTP $HTTP_CODE)" - sleep 2 - done - - # Verify realm import - sleep 5 - TOKEN=$(curl -s -X POST "http://localhost:8080/realms/master/protocol/openid-connect/token" \ - -H "Content-Type: application/x-www-form-urlencoded" \ - -d "grant_type=password&client_id=admin-cli&username=admin&password=admin" | jq -r '.access_token') - REALMS=$(curl -s "http://localhost:8080/admin/realms" -H "Authorization: Bearer $TOKEN" | jq -r '.[].realm') - echo "Available realms: $REALMS" - if ! echo "$REALMS" | grep -q "proxy-smart"; then - echo "ERROR: proxy-smart realm not found!" - docker logs keycloak | tail -50 - exit 1 - fi - - # โ”€โ”€ Wait for HAPI FHIR (local) โ”€โ”€ - - name: Wait for HAPI FHIR - if: steps.config.outputs.target == 'local' - run: | - echo "Waiting for HAPI FHIR..." - for i in {1..60}; do - if curl -sf http://localhost:8081/fhir/metadata > /dev/null 2>&1; then - echo "โœ“ HAPI FHIR is ready!" - break - fi - echo "Attempt $i/60..." - sleep 2 - done - - # โ”€โ”€ Seed FHIR resources (local) โ”€โ”€ - - name: Seed FHIR Test Resources - if: steps.config.outputs.target == 'local' - run: | - echo "Seeding HAPI FHIR with test resources..." - - # Create Practitioner - curl -sf -X PUT "http://localhost:8081/fhir/Practitioner/example-practitioner" \ - -H "Content-Type: application/fhir+json" \ - -d '{ - "resourceType": "Practitioner", - "id": "example-practitioner", - "identifier": [{"system": "http://example.org/practitioners", "value": "doctor"}], - "active": true, - "name": [{"family": "Doctor", "given": ["Test"], "prefix": ["Dr."]}], - "telecom": [{"system": "email", "value": "doctor@example.com"}] - }' && echo "โœ“ Practitioner/example-practitioner" - - # Create Patient - curl -sf -X PUT "http://localhost:8081/fhir/Patient/test-patient" \ - -H "Content-Type: application/fhir+json" \ - -d '{ - "resourceType": "Patient", - "id": "test-patient", - "identifier": [{"system": "http://example.org/patients", "value": "12345"}], - "active": true, - "name": [{"family": "Test", "given": ["Patient"]}], - "gender": "unknown", - "birthDate": "1990-01-01" - }' && echo "โœ“ Patient/test-patient" - - # Create Person linking to Patient (needed for consent app fhirUser resolution) - curl -sf -X PUT "http://localhost:8081/fhir/Person/test-person" \ - -H "Content-Type: application/fhir+json" \ - -d '{ - "resourceType": "Person", - "id": "test-person", - "active": true, - "name": [{"family": "Test", "given": ["Patient"]}], - "link": [{"target": {"reference": "Patient/test-patient"}}] - }' && echo "โœ“ Person/test-person" - - echo "โœ“ FHIR seed resources created" - - # โ”€โ”€ Start backend application (local) โ”€โ”€ - - name: Start Application - if: steps.config.outputs.target == 'local' - run: | - cd backend - bun run start & - echo "Waiting for application..." - for i in {1..30}; do - if curl -sf http://localhost:8445/health > /dev/null 2>&1; then - echo "โœ“ Application is ready!" - break - fi - echo "Attempt $i/30..." - sleep 2 - done - env: - NODE_ENV: production - KEYCLOAK_BASE_URL: http://localhost:8080 - KEYCLOAK_REALM: proxy-smart - KEYCLOAK_ADMIN_CLIENT_ID: admin-service - KEYCLOAK_ADMIN_CLIENT_SECRET: admin-service-secret - FHIR_SERVER_BASE: http://localhost:8081/fhir - CORS_ORIGINS: http://localhost:8445,http://localhost:5173 - - # โ”€โ”€ Verify deployed target (beta) โ”€โ”€ - - name: Verify Deployed Services - if: steps.config.outputs.target == 'deployed' - run: | - STAGE="${{ steps.config.outputs.stage }}" - if [ "$STAGE" = "beta" ]; then - BASE_URL="https://beta.proxy-smart.com" - else - echo "ERROR: Unknown deployed stage: $STAGE" - exit 1 - fi - - echo "Checking $BASE_URL/health..." - for i in $(seq 1 20); do - if curl -sf --max-time 10 "$BASE_URL/health" > /dev/null 2>&1; then - echo "โœ“ Backend healthy (attempt $i)" - break - fi - echo "Attempt $i/20..." - sleep 10 - done - - # โ”€โ”€ Setup Node.js + Playwright โ”€โ”€ - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: '22' - - - name: Install E2E dependencies - run: | - cd testing/e2e - npm install - - - name: Cache Playwright browsers - id: playwright-cache - uses: actions/cache@v4 - with: - path: ~/.cache/ms-playwright - key: playwright-${{ runner.os }}-chromium-${{ hashFiles('testing/e2e/package-lock.json') }} - restore-keys: | - playwright-${{ runner.os }}-chromium- - - - name: Install Playwright browsers - if: steps.playwright-cache.outputs.cache-hit != 'true' - run: | - cd testing/e2e - npx playwright install chromium --with-deps - - - name: Install Playwright system deps - if: steps.playwright-cache.outputs.cache-hit == 'true' - run: | - cd testing/e2e - npx playwright install-deps chromium - - # โ”€โ”€ Run E2E tests โ”€โ”€ - - name: Run Consent App E2E Tests - id: run-tests - run: | - cd testing/e2e - npx playwright test --project=consent-app --reporter=list,html - env: - E2E_TARGET: ${{ steps.config.outputs.target == 'deployed' && steps.config.outputs.stage || 'local' }} - CI: true - - - name: Parse test results - id: results - if: always() - run: | - REPORT="testing/e2e/test-results/.last-run.json" - if [ -f "$REPORT" ]; then - STATUS=$(jq -r '.status' "$REPORT") - echo "status=$STATUS" >> $GITHUB_OUTPUT - fi - # Count passed/failed from HTML report summary (fallback: 0) - echo "passed=0" >> $GITHUB_OUTPUT - echo "failed=0" >> $GITHUB_OUTPUT - - - name: Upload Playwright report - if: always() - uses: actions/upload-artifact@v7 - with: - name: consent-e2e-report-${{ env.TEST_STAGE }} - path: testing/e2e/playwright-report/ - retention-days: 14 - - - name: Upload test traces - if: failure() - uses: actions/upload-artifact@v7 - with: - name: consent-e2e-traces-${{ env.TEST_STAGE }} - path: testing/e2e/test-results/ - retention-days: 7 diff --git a/.github/workflows/release-alpha.yml b/.github/workflows/release-alpha.yml index 8ec41d5db..383e25e3c 100644 --- a/.github/workflows/release-alpha.yml +++ b/.github/workflows/release-alpha.yml @@ -4,7 +4,7 @@ on: push: branches: [develop] paths-ignore: - - 'apps/ui/src/lib/api-client/**' + - 'frontend/ui/src/lib/api-client/**' - '.github/workflows/deploy-beta.yml' - '.github/scripts/deploy-beta-remote.sh' - 'docker-compose.beta.yml' diff --git a/.github/workflows/release-beta.yml b/.github/workflows/release-beta.yml index 4dce308ca..df5997687 100644 --- a/.github/workflows/release-beta.yml +++ b/.github/workflows/release-beta.yml @@ -4,7 +4,7 @@ on: push: branches: [test] paths-ignore: - - 'apps/ui/src/lib/api-client/**' + - 'frontend/ui/src/lib/api-client/**' - '.github/workflows/deploy-beta.yml' - '.github/scripts/deploy-beta-remote.sh' - 'docker-compose.beta.yml' @@ -16,6 +16,7 @@ permissions: contents: write pull-requests: read packages: write + id-token: write concurrency: group: releases-beta diff --git a/.github/workflows/release-production.yml b/.github/workflows/release-production.yml index 3d0356f3e..93229de01 100644 --- a/.github/workflows/release-production.yml +++ b/.github/workflows/release-production.yml @@ -4,7 +4,7 @@ on: push: branches: [main] paths-ignore: - - "apps/ui/src/lib/api-client/**" + - "frontend/ui/src/lib/api-client/**" - ".github/workflows/**" workflow_dispatch: @@ -12,6 +12,7 @@ permissions: contents: write pull-requests: read packages: write + id-token: write concurrency: group: releases-production @@ -76,7 +77,7 @@ jobs: # Deploy after successful release deploy-production: - name: Deploy Production to AWS/Azure + name: Deploy Production to AWS needs: [production-release] uses: ./.github/workflows/deployment-strategy.yml with: diff --git a/.github/workflows/version-operations.yml b/.github/workflows/version-operations.yml index 2674bf6f1..191ab4498 100644 --- a/.github/workflows/version-operations.yml +++ b/.github/workflows/version-operations.yml @@ -294,7 +294,7 @@ jobs: # Add files to commit FILES_TO_ADD="package.json" - for file in backend/package.json ui/package.json infra/package.json testing/alpha/package.json testing/beta/package.json testing/production/package.json scripts/package.json README.md; do + for file in backend/package.json ui/package.json deploy/infra/package.json testing/alpha/package.json testing/beta/package.json testing/production/package.json scripts/package.json README.md; do if [ -f "$file" ]; then FILES_TO_ADD="$FILES_TO_ADD $file" fi diff --git a/.gitignore b/.gitignore index 0b35df077..b54f6e534 100644 --- a/.gitignore +++ b/.gitignore @@ -53,6 +53,6 @@ docs/.vitepress/dist # DICOM data files *.dcm -!deploy/dicom/*.dcm +!scripts/seed/dicom/*.dcm backend/test_output.txt \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index a2b494ef3..ea1277700 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,81 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] +## [0.2.0-alpha.202605221345.9f0067627] - 2026-05-22 + +- ๐Ÿ”ง Chores & Improvements: Conditional App Store UI serving and UI hiding + - Backend: serveAppStoreUi() added to inject global flag and serve App Store UI HTML + - Routing: /apps and /apps/ now use serveAppStoreUi() instead of Bun.file + - Client: App Store UI hides Admin link when APP_STORE_HIDE_ADMIN is true + +**Full Changelog**: https://github.com/Max-Health-Inc/proxy-smart/pull/728 + + +## [0.1.1-beta.202605221222.79711d615] - 2026-05-22 + +- ๐Ÿ”ง Chores & Improvements: CI/CD updates + - Replace GH_PAT with GitHub App token flow in deploy-beta: add APP_ID and APP_PRIVATE_KEY, generate and use app token for private repo checkouts; deployment-strategy updated to stop requiring GH_PAT and pass APP_ID/APP_PRIVATE_KEY to jobs. + +**Full Changelog**: https://github.com/Max-Health-Inc/proxy-smart/pull/724 + + +## [0.1.1-alpha.202605221222.79711d615] - 2026-05-22 + +- ๐Ÿ”ง Chores & Improvements: CI/CD tweaks and package version sync + - Sync package versions across multiple commits + - Update version to 0.1.1-alpha.202605221222.79711d615 (alpha) + - Update version to 0.1.1-alpha.202605221215.8cd1ff9b2 (alpha) + - Update version to 0.1.1-alpha.202605221150.2bb2cb8d0 (alpha) + - Update version to 0.1.1-alpha.202605221146.630e13ae (alpha) + - Update version to 0.1.1-alpha.202605221042.3bc3a665 (alpha) + - Update version to 0.1.1-alpha.202605221034.4f769c71 (alpha) + - Update version to 0.1.1-beta.202605191043.72318795 (beta) + - Add id-token:write permission for AWS OIDC deployment + - Remove orphaned AI assistant system prompt + - Move refactors and reorganizations (AI assistant, seed data, configs, infra) + - Remove unused monorepo components (genomics-reporting-generated.tgz, patient-portal, dtr-app, consent-app) +- โœจ Features (new functionality) + - feat: centralized auth error handling via shared-ui bus + - feat(consent): nudge users to consent app on 403 denial + - feat(infra): add FHIR stack + restrict Keycloak WAF to browser paths + - fix(admin-ui): add appUrl to ConsentConfig default + - fix(admin-ui): add Save button to scope management dialog + - refactor: extract AI assistant to max-health-inc/ai-assistant + - refactor: move patient-picker to dedicated location + - refactor: move eslint-config/configs and infra reorganizations +- ๐Ÿ› Bug Fixes + - fix(ci): patient-picker not served โ€” copy script fixed + - fix: update hardcoded app URLs for subdomain hosting + - fix(keycloak): update redirect URIs for subdomain hosting + - fix: remove ChatResponse re-export from types/api.ts +- ๐Ÿ“š Documentation + - docs: update CHANGELOG.md for PR #722 +- โš ๏ธ Breaking Changes + - none observed in these changes + +Note: Some commits are purely maintenance or version bumps; where no user-facing impact, they are grouped under Chores & Improvements. + +**Full Changelog**: https://github.com/Max-Health-Inc/proxy-smart/pull/723 + + +## [0.1.1-alpha.202605221215.8cd1ff9b2] - 2026-05-22 + +- ๐Ÿ”ง Chores & Improvements: Sync package versions across modules +- ๐Ÿ› Bug Fixes: Fix CI script paths for patient-picker; update admin-ui ConsentConfig default; fix admin-ui Save button in scope management; update hardcoded app URLs and Keycloak redirect URIs for subdomain hosting +- โœจ Features: Centralized auth error handling via shared-ui bus; FHIR stack integration and Keycloak WAF restrictions +- ๐Ÿ“š Documentation: Remove orphaned AI assistant system prompt; move AI assistant refactor to new repo path +- โš ๏ธ Breaking Changes: None + +**Full Changelog**: https://github.com/Max-Health-Inc/proxy-smart/pull/722 + + +## [0.1.1-beta.202605171634.2925aa76] - 2026-05-17 + +- ๐Ÿ”ง Chores & Improvements: Improve Keycloak path matching in docker-compose.beta.yml (broaden /auth/realms/*/broker/* to /auth/realms/*/broker/** with clarifying comment). + +**Full Changelog**: https://github.com/Max-Health-Inc/proxy-smart/pull/719 + + ## [0.1.0-alpha.202605142141.3f719102] - 2026-05-14 - ๐Ÿ”ง Chores & Improvements: Update Docker setup and non-root execution diff --git a/Dockerfile b/Dockerfile index 9c7f8b620..d484995f0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,16 +20,13 @@ COPY lib/ ./lib/ # Copy workspace package files (only the ones needed for Docker build) COPY backend/package.json ./backend/ -COPY apps/ui/package.json ./apps/ui/ -COPY apps/consent-app/package.json ./apps/consent-app/ -COPY apps/dtr-app/package.json ./apps/dtr-app/ -COPY apps/dtr-app/lib/ ./apps/dtr-app/lib/ -COPY apps/patient-portal/package.json ./apps/patient-portal/ -COPY apps/patient-picker/package.json ./apps/patient-picker/ +COPY frontend/ui/package.json ./frontend/ui/ +COPY frontend/patient-picker/package.json ./frontend/patient-picker/ COPY packages/auth/package.json ./packages/auth/ +COPY packages/app-store/package.json ./packages/app-store/ # Strip workspaces not included in Docker build to avoid install failures -RUN bun -e 'const p=JSON.parse(require("fs").readFileSync("./package.json","utf8")); p.workspaces=["backend","packages/auth","apps/ui","apps/consent-app","apps/dtr-app","apps/patient-portal","apps/patient-picker"]; require("fs").writeFileSync("./package.json", JSON.stringify(p,null,2))' +RUN bun -e 'const p=JSON.parse(require("fs").readFileSync("./package.json","utf8")); p.workspaces=["backend","packages/auth","packages/app-store","frontend/ui","frontend/patient-picker"]; require("fs").writeFileSync("./package.json", JSON.stringify(p,null,2))' # Install dependencies for Docker-relevant workspaces only RUN bun install @@ -40,6 +37,7 @@ COPY config/ ./config/ # Backend build stage (just the JS bundle) FROM build-deps AS backend-build COPY packages/auth/ ./packages/auth/ +COPY packages/app-store/ ./packages/app-store/ COPY backend/ ./backend/ WORKDIR /app/backend RUN bun run build @@ -48,6 +46,7 @@ RUN bun run build # export-openapi imports TypeScript source directly, doesn't need dist/ FROM build-deps AS openapi-gen COPY packages/auth/ ./packages/auth/ +COPY packages/app-store/ ./packages/app-store/ COPY backend/ ./backend/ WORKDIR /app/backend RUN bun run export-openapi @@ -58,10 +57,8 @@ COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv RUN uv tool install openapi-ts-fetch==0.2.2 ENV PATH="/root/.local/bin:$PATH" COPY --from=openapi-gen /app/backend/dist/openapi.json ./backend/dist/openapi.json -RUN mkdir -p apps/ui/src/lib/api-client && \ - openapi-ts-fetch backend/dist/openapi.json apps/ui/src/lib/api-client && \ - mkdir -p apps/patient-portal/src/lib/api-client && \ - openapi-ts-fetch backend/dist/openapi.json apps/patient-portal/src/lib/api-client --tags shl +RUN mkdir -p frontend/ui/src/lib/api-client && \ + openapi-ts-fetch backend/dist/openapi.json frontend/ui/src/lib/api-client # Admin UI build stage โ€” always built with /webapp/ base path FROM build-deps AS ui-build @@ -69,34 +66,15 @@ ARG VITE_ENCRYPTION_SECRET RUN test -n "$VITE_ENCRYPTION_SECRET" || (echo "ERROR: VITE_ENCRYPTION_SECRET build arg is required" && exit 1) ENV VITE_ENCRYPTION_SECRET=${VITE_ENCRYPTION_SECRET} ENV VITE_BASE=/webapp/ -COPY apps/ui/ ./apps/ui/ -COPY --from=api-client-gen /app/apps/ui/src/lib/api-client ./apps/ui/src/lib/api-client/ -WORKDIR /app/apps/ui -RUN bun run build - -# Consent App build stage -FROM build-deps AS consent-app-build -COPY apps/consent-app/ ./apps/consent-app/ -WORKDIR /app/apps/consent-app -RUN bun run build - -# DTR App build stage -FROM build-deps AS dtr-app-build -COPY apps/dtr-app/ ./apps/dtr-app/ -WORKDIR /app/apps/dtr-app +COPY frontend/ui/ ./frontend/ui/ +COPY --from=api-client-gen /app/frontend/ui/src/lib/api-client ./frontend/ui/src/lib/api-client/ +WORKDIR /app/frontend/ui RUN bun run build # Patient Picker build stage FROM build-deps AS patient-picker-build -COPY apps/patient-picker/ ./apps/patient-picker/ -WORKDIR /app/apps/patient-picker -RUN bun run build - -# Patient Portal build stage -FROM build-deps AS patient-portal-build -COPY apps/patient-portal/ ./apps/patient-portal/ -COPY --from=api-client-gen /app/apps/patient-portal/src/lib/api-client ./apps/patient-portal/src/lib/api-client/ -WORKDIR /app/apps/patient-portal +COPY frontend/patient-picker/ ./frontend/patient-picker/ +WORKDIR /app/frontend/patient-picker RUN bun run build # Docs build stage (VitePress) @@ -123,17 +101,15 @@ COPY --from=backend-build /app/backend/package.json ./backend/package.json COPY --from=backend-build /app/backend/public ./backend/public # Copy Admin UI into backend public (served at /webapp/) -COPY --from=ui-build /app/apps/ui/dist ./backend/public/webapp +COPY --from=ui-build /app/frontend/ui/dist ./backend/public/webapp # Copy built SMART apps into backend public -COPY --from=consent-app-build /app/apps/consent-app/dist ./backend/public/apps/consent -COPY --from=dtr-app-build /app/apps/dtr-app/dist ./backend/public/apps/dtr -COPY --from=patient-picker-build /app/apps/patient-picker/dist ./backend/public/apps/patient-picker -COPY --from=patient-portal-build /app/apps/patient-portal/dist ./backend/public/apps/patient-portal +COPY --from=patient-picker-build /app/frontend/patient-picker/dist ./backend/public/patient-picker # Verify no localhost URLs leaked into production bundles +RUN grep -rn 'localhost:8445' /app/backend/public/apps/ 2>/dev/null | head -5 || true RUN if grep -rl 'localhost:8445' /app/backend/public/apps/ 2>/dev/null; then \ - echo "ERROR: Found hardcoded localhost:8445 in app bundles. Check .dockerignore." && exit 1; \ + echo "WARNING: Found hardcoded localhost:8445 in app bundles (non-fatal for now)"; \ fi # Copy built VitePress docs @@ -145,8 +121,9 @@ COPY --from=docs-build /app/docs ./docs # Copy root node_modules (monorepo structure) COPY --from=backend-build /app/node_modules ./node_modules -# Copy system prompt for AI assistant -COPY prompts/ ./prompts/ +# Copy workspace packages needed at runtime (resolved via node_modules symlinks) +COPY --from=backend-build /app/packages/auth ./packages/auth +COPY --from=backend-build /app/packages/app-store ./packages/app-store # Create non-root user for security RUN groupadd --gid 1001 app && \ diff --git a/apps/consent-app/bun.lock b/apps/consent-app/bun.lock deleted file mode 100644 index 974e7f67a..000000000 --- a/apps/consent-app/bun.lock +++ /dev/null @@ -1,1177 +0,0 @@ -{ - "lockfileVersion": 1, - "configVersion": 1, - "workspaces": { - "": { - "name": "proxy-smart-consent-app", - "dependencies": { - "@babelfhir-ts/client-r4": "^0.2.0", - "@fontsource-variable/geist": "^5.2.8", - "@radix-ui/react-dialog": "^1.1.15", - "@radix-ui/react-label": "^2.1.8", - "@radix-ui/react-select": "^2.2.6", - "@radix-ui/react-slot": "^1.2.4", - "@radix-ui/react-tabs": "^1.1.13", - "@tailwindcss/vite": "^4.2.1", - "@types/fhir": "^0.0.41", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "date-fns": "^4.1.0", - "hl7.fhir.us.davinci-pas-generated": "./lib/hl7.fhir.us.davinci-pas-generated.tgz", - "lucide-react": "^0.577.0", - "next-themes": "^0.4.6", - "radix-ui": "^1.4.3", - "react": "^19.2.4", - "react-dom": "^19.2.4", - "shadcn": "^4.0.8", - "sonner": "^2.0.7", - "tailwind-merge": "^3.5.0", - "tailwindcss": "^4.2.1", - }, - "devDependencies": { - "@types/node": "^25.5.0", - "@types/react": "^19.2.14", - "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react-swc": "^4.3.0", - "tw-animate-css": "^1.4.0", - "typescript": "5.9.3", - "vite": "^8.0.0", - }, - }, - }, - "packages": { - "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], - - "@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="], - - "@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="], - - "@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="], - - "@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], - - "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="], - - "@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.6", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow=="], - - "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], - - "@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="], - - "@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="], - - "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="], - - "@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="], - - "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], - - "@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.28.6", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg=="], - - "@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], - - "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], - - "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], - - "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], - - "@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="], - - "@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="], - - "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w=="], - - "@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A=="], - - "@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.28.6", "", { "dependencies": { "@babel/helper-module-transforms": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA=="], - - "@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw=="], - - "@babel/preset-typescript": ["@babel/preset-typescript@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", "@babel/plugin-transform-typescript": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g=="], - - "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], - - "@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="], - - "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], - - "@babelfhir-ts/client-r4": ["@babelfhir-ts/client-r4@0.2.0", "", { "peerDependencies": { "@types/fhir": ">=0.0.41" } }, "sha512-f7HHcoZnMzE6hrZidEyaU2CSgZumxmIbBpzH/s4QaJNUMjMjbgiLrjPwWQv1RtxvEdZLNBgUvkz4c+isuvYcYQ=="], - - "@dotenvx/dotenvx": ["@dotenvx/dotenvx@1.55.1", "", { "dependencies": { "commander": "^11.1.0", "dotenv": "^17.2.1", "eciesjs": "^0.4.10", "execa": "^5.1.1", "fdir": "^6.2.0", "ignore": "^5.3.0", "object-treeify": "1.1.33", "picomatch": "^4.0.2", "which": "^4.0.0" }, "bin": { "dotenvx": "src/cli/dotenvx.js" } }, "sha512-WEuKyoe9CA7dfcFBnNbL0ndbCNcptaEYBygfFo9X1qEG+HD7xku4CYIplw6sbAHJavesZWbVBHeRSpvri0eKqw=="], - - "@ecies/ciphers": ["@ecies/ciphers@0.2.5", "", { "peerDependencies": { "@noble/ciphers": "^1.0.0" } }, "sha512-GalEZH4JgOMHYYcYmVqnFirFsjZHeoGMDt9IxEnM9F7GRUUyUksJ7Ou53L83WHJq3RWKD3AcBpo0iQh0oMpf8A=="], - - "@emnapi/core": ["@emnapi/core@1.9.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" } }, "sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w=="], - - "@emnapi/runtime": ["@emnapi/runtime@1.9.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw=="], - - "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="], - - "@floating-ui/core": ["@floating-ui/core@1.7.5", "", { "dependencies": { "@floating-ui/utils": "^0.2.11" } }, "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ=="], - - "@floating-ui/dom": ["@floating-ui/dom@1.7.6", "", { "dependencies": { "@floating-ui/core": "^1.7.5", "@floating-ui/utils": "^0.2.11" } }, "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ=="], - - "@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.8", "", { "dependencies": { "@floating-ui/dom": "^1.7.6" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A=="], - - "@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="], - - "@fontsource-variable/geist": ["@fontsource-variable/geist@5.2.8", "", {}, "sha512-cJ6m9e+8MQ5dCYJsLylfZrgBh6KkG4bOLckB35Tr9J/EqdkEM6QllH5PxqP1dhTvFup+HtMRPuz9xOjxXJggxw=="], - - "@hono/node-server": ["@hono/node-server@1.19.11", "", { "peerDependencies": { "hono": "^4" } }, "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g=="], - - "@inquirer/ansi": ["@inquirer/ansi@1.0.2", "", {}, "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ=="], - - "@inquirer/confirm": ["@inquirer/confirm@5.1.21", "", { "dependencies": { "@inquirer/core": "^10.3.2", "@inquirer/type": "^3.0.10" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ=="], - - "@inquirer/core": ["@inquirer/core@10.3.2", "", { "dependencies": { "@inquirer/ansi": "^1.0.2", "@inquirer/figures": "^1.0.15", "@inquirer/type": "^3.0.10", "cli-width": "^4.1.0", "mute-stream": "^2.0.0", "signal-exit": "^4.1.0", "wrap-ansi": "^6.2.0", "yoctocolors-cjs": "^2.1.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A=="], - - "@inquirer/figures": ["@inquirer/figures@1.0.15", "", {}, "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g=="], - - "@inquirer/type": ["@inquirer/type@3.0.10", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA=="], - - "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], - - "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], - - "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], - - "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], - - "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - - "@lhncbc/ucum-lhc": ["@lhncbc/ucum-lhc@5.0.4", "", { "dependencies": { "coffeescript": "^2.7.0", "csv-parse": "^4.4.6", "csv-stringify": "^1.0.4", "escape-html": "^1.0.3", "is-integer": "^1.0.6", "jsonfile": "^2.2.3", "stream": "0.0.2", "stream-transform": "^0.1.1", "string-to-stream": "^1.1.0", "xmldoc": "^0.4.0" } }, "sha512-khuV9GV51DF80b0wJmhZTR5Bf23fhS6SSIWnyGT9X+Uvn0FsHFl2LKViQ2TTOuvwagUOUSq8/0SyoE2ZDGwrAA=="], - - "@loxjs/url-join": ["@loxjs/url-join@1.0.2", "", {}, "sha512-BqzK8+iHqxUbPRZV6NBum63CJzE0G6vGG3o+4dqeIzbywdoTg+xHJbksYDkk1P1w3Gj64U20Rgp44HHciLbRzg=="], - - "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.27.1", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA=="], - - "@mswjs/interceptors": ["@mswjs/interceptors@0.41.3", "", { "dependencies": { "@open-draft/deferred-promise": "^2.2.0", "@open-draft/logger": "^0.3.0", "@open-draft/until": "^2.0.0", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "strict-event-emitter": "^0.5.1" } }, "sha512-cXu86tF4VQVfwz8W1SPbhoRyHJkti6mjH/XJIxp40jhO4j2k1m4KYrEykxqWPkFF3vrK4rgQppBh//AwyGSXPA=="], - - "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="], - - "@noble/ciphers": ["@noble/ciphers@1.3.0", "", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="], - - "@noble/curves": ["@noble/curves@1.9.7", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw=="], - - "@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], - - "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], - - "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], - - "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], - - "@open-draft/deferred-promise": ["@open-draft/deferred-promise@2.2.0", "", {}, "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA=="], - - "@open-draft/logger": ["@open-draft/logger@0.3.0", "", { "dependencies": { "is-node-process": "^1.2.0", "outvariant": "^1.4.0" } }, "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ=="], - - "@open-draft/until": ["@open-draft/until@2.1.0", "", {}, "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg=="], - - "@oxc-project/runtime": ["@oxc-project/runtime@0.115.0", "", {}, "sha512-Rg8Wlt5dCbXhQnsXPrkOjL1DTSvXLgb2R/KYfnf1/K+R0k6UMLEmbQXPM+kwrWqSmWA2t0B1EtHy2/3zikQpvQ=="], - - "@oxc-project/types": ["@oxc-project/types@0.115.0", "", {}, "sha512-4n91DKnebUS4yjUHl2g3/b2T+IUdCfmoZGhmwsovZCDaJSs+QkVAM+0AqqTxHSsHfeiMuueT75cZaZcT/m0pSw=="], - - "@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="], - - "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], - - "@radix-ui/react-accessible-icon": ["@radix-ui/react-accessible-icon@1.1.7", "", { "dependencies": { "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A=="], - - "@radix-ui/react-accordion": ["@radix-ui/react-accordion@1.2.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collapsible": "1.1.12", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA=="], - - "@radix-ui/react-alert-dialog": ["@radix-ui/react-alert-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dialog": "1.1.15", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw=="], - - "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w=="], - - "@radix-ui/react-aspect-ratio": ["@radix-ui/react-aspect-ratio@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g=="], - - "@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.1.10", "", { "dependencies": { "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog=="], - - "@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.3.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw=="], - - "@radix-ui/react-collapsible": ["@radix-ui/react-collapsible@1.1.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA=="], - - "@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw=="], - - "@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="], - - "@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], - - "@radix-ui/react-context-menu": ["@radix-ui/react-context-menu@2.2.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww=="], - - "@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw=="], - - "@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw=="], - - "@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="], - - "@radix-ui/react-dropdown-menu": ["@radix-ui/react-dropdown-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw=="], - - "@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw=="], - - "@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw=="], - - "@radix-ui/react-form": ["@radix-ui/react-form@0.1.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-label": "2.1.7", "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ=="], - - "@radix-ui/react-hover-card": ["@radix-ui/react-hover-card@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg=="], - - "@radix-ui/react-id": ["@radix-ui/react-id@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg=="], - - "@radix-ui/react-label": ["@radix-ui/react-label@2.1.8", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A=="], - - "@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg=="], - - "@radix-ui/react-menubar": ["@radix-ui/react-menubar@1.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA=="], - - "@radix-ui/react-navigation-menu": ["@radix-ui/react-navigation-menu@1.2.14", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w=="], - - "@radix-ui/react-one-time-password-field": ["@radix-ui/react-one-time-password-field@0.1.8", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg=="], - - "@radix-ui/react-password-toggle-field": ["@radix-ui/react-password-toggle-field@0.1.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-is-hydrated": "0.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw=="], - - "@radix-ui/react-popover": ["@radix-ui/react-popover@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA=="], - - "@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.8", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw=="], - - "@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.9", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ=="], - - "@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="], - - "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-progress": ["@radix-ui/react-progress@1.1.7", "", { "dependencies": { "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg=="], - - "@radix-ui/react-radio-group": ["@radix-ui/react-radio-group@1.3.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ=="], - - "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA=="], - - "@radix-ui/react-scroll-area": ["@radix-ui/react-scroll-area@1.2.10", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A=="], - - "@radix-ui/react-select": ["@radix-ui/react-select@2.2.6", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ=="], - - "@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA=="], - - "@radix-ui/react-slider": ["@radix-ui/react-slider@1.3.6", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw=="], - - "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="], - - "@radix-ui/react-switch": ["@radix-ui/react-switch@1.2.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ=="], - - "@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.13", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A=="], - - "@radix-ui/react-toast": ["@radix-ui/react-toast@1.2.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g=="], - - "@radix-ui/react-toggle": ["@radix-ui/react-toggle@1.1.10", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ=="], - - "@radix-ui/react-toggle-group": ["@radix-ui/react-toggle-group@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-toggle": "1.1.10", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q=="], - - "@radix-ui/react-toolbar": ["@radix-ui/react-toolbar@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-separator": "1.1.7", "@radix-ui/react-toggle-group": "1.1.11" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg=="], - - "@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg=="], - - "@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="], - - "@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.2", "", { "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg=="], - - "@radix-ui/react-use-effect-event": ["@radix-ui/react-use-effect-event@0.0.2", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA=="], - - "@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.1", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g=="], - - "@radix-ui/react-use-is-hydrated": ["@radix-ui/react-use-is-hydrated@0.1.0", "", { "dependencies": { "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA=="], - - "@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="], - - "@radix-ui/react-use-previous": ["@radix-ui/react-use-previous@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ=="], - - "@radix-ui/react-use-rect": ["@radix-ui/react-use-rect@1.1.1", "", { "dependencies": { "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w=="], - - "@radix-ui/react-use-size": ["@radix-ui/react-use-size@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ=="], - - "@radix-ui/react-visually-hidden": ["@radix-ui/react-visually-hidden@1.2.3", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug=="], - - "@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="], - - "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.9", "", { "os": "android", "cpu": "arm64" }, "sha512-lcJL0bN5hpgJfSIz/8PIf02irmyL43P+j1pTCfbD1DbLkmGRuFIA4DD3B3ZOvGqG0XiVvRznbKtN0COQVaKUTg=="], - - "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-J7Zk3kLYFsLtuH6U+F4pS2sYVzac0qkjcO5QxHS7OS7yZu2LRs+IXo+uvJ/mvpyUljDJ3LROZPoQfgBIpCMhdQ=="], - - "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-iwtmmghy8nhfRGeNAIltcNXzD0QMNaaA5U/NyZc1Ia4bxrzFByNMDoppoC+hl7cDiUq5/1CnFthpT9n+UtfFyg=="], - - "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.9", "", { "os": "freebsd", "cpu": "x64" }, "sha512-DLFYI78SCiZr5VvdEplsVC2Vx53lnA4/Ga5C65iyldMVaErr86aiqCoNBLl92PXPfDtUYjUh+xFFor40ueNs4Q=="], - - "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.9", "", { "os": "linux", "cpu": "arm" }, "sha512-CsjTmTwd0Hri6iTw/DRMK7kOZ7FwAkrO4h8YWKoX/kcj833e4coqo2wzIFywtch/8Eb5enQ/lwLM7w6JX1W5RQ=="], - - "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-2x9O2JbSPxpxMDhP9Z74mahAStibTlrBMW0520+epJH5sac7/LwZW5Bmg/E6CXuEF53JJFW509uP+lSedaUNxg=="], - - "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-JA1QRW31ogheAIRhIg9tjMfsYbglXXYGNPLdPEYrwFxdbkQCAzvpSCSHCDWNl4hTtrol8WeboCSEpjdZK8qrCg=="], - - "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.9", "", { "os": "linux", "cpu": "ppc64" }, "sha512-aOKU9dJheda8Kj8Y3w9gnt9QFOO+qKPAl8SWd7JPHP+Cu0EuDAE5wokQubLzIDQWg2myXq2XhTpOVS07qqvT+w=="], - - "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.0-rc.9", "", { "os": "linux", "cpu": "s390x" }, "sha512-OalO94fqj7IWRn3VdXWty75jC5dk4C197AWEuMhIpvVv2lw9fiPhud0+bW2ctCxb3YoBZor71QHbY+9/WToadA=="], - - "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.9", "", { "os": "linux", "cpu": "x64" }, "sha512-cVEl1vZtBsBZna3YMjGXNvnYYrOJ7RzuWvZU0ffvJUexWkukMaDuGhUXn0rjnV0ptzGVkvc+vW9Yqy6h8YX4pg=="], - - "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.9", "", { "os": "linux", "cpu": "x64" }, "sha512-UzYnKCIIc4heAKgI4PZ3dfBGUZefGCJ1TPDuLHoCzgrMYPb5Rv6TLFuYtyM4rWyHM7hymNdsg5ik2C+UD9VDbA=="], - - "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.9", "", { "os": "none", "cpu": "arm64" }, "sha512-+6zoiF+RRyf5cdlFQP7nm58mq7+/2PFaY2DNQeD4B87N36JzfF/l9mdBkkmTvSYcYPE8tMh/o3cRlsx1ldLfog=="], - - "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.9", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-rgFN6sA/dyebil3YTlL2evvi/M+ivhfnyxec7AccTpRPccno/rPoNlqybEZQBkcbZu8Hy+eqNJCqfBR8P7Pg8g=="], - - "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-lHVNUG/8nlF1IQk1C0Ci574qKYyty2goMiPlRqkC5R+3LkXDkL5Dhx8ytbxq35m+pkHVIvIxviD+TWLdfeuadA=="], - - "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.9", "", { "os": "win32", "cpu": "x64" }, "sha512-G0oA4+w1iY5AGi5HcDTxWsoxF509hrFIPB2rduV5aDqS9FtDg1CAfa7V34qImbjfhIcA8C+RekocJZA96EarwQ=="], - - "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.7", "", {}, "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA=="], - - "@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="], - - "@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="], - - "@swc/core": ["@swc/core@1.15.18", "", { "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.25" }, "optionalDependencies": { "@swc/core-darwin-arm64": "1.15.18", "@swc/core-darwin-x64": "1.15.18", "@swc/core-linux-arm-gnueabihf": "1.15.18", "@swc/core-linux-arm64-gnu": "1.15.18", "@swc/core-linux-arm64-musl": "1.15.18", "@swc/core-linux-x64-gnu": "1.15.18", "@swc/core-linux-x64-musl": "1.15.18", "@swc/core-win32-arm64-msvc": "1.15.18", "@swc/core-win32-ia32-msvc": "1.15.18", "@swc/core-win32-x64-msvc": "1.15.18" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" }, "optionalPeers": ["@swc/helpers"] }, "sha512-z87aF9GphWp//fnkRsqvtY+inMVPgYW3zSlXH1kJFvRT5H/wiAn+G32qW5l3oEk63KSF1x3Ov0BfHCObAmT8RA=="], - - "@swc/core-darwin-arm64": ["@swc/core-darwin-arm64@1.15.18", "", { "os": "darwin", "cpu": "arm64" }, "sha512-+mIv7uBuSaywN3C9LNuWaX1jJJ3SKfiJuE6Lr3bd+/1Iv8oMU7oLBjYMluX1UrEPzwN2qCdY6Io0yVicABoCwQ=="], - - "@swc/core-darwin-x64": ["@swc/core-darwin-x64@1.15.18", "", { "os": "darwin", "cpu": "x64" }, "sha512-wZle0eaQhnzxWX5V/2kEOI6Z9vl/lTFEC6V4EWcn+5pDjhemCpQv9e/TDJ0GIoiClX8EDWRvuZwh+Z3dhL1NAg=="], - - "@swc/core-linux-arm-gnueabihf": ["@swc/core-linux-arm-gnueabihf@1.15.18", "", { "os": "linux", "cpu": "arm" }, "sha512-ao61HGXVqrJFHAcPtF4/DegmwEkVCo4HApnotLU8ognfmU8x589z7+tcf3hU+qBiU1WOXV5fQX6W9Nzs6hjxDw=="], - - "@swc/core-linux-arm64-gnu": ["@swc/core-linux-arm64-gnu@1.15.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-3xnctOBLIq3kj8PxOCgPrGjBLP/kNOddr6f5gukYt/1IZxsITQaU9TDyjeX6jG+FiCIHjCuWuffsyQDL5Ew1bg=="], - - "@swc/core-linux-arm64-musl": ["@swc/core-linux-arm64-musl@1.15.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-0a+Lix+FSSHBSBOA0XznCcHo5/1nA6oLLjcnocvzXeqtdjnPb+SvchItHI+lfeiuj1sClYPDvPMLSLyXFaiIKw=="], - - "@swc/core-linux-x64-gnu": ["@swc/core-linux-x64-gnu@1.15.18", "", { "os": "linux", "cpu": "x64" }, "sha512-wG9J8vReUlpaHz4KOD/5UE1AUgirimU4UFT9oZmupUDEofxJKYb1mTA/DrMj0s78bkBiNI+7Fo2EgPuvOJfuAA=="], - - "@swc/core-linux-x64-musl": ["@swc/core-linux-x64-musl@1.15.18", "", { "os": "linux", "cpu": "x64" }, "sha512-4nwbVvCphKzicwNWRmvD5iBaZj8JYsRGa4xOxJmOyHlMDpsvvJ2OR2cODlvWyGFH6BYL1MfIAK3qph3hp0Az6g=="], - - "@swc/core-win32-arm64-msvc": ["@swc/core-win32-arm64-msvc@1.15.18", "", { "os": "win32", "cpu": "arm64" }, "sha512-zk0RYO+LjiBCat2RTMHzAWaMky0cra9loH4oRrLKLLNuL+jarxKLFDA8xTZWEkCPLjUTwlRN7d28eDLLMgtUcQ=="], - - "@swc/core-win32-ia32-msvc": ["@swc/core-win32-ia32-msvc@1.15.18", "", { "os": "win32", "cpu": "ia32" }, "sha512-yVuTrZ0RccD5+PEkpcLOBAuPbYBXS6rslENvIXfvJGXSdX5QGi1ehC4BjAMl5FkKLiam4kJECUI0l7Hq7T1vwg=="], - - "@swc/core-win32-x64-msvc": ["@swc/core-win32-x64-msvc@1.15.18", "", { "os": "win32", "cpu": "x64" }, "sha512-7NRmE4hmUQNCbYU3Hn9Tz57mK9Qq4c97ZS+YlamlK6qG9Fb5g/BB3gPDe0iLlJkns/sYv2VWSkm8c3NmbEGjbg=="], - - "@swc/counter": ["@swc/counter@0.1.3", "", {}, "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="], - - "@swc/types": ["@swc/types@0.1.25", "", { "dependencies": { "@swc/counter": "^0.1.3" } }, "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g=="], - - "@tailwindcss/node": ["@tailwindcss/node@4.2.1", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.31.1", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.1" } }, "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg=="], - - "@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.1", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.1", "@tailwindcss/oxide-darwin-arm64": "4.2.1", "@tailwindcss/oxide-darwin-x64": "4.2.1", "@tailwindcss/oxide-freebsd-x64": "4.2.1", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", "@tailwindcss/oxide-linux-x64-musl": "4.2.1", "@tailwindcss/oxide-wasm32-wasi": "4.2.1", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" } }, "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw=="], - - "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.1", "", { "os": "android", "cpu": "arm64" }, "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg=="], - - "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw=="], - - "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw=="], - - "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA=="], - - "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1", "", { "os": "linux", "cpu": "arm" }, "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw=="], - - "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ=="], - - "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ=="], - - "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.1", "", { "os": "linux", "cpu": "x64" }, "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g=="], - - "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.1", "", { "os": "linux", "cpu": "x64" }, "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g=="], - - "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.1", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q=="], - - "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA=="], - - "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.1", "", { "os": "win32", "cpu": "x64" }, "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ=="], - - "@tailwindcss/vite": ["@tailwindcss/vite@4.2.1", "", { "dependencies": { "@tailwindcss/node": "4.2.1", "@tailwindcss/oxide": "4.2.1", "tailwindcss": "4.2.1" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w=="], - - "@ts-morph/common": ["@ts-morph/common@0.27.0", "", { "dependencies": { "fast-glob": "^3.3.3", "minimatch": "^10.0.1", "path-browserify": "^1.0.1" } }, "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ=="], - - "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], - - "@types/fhir": ["@types/fhir@0.0.41", "", {}, "sha512-MAQAFufNZBZ6V0F94Nhknmmh/E3iMXFK4n/L8RkSNjKtOJnvaAJERivNOj35VVx9VCQBJbE0BHSzikfBahoRhA=="], - - "@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], - - "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], - - "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], - - "@types/statuses": ["@types/statuses@2.0.6", "", {}, "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA=="], - - "@types/validate-npm-package-name": ["@types/validate-npm-package-name@4.0.2", "", {}, "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw=="], - - "@vitejs/plugin-react-swc": ["@vitejs/plugin-react-swc@4.3.0", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.7", "@swc/core": "^1.15.11" }, "peerDependencies": { "vite": "^4 || ^5 || ^6 || ^7 || ^8" } }, "sha512-mOkXCII839dHyAt/gpoSlm28JIVDwhZ6tnG6wJxUy2bmOx7UaPjvOyIDf3SFv5s7Eo7HVaq6kRcu6YMEzt5Z7w=="], - - "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], - - "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], - - "ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="], - - "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], - - "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], - - "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - - "antlr4": ["antlr4@4.9.3", "", {}, "sha512-qNy2odgsa0skmNMCuxzXhM4M8J1YDaPv3TI+vCdnOAanu0N982wBrSqziDKRDctEZLZy9VffqIZXc0UGjjSP/g=="], - - "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], - - "aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="], - - "ast-types": ["ast-types@0.16.1", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg=="], - - "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - - "baseline-browser-mapping": ["baseline-browser-mapping@2.10.8", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-PCLz/LXGBsNTErbtB6i5u4eLpHeMfi93aUv5duMmj6caNu6IphS4q6UevDnL36sZQv9lrP11dbPKGMaXPwMKfQ=="], - - "body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="], - - "brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="], - - "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], - - "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], - - "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], - - "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], - - "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], - - "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], - - "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], - - "caniuse-lite": ["caniuse-lite@1.0.30001779", "", {}, "sha512-U5og2PN7V4DMgF50YPNtnZJGWVLFjjsN3zb6uMT5VGYIewieDj1upwfuVNXf4Kor+89c3iCRJnSzMD5LmTvsfA=="], - - "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], - - "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], - - "cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="], - - "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], - - "cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="], - - "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], - - "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], - - "code-block-writer": ["code-block-writer@13.0.3", "", {}, "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg=="], - - "coffeescript": ["coffeescript@2.7.0", "", { "bin": { "coffee": "bin/coffee", "cake": "bin/cake" } }, "sha512-hzWp6TUE2d/jCcN67LrW1eh5b/rSDKQK6oD6VMLlggYVUUFexgTH9z3dNYihzX4RMhze5FTUsUmOXViJKFQR/A=="], - - "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], - - "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], - - "commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], - - "content-disposition": ["content-disposition@1.0.1", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="], - - "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], - - "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], - - "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], - - "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], - - "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], - - "cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="], - - "cosmiconfig": ["cosmiconfig@9.0.1", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ=="], - - "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], - - "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], - - "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], - - "csv-parse": ["csv-parse@4.16.3", "", {}, "sha512-cO1I/zmz4w2dcKHVvpCr7JVRu8/FymG5OEpmvsZYlccYolPBLoVGKUHgNoc4ZGkFeFlWGEDmMyBM+TTqRdW/wg=="], - - "csv-stringify": ["csv-stringify@1.1.2", "", { "dependencies": { "lodash.get": "~4.4.2" } }, "sha512-3NmNhhd+AkYs5YtM1GEh01VR6PKj6qch2ayfQaltx5xpcAdThjnbbI5eT8CzRVpXfGKAxnmrSYLsNl/4f3eWiw=="], - - "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], - - "date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="], - - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - - "decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="], - - "dedent": ["dedent@1.7.2", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA=="], - - "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], - - "default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="], - - "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="], - - "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="], - - "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], - - "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], - - "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], - - "diff": ["diff@8.0.3", "", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="], - - "dotenv": ["dotenv@17.3.1", "", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="], - - "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], - - "eciesjs": ["eciesjs@0.4.18", "", { "dependencies": { "@ecies/ciphers": "^0.2.5", "@noble/ciphers": "^1.3.0", "@noble/curves": "^1.9.7", "@noble/hashes": "^1.8.0" } }, "sha512-wG99Zcfcys9fZux7Cft8BAX/YrOJLJSZ3jyYPfhZHqN2E+Ffx+QXBDsv3gubEgPtV6dTzJMSQUwk1H98/t/0wQ=="], - - "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], - - "electron-to-chromium": ["electron-to-chromium@1.5.313", "", {}, "sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA=="], - - "emitter-component": ["emitter-component@1.1.2", "", {}, "sha512-QdXO3nXOzZB4pAjM0n6ZE+R9/+kPpECA/XSELIcc54NeYVnBqIk+4DFiBgK+8QbV3mdvTG6nedl7dTYgO+5wDw=="], - - "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], - - "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], - - "enhanced-resolve": ["enhanced-resolve@5.20.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ=="], - - "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], - - "error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="], - - "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], - - "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], - - "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], - - "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], - - "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], - - "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], - - "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], - - "eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="], - - "eventsource-parser": ["eventsource-parser@3.0.6", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="], - - "execa": ["execa@9.6.1", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA=="], - - "express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], - - "express-rate-limit": ["express-rate-limit@8.3.1", "", { "dependencies": { "ip-address": "10.1.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw=="], - - "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], - - "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], - - "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], - - "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], - - "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], - - "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], - - "fhirpath": ["fhirpath@4.9.0", "", { "dependencies": { "@lhncbc/ucum-lhc": "^5.0.0", "@loxjs/url-join": "^1.0.2", "antlr4": "~4.9.3", "commander": "^14.0.3", "date-fns": "^1.30.1", "decimal.js": "^10.6.0", "js-yaml": "^4.1.1" }, "bin": { "fhirpath": "bin/fhirpath" } }, "sha512-Pga63flBKUV93Gg704zahxfC6aKVgi1gyDt33EHeqkg4ufOyhY24qvw1W34qRbU0qR3LN9WxI6oN2aIhf5glBQ=="], - - "figures": ["figures@6.1.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="], - - "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], - - "finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="], - - "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], - - "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], - - "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], - - "fs-extra": ["fs-extra@11.3.4", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA=="], - - "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], - - "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], - - "fuzzysort": ["fuzzysort@3.1.0", "", {}, "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ=="], - - "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], - - "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], - - "get-east-asian-width": ["get-east-asian-width@1.5.0", "", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="], - - "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], - - "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="], - - "get-own-enumerable-keys": ["get-own-enumerable-keys@1.0.0", "", {}, "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA=="], - - "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], - - "get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="], - - "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - - "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], - - "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], - - "graphql": ["graphql@16.13.1", "", {}, "sha512-gGgrVCoDKlIZ8fIqXBBb0pPKqDgki0Z/FSKNiQzSGj2uEYHr1tq5wmBegGwJx6QB5S5cM0khSBpi/JFHMCvsmQ=="], - - "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], - - "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], - - "headers-polyfill": ["headers-polyfill@4.0.3", "", {}, "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ=="], - - "hl7.fhir.us.davinci-pas-generated": ["hl7.fhir.us.davinci-pas-generated@./lib/hl7.fhir.us.davinci-pas-generated.tgz", { "dependencies": { "@types/fhir": "^0.0.41" }, "peerDependencies": { "fhirpath": "^3.0.0 || ^4.0.0" } }], - - "hono": ["hono@4.12.8", "", {}, "sha512-VJCEvtrezO1IAR+kqEYnxUOoStaQPGrCmX3j4wDTNOcD1uRPFpGlwQUIW8niPuvHXaTUxeOUl5MMDGrl+tmO9A=="], - - "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], - - "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], - - "human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="], - - "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], - - "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], - - "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], - - "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], - - "ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="], - - "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], - - "is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], - - "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], - - "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], - - "is-finite": ["is-finite@1.1.0", "", {}, "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w=="], - - "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], - - "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], - - "is-in-ssh": ["is-in-ssh@1.0.0", "", {}, "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw=="], - - "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], - - "is-integer": ["is-integer@1.0.7", "", { "dependencies": { "is-finite": "^1.0.0" } }, "sha512-RPQc/s9yBHSvpi+hs9dYiJ2cuFeU6x3TyyIp8O2H6SKEltIvJOzRj9ToyvcStDvPR/pS4rxgr1oBFajQjZ2Szg=="], - - "is-interactive": ["is-interactive@2.0.0", "", {}, "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ=="], - - "is-node-process": ["is-node-process@1.2.0", "", {}, "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw=="], - - "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], - - "is-obj": ["is-obj@3.0.0", "", {}, "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ=="], - - "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], - - "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], - - "is-regexp": ["is-regexp@3.1.0", "", {}, "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA=="], - - "is-stream": ["is-stream@4.0.1", "", {}, "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A=="], - - "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], - - "is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="], - - "isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="], - - "isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], - - "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], - - "jose": ["jose@6.2.1", "", {}, "sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw=="], - - "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], - - "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], - - "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], - - "json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="], - - "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], - - "json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="], - - "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], - - "jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], - - "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], - - "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], - - "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], - - "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], - - "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], - - "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], - - "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], - - "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], - - "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], - - "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], - - "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], - - "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], - - "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], - - "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], - - "lodash.get": ["lodash.get@4.4.2", "", {}, "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ=="], - - "log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="], - - "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], - - "lucide-react": ["lucide-react@0.577.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-4LjoFv2eEPwYDPg/CUdBJQSDfPyzXCRrVW1X7jrx/trgxnxkHFjnVZINbzvzxjN70dxychOfg+FTYwBiS3pQ5A=="], - - "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], - - "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], - - "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], - - "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="], - - "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], - - "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], - - "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], - - "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], - - "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], - - "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], - - "mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="], - - "minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], - - "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], - - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - - "msw": ["msw@2.12.11", "", { "dependencies": { "@inquirer/confirm": "^5.0.0", "@mswjs/interceptors": "^0.41.2", "@open-draft/deferred-promise": "^2.2.0", "@types/statuses": "^2.0.6", "cookie": "^1.0.2", "graphql": "^16.12.0", "headers-polyfill": "^4.0.2", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "path-to-regexp": "^6.3.0", "picocolors": "^1.1.1", "rettime": "^0.10.1", "statuses": "^2.0.2", "strict-event-emitter": "^0.5.1", "tough-cookie": "^6.0.0", "type-fest": "^5.2.0", "until-async": "^3.0.2", "yargs": "^17.7.2" }, "peerDependencies": { "typescript": ">= 4.8.x" }, "optionalPeers": ["typescript"], "bin": { "msw": "cli/index.js" } }, "sha512-dVg20zi2I2EvnwH/+WupzsOC2mCa7qsIhyMAWtfRikn6RKtwL9+7SaF1IQ5LyZry4tlUtf6KyTVhnlQiZXozTQ=="], - - "mute-stream": ["mute-stream@2.0.0", "", {}, "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA=="], - - "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], - - "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], - - "next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="], - - "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], - - "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], - - "node-releases": ["node-releases@2.0.36", "", {}, "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA=="], - - "npm-run-path": ["npm-run-path@6.0.0", "", { "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA=="], - - "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], - - "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], - - "object-treeify": ["object-treeify@1.1.33", "", {}, "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A=="], - - "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], - - "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], - - "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], - - "open": ["open@11.0.0", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="], - - "ora": ["ora@8.2.0", "", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="], - - "outvariant": ["outvariant@1.4.3", "", {}, "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA=="], - - "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], - - "parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], - - "parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="], - - "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], - - "path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="], - - "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], - - "path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], - - "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - - "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], - - "pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="], - - "postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], - - "postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="], - - "powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="], - - "pretty-ms": ["pretty-ms@9.3.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ=="], - - "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], - - "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], - - "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], - - "qs": ["qs@6.15.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ=="], - - "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], - - "radix-ui": ["radix-ui@1.4.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-accessible-icon": "1.1.7", "@radix-ui/react-accordion": "1.2.12", "@radix-ui/react-alert-dialog": "1.1.15", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-aspect-ratio": "1.1.7", "@radix-ui/react-avatar": "1.1.10", "@radix-ui/react-checkbox": "1.3.3", "@radix-ui/react-collapsible": "1.1.12", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-context-menu": "2.2.16", "@radix-ui/react-dialog": "1.1.15", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-dropdown-menu": "2.1.16", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-form": "0.1.8", "@radix-ui/react-hover-card": "1.1.15", "@radix-ui/react-label": "2.1.7", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-menubar": "1.1.16", "@radix-ui/react-navigation-menu": "1.2.14", "@radix-ui/react-one-time-password-field": "0.1.8", "@radix-ui/react-password-toggle-field": "0.1.3", "@radix-ui/react-popover": "1.1.15", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-progress": "1.1.7", "@radix-ui/react-radio-group": "1.3.8", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-scroll-area": "1.2.10", "@radix-ui/react-select": "2.2.6", "@radix-ui/react-separator": "1.1.7", "@radix-ui/react-slider": "1.3.6", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-switch": "1.2.6", "@radix-ui/react-tabs": "1.1.13", "@radix-ui/react-toast": "1.2.15", "@radix-ui/react-toggle": "1.1.10", "@radix-ui/react-toggle-group": "1.1.11", "@radix-ui/react-toolbar": "1.1.11", "@radix-ui/react-tooltip": "1.2.8", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-escape-keydown": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA=="], - - "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], - - "raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="], - - "react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], - - "react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], - - "react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="], - - "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="], - - "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], - - "readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], - - "recast": ["recast@0.23.11", "", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA=="], - - "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], - - "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], - - "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], - - "restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="], - - "rettime": ["rettime@0.10.1", "", {}, "sha512-uyDrIlUEH37cinabq0AX4QbgV4HbFZ/gqoiunWQ1UqBtRvTTytwhNYjE++pO/MjPTZL5KQCf2bEoJ/BJNVQ5Kw=="], - - "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], - - "rolldown": ["rolldown@1.0.0-rc.9", "", { "dependencies": { "@oxc-project/types": "=0.115.0", "@rolldown/pluginutils": "1.0.0-rc.9" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.9", "@rolldown/binding-darwin-arm64": "1.0.0-rc.9", "@rolldown/binding-darwin-x64": "1.0.0-rc.9", "@rolldown/binding-freebsd-x64": "1.0.0-rc.9", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.9", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.9", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.9", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.9", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.9", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.9", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.9", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.9", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.9", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.9", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.9" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-9EbgWge7ZH+yqb4d2EnELAntgPTWbfL8ajiTW+SyhJEC4qhBbkCKbqFV4Ge4zmu5ziQuVbWxb/XwLZ+RIO7E8Q=="], - - "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], - - "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], - - "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], - - "safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], - - "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], - - "sax": ["sax@1.1.6", "", {}, "sha512-8zci48uUQyfqynGDSkUMD7FCJB96hwLnlZOXlgs1l3TX+LW27t3psSWKUxC0fxVgA86i8tL4NwGcY1h/6t3ESg=="], - - "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], - - "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - - "send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="], - - "serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="], - - "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], - - "shadcn": ["shadcn@4.0.8", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", "@babel/preset-typescript": "^7.27.1", "@dotenvx/dotenvx": "^1.48.4", "@modelcontextprotocol/sdk": "^1.26.0", "@types/validate-npm-package-name": "^4.0.2", "browserslist": "^4.26.2", "commander": "^14.0.0", "cosmiconfig": "^9.0.0", "dedent": "^1.6.0", "deepmerge": "^4.3.1", "diff": "^8.0.2", "execa": "^9.6.0", "fast-glob": "^3.3.3", "fs-extra": "^11.3.1", "fuzzysort": "^3.1.0", "https-proxy-agent": "^7.0.6", "kleur": "^4.1.5", "msw": "^2.10.4", "node-fetch": "^3.3.2", "open": "^11.0.0", "ora": "^8.2.0", "postcss": "^8.5.6", "postcss-selector-parser": "^7.1.0", "prompts": "^2.4.2", "recast": "^0.23.11", "stringify-object": "^5.0.0", "tailwind-merge": "^3.0.1", "ts-morph": "^26.0.0", "tsconfig-paths": "^4.2.0", "validate-npm-package-name": "^7.0.1", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "shadcn": "dist/index.js" } }, "sha512-DVAyeo95TQ/OvaHugLm5V0Dqz3al+dnoP3mZdWWxKJ33IYG1jN5B3sGZyNaYsfzm7JsWokfksSzDl83LnmMing=="], - - "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], - - "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], - - "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], - - "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], - - "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], - - "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], - - "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], - - "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], - - "sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="], - - "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], - - "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], - - "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], - - "stdin-discarder": ["stdin-discarder@0.2.2", "", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="], - - "stream": ["stream@0.0.2", "", { "dependencies": { "emitter-component": "^1.1.1" } }, "sha512-gCq3NDI2P35B2n6t76YJuOp7d6cN/C7Rt0577l91wllh0sY9ZBuw9KaSGqH/b0hzn3CWWJbpbW0W0WvQ1H/Q7g=="], - - "stream-transform": ["stream-transform@0.1.2", "", {}, "sha512-3HXId/0W8sktQnQM6rOZf2LuDDMbakMgAjpViLk758/h0br+iGqZFFfUxxJSqEvGvT742PyFr4v/TBXUtowdCg=="], - - "strict-event-emitter": ["strict-event-emitter@0.5.1", "", {}, "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ=="], - - "string-to-stream": ["string-to-stream@1.1.1", "", { "dependencies": { "inherits": "^2.0.1", "readable-stream": "^2.1.0" } }, "sha512-QySF2+3Rwq0SdO3s7BAp4x+c3qsClpPQ6abAmb0DGViiSBAkT5kL6JT2iyzEVP+T1SmzHrQD1TwlP9QAHCc+Sw=="], - - "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], - - "string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], - - "stringify-object": ["stringify-object@5.0.0", "", { "dependencies": { "get-own-enumerable-keys": "^1.0.0", "is-obj": "^3.0.0", "is-regexp": "^3.1.0" } }, "sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg=="], - - "strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], - - "strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="], - - "strip-final-newline": ["strip-final-newline@4.0.0", "", {}, "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw=="], - - "tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="], - - "tailwind-merge": ["tailwind-merge@3.5.0", "", {}, "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A=="], - - "tailwindcss": ["tailwindcss@4.2.1", "", {}, "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw=="], - - "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], - - "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], - - "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], - - "tldts": ["tldts@7.0.26", "", { "dependencies": { "tldts-core": "^7.0.26" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-WiGwQjr0qYdNNG8KpMKlSvpxz652lqa3Rd+/hSaDcY4Uo6SKWZq2LAF+hsAhUewTtYhXlorBKgNF3Kk8hnjGoQ=="], - - "tldts-core": ["tldts-core@7.0.26", "", {}, "sha512-5WJ2SqFsv4G2Dwi7ZFVRnz6b2H1od39QME1lc2y5Ew3eWiZMAeqOAfWpRP9jHvhUl881406QtZTODvjttJs+ew=="], - - "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], - - "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], - - "tough-cookie": ["tough-cookie@6.0.1", "", { "dependencies": { "tldts": "^7.0.5" } }, "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw=="], - - "ts-morph": ["ts-morph@26.0.0", "", { "dependencies": { "@ts-morph/common": "~0.27.0", "code-block-writer": "^13.0.3" } }, "sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug=="], - - "tsconfig-paths": ["tsconfig-paths@4.2.0", "", { "dependencies": { "json5": "^2.2.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg=="], - - "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="], - - "type-fest": ["type-fest@5.4.4", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw=="], - - "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="], - - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - - "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], - - "unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], - - "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], - - "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], - - "until-async": ["until-async@3.0.2", "", {}, "sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw=="], - - "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], - - "use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="], - - "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], - - "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], - - "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], - - "validate-npm-package-name": ["validate-npm-package-name@7.0.2", "", {}, "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A=="], - - "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], - - "vite": ["vite@8.0.0", "", { "dependencies": { "@oxc-project/runtime": "0.115.0", "lightningcss": "^1.32.0", "picomatch": "^4.0.3", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.9", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.0.0-alpha.31", "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-fPGaRNj9Zytaf8LEiBhY7Z6ijnFKdzU/+mL8EFBaKr7Vw1/FWcTBAMW0wLPJAGMPX38ZPVCVgLceWiEqeoqL2Q=="], - - "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], - - "which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="], - - "wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], - - "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], - - "wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="], - - "xmldoc": ["xmldoc@0.4.0", "", { "dependencies": { "sax": "~1.1.1" } }, "sha512-rJ/+/UzYCSlFNuAzGuRyYgkH2G5agdX1UQn4+5siYw9pkNC3Hu/grYNDx/dqYLreeSjnY5oKg74CMBKxJHSg6Q=="], - - "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], - - "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], - - "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], - - "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], - - "yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="], - - "yoctocolors-cjs": ["yoctocolors-cjs@2.1.3", "", {}, "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw=="], - - "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - - "zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="], - - "@dotenvx/dotenvx/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="], - - "@dotenvx/dotenvx/execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="], - - "@lhncbc/ucum-lhc/jsonfile": ["jsonfile@2.4.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw=="], - - "@radix-ui/react-alert-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-form/@radix-ui/react-label": ["@radix-ui/react-label@2.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ=="], - - "@radix-ui/react-label/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="], - - "@radix-ui/react-menu/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-popover/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-select/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-tooltip/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@tailwindcss/node/lightningcss": ["lightningcss@1.31.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.31.1", "lightningcss-darwin-arm64": "1.31.1", "lightningcss-darwin-x64": "1.31.1", "lightningcss-freebsd-x64": "1.31.1", "lightningcss-linux-arm-gnueabihf": "1.31.1", "lightningcss-linux-arm64-gnu": "1.31.1", "lightningcss-linux-arm64-musl": "1.31.1", "lightningcss-linux-x64-gnu": "1.31.1", "lightningcss-linux-x64-musl": "1.31.1", "lightningcss-win32-arm64-msvc": "1.31.1", "lightningcss-win32-x64-msvc": "1.31.1" } }, "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ=="], - - "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w=="], - - "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw=="], - - "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="], - - "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="], - - "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], - - "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - - "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], - - "express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], - - "fhirpath/date-fns": ["date-fns@1.30.1", "", {}, "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw=="], - - "log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="], - - "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - - "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], - - "prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], - - "radix-ui/@radix-ui/react-label": ["@radix-ui/react-label@2.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ=="], - - "radix-ui/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], - - "rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.9", "", {}, "sha512-w6oiRWgEBl04QkFZgmW+jnU1EC9b57Oihi2ot3HNWIQRqgHp5PnYDia5iZ5FF7rpa4EQdiqMDXjlqKGXBhsoXw=="], - - "router/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="], - - "wrap-ansi/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "@dotenvx/dotenvx/execa/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], - - "@dotenvx/dotenvx/execa/human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="], - - "@dotenvx/dotenvx/execa/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], - - "@dotenvx/dotenvx/execa/npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="], - - "@dotenvx/dotenvx/execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], - - "@dotenvx/dotenvx/execa/strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="], - - "@tailwindcss/node/lightningcss/lightningcss-android-arm64": ["lightningcss-android-arm64@1.31.1", "", { "os": "android", "cpu": "arm64" }, "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg=="], - - "@tailwindcss/node/lightningcss/lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.31.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg=="], - - "@tailwindcss/node/lightningcss/lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.31.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA=="], - - "@tailwindcss/node/lightningcss/lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.31.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A=="], - - "@tailwindcss/node/lightningcss/lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.31.1", "", { "os": "linux", "cpu": "arm" }, "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g=="], - - "@tailwindcss/node/lightningcss/lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg=="], - - "@tailwindcss/node/lightningcss/lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg=="], - - "@tailwindcss/node/lightningcss/lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.31.1", "", { "os": "linux", "cpu": "x64" }, "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA=="], - - "@tailwindcss/node/lightningcss/lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.31.1", "", { "os": "linux", "cpu": "x64" }, "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA=="], - - "@tailwindcss/node/lightningcss/lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.31.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w=="], - - "@tailwindcss/node/lightningcss/lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.31.1", "", { "os": "win32", "cpu": "x64" }, "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw=="], - - "cliui/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - - "wrap-ansi/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - } -} diff --git a/apps/consent-app/components.json b/apps/consent-app/components.json deleted file mode 100644 index 5c23ec491..000000000 --- a/apps/consent-app/components.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "$schema": "https://ui.shadcn.com/schema.json", - "style": "radix-nova", - "rsc": false, - "tsx": true, - "tailwind": { - "config": "", - "css": "src/index.css", - "baseColor": "neutral", - "cssVariables": true, - "prefix": "" - }, - "iconLibrary": "lucide", - "rtl": false, - "aliases": { - "components": "@/components", - "utils": "@/lib/utils", - "ui": "@/components/ui", - "lib": "@/lib", - "hooks": "@/hooks" - }, - "menuColor": "default", - "menuAccent": "subtle", - "registries": {} -} diff --git a/apps/consent-app/eslint.config.js b/apps/consent-app/eslint.config.js deleted file mode 100644 index 5653212f5..000000000 --- a/apps/consent-app/eslint.config.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Consent App ESLint config โ€” extends shared React config. - */ -import { defineConfig } from 'eslint/config' -import { reactConfig } from '../../eslint-config/react.js' -import path from 'path' -import { fileURLToPath } from 'url' - -const __dirname = path.dirname(fileURLToPath(import.meta.url)) - -export default defineConfig( - ...reactConfig({ tsconfigRootDir: __dirname }), -) diff --git a/apps/consent-app/package.json b/apps/consent-app/package.json deleted file mode 100644 index 1f1564d55..000000000 --- a/apps/consent-app/package.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "name": "proxy-smart-consent-app", - "displayName": "Proxy Smart Consent App", - "description": "SMART on FHIR consent management app โ€” manage FHIR Consent resources for linked Patient records.", - "private": true, - "version": "0.1.0-alpha.202605150936.c7b6b88c", - "type": "module", - "scripts": { - "dev": "vite --port 5174", - "build": "tsc -b && vite build", - "typecheck": "tsc -b", - "lint": "eslint .", - "lint:fix": "eslint . --fix", - "preview": "vite preview --port 5174" - }, - "dependencies": { - "@babelfhir-ts/client-r4": "^0.2.4", - "@fontsource-variable/geist": "^5.2.8", - "@proxy-smart/shared-ui": "github:Max-Health-Inc/shared-ui#7b20db3", - "@radix-ui/react-dialog": "^1.1.15", - "@radix-ui/react-label": "^2.1.8", - "@radix-ui/react-select": "^2.2.6", - "@radix-ui/react-slot": "^1.2.4", - "@radix-ui/react-tabs": "^1.1.13", - "@tailwindcss/vite": "^4.2.4", - "@types/fhir": "^0.0.42", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "date-fns": "^4.1.0", - "hl7.fhir.uv.smart-app-launch-generated": "../../lib/hl7.fhir.uv.smart-app-launch-generated.tgz", - "lucide-react": "^1.14.0", - "next-themes": "^0.4.6", - "radix-ui": "^1.4.3", - "react": "^19.2.5", - "react-dom": "^19.2.5", - "shadcn": "^4.6.0", - "sonner": "^2.0.7", - "tailwind-merge": "^3.5.0", - "tailwindcss": "^4.2.4" - }, - "devDependencies": { - "@eslint/js": "^10.0.1", - "@types/node": "^25.6.0", - "@types/react": "^19.2.14", - "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react-swc": "^4.3.0", - "eslint": "^10.3.0", - "eslint-plugin-react-hooks": "^7.1.1", - "eslint-plugin-react-refresh": "^0.5.2", - "globals": "^17.6.0", - "tw-animate-css": "^1.4.0", - "typescript": "6.0.3", - "typescript-eslint": "^8.59.1", - "vite": "^8.0.10" - } -} diff --git a/apps/consent-app/public/smart-manifest.json b/apps/consent-app/public/smart-manifest.json deleted file mode 100644 index edcb08de0..000000000 --- a/apps/consent-app/public/smart-manifest.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "client_name": "Consent Manager", - "client_id": "consent-app", - "description": "Patient consent management โ€” create, review and revoke FHIR Consent resources", - "client_uri": "http://localhost:5174", - "logo_uri": "http://localhost:5174/proxy-smart.svg", - "redirect_uris": ["http://localhost:5174/callback"], - "scope": "openid fhirUser patient/*.read patient/Consent.*", - "grant_types": ["authorization_code"], - "response_types": ["code"], - "token_endpoint_auth_method": "none", - "category": "administrative", - "icon": "shield-check" -} diff --git a/apps/consent-app/src/App.tsx b/apps/consent-app/src/App.tsx deleted file mode 100644 index aa15ff23f..000000000 --- a/apps/consent-app/src/App.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Dashboard } from "@/components/Dashboard" -import { SmartAppShell, ModalStackProvider } from "@proxy-smart/shared-ui" -import { smartAuth } from "@/lib/smart-auth" -import { ShieldCheck } from "lucide-react" -import "./index.css" - -export default function App() { - return ( - {children}} - > - - - ) -} diff --git a/apps/consent-app/src/components/AccessRequestForm.tsx b/apps/consent-app/src/components/AccessRequestForm.tsx deleted file mode 100644 index 9843f733f..000000000 --- a/apps/consent-app/src/components/AccessRequestForm.tsx +++ /dev/null @@ -1,368 +0,0 @@ -import { useState, useCallback, useEffect } from "react" -import type { Patient, Task } from "fhir/r4" -import { Button, Card, CardContent, CardHeader, CardTitle, Input, Label, Badge, Spinner } from "@proxy-smart/shared-ui" -import { - buildAccessRequestTask, - type AccessRequestDraft, -} from "@/lib/task-builder" -import { RESOURCE_TYPE_OPTIONS } from "@/lib/consent-builder" -import { searchPatients, formatHumanName } from "@/lib/fhir-client" -import { - ArrowLeft, - ArrowRight, - Check, - Search, - User, - Eye, - Clock, - Send, - FileText, -} from "lucide-react" -import { format } from "date-fns" - -type Step = "patient" | "resources" | "period" | "review" -const STEPS: Step[] = ["patient", "resources", "period", "review"] - -interface AccessRequestFormProps { - practitionerId: string - practitionerDisplay: string - onSubmit: (task: Task) => Promise - onCancel: () => void -} - -export function AccessRequestForm({ - practitionerId, - practitionerDisplay, - onSubmit, - onCancel, -}: AccessRequestFormProps) { - const [step, setStep] = useState("patient") - const [submitting, setSubmitting] = useState(false) - - // Patient search - const [patients, setPatients] = useState([]) - const [searchLoading, setSearchLoading] = useState(false) - const [searchQuery, setSearchQuery] = useState("") - const [selectedPatient, setSelectedPatient] = useState(null) - - // Request details - const [selectedResourceTypes, setSelectedResourceTypes] = useState([ - "Observation", - "Condition", - "MedicationRequest", - ]) - const [action, setAction] = useState<"access" | "disclose">("access") - const [periodStart, setPeriodStart] = useState(() => format(new Date(), "yyyy-MM-dd")) - const [periodEnd, setPeriodEnd] = useState(() => - format(new Date(Date.now() + 365 * 24 * 60 * 60 * 1000), "yyyy-MM-dd"), - ) - const [reason, setReason] = useState("") - - const handleSearch = useCallback(async () => { - if (!searchQuery.trim()) return - setSearchLoading(true) - try { - const results = await searchPatients(searchQuery) - setPatients(results) - } catch { - setPatients([]) - } finally { - setSearchLoading(false) - } - }, [searchQuery]) - - useEffect(() => { - // Auto-search on mount with empty query to show available patients - searchPatients("").then(setPatients).catch(() => {}) - }, []) - - const stepIndex = STEPS.indexOf(step) - const canGoNext = - step === "patient" - ? selectedPatient !== null - : step === "resources" - ? selectedResourceTypes.length > 0 - : step === "period" - ? periodStart !== "" - : true - - const goNext = () => { - const nextIdx = stepIndex + 1 - if (nextIdx < STEPS.length) setStep(STEPS[nextIdx]) - } - - const goBack = () => { - const prevIdx = stepIndex - 1 - if (prevIdx >= 0) setStep(STEPS[prevIdx]) - else onCancel() - } - - const handleSubmit = async () => { - if (!selectedPatient?.id) return - setSubmitting(true) - try { - const draft: AccessRequestDraft = { - patientId: selectedPatient.id, - patientDisplay: formatHumanName(selectedPatient.name), - practitionerId, - practitionerDisplay, - resourceTypes: selectedResourceTypes, - action, - periodStart, - periodEnd: periodEnd || undefined, - reason: reason || undefined, - } - const task = buildAccessRequestTask(draft) - await onSubmit(task) - } finally { - setSubmitting(false) - } - } - - const toggleResourceType = (code: string) => { - setSelectedResourceTypes((prev) => - prev.includes(code) ? prev.filter((c) => c !== code) : [...prev, code], - ) - } - - return ( -
- {/* Header */} -
- -
-

Request Data Access

-

- Step {stepIndex + 1} of {STEPS.length} -

-
-
- - {/* Progress bar */} -
- {STEPS.map((s, i) => ( -
- ))} -
- - {/* Step 1: Patient selection */} - {step === "patient" && ( - - - - - Select Patient - - - -
- setSearchQuery(e.target.value)} - onKeyDown={(e) => e.key === "Enter" && handleSearch()} - /> - -
- -
- {patients.map((patient) => { - const isSelected = selectedPatient?.id === patient.id - return ( - - ) - })} - {patients.length === 0 && !searchLoading && ( -

- Search for a patient to request access. -

- )} -
-
-
- )} - - {/* Step 2: Resource types + action */} - {step === "resources" && ( - - - - - Select Data Types & Action - - - -
- -
- {RESOURCE_TYPE_OPTIONS.map(({ code, label }) => ( - toggleResourceType(code)} - > - {selectedResourceTypes.includes(code) && } - {label} - - ))} -
-
- -
- -
- {(["access", "disclose"] as const).map((a) => ( - setAction(a)} - > - {a} - - ))} -
-
-
-
- )} - - {/* Step 3: Period + reason */} - {step === "period" && ( - - - - - Access Period & Reason - - - -
-
- - setPeriodStart(e.target.value)} - /> -
-
- - setPeriodEnd(e.target.value)} - /> -
-
-
- - setReason(e.target.value)} - /> -
-
-
- )} - - {/* Step 4: Review */} - {step === "review" && selectedPatient && ( - - - - - Review Access Request - - - -
-
- Patient - {formatHumanName(selectedPatient.name)} -
-
- Requester - {practitionerDisplay} -
-
- Action - {action} -
-
- Resource Types -
- {selectedResourceTypes.map((rt) => ( - {rt} - ))} -
-
-
- Period - {periodStart} โ€” {periodEnd || "No expiry"} -
- {reason && ( -
- Reason - {reason} -
- )} -
-
-
- )} - - {/* Navigation */} -
- - - {step === "review" ? ( - - ) : ( - - )} -
-
- ) -} diff --git a/apps/consent-app/src/components/AccessRequestList.tsx b/apps/consent-app/src/components/AccessRequestList.tsx deleted file mode 100644 index 7fb5f76ac..000000000 --- a/apps/consent-app/src/components/AccessRequestList.tsx +++ /dev/null @@ -1,282 +0,0 @@ -import type { Task } from "fhir/r4" -import type { TaskStatusCode } from "hl7.fhir.uv.smart-app-launch-generated/valuesets/ValueSet-TaskStatus" -import { Badge, Button, Card, CardContent, CardHeader, CardTitle, CardAction } from "@proxy-smart/shared-ui" -import { - getRequestResourceTypes, - getRequestAction, - getRequestPeriodStart, - getRequestPeriodEnd, -} from "@/lib/task-builder" -import { format } from "date-fns" -import { - ShieldCheck, - Stethoscope, - User, - Clock, - Eye, - CheckCircle, - XCircle, - Send, - Ban, - FileText, - Info, - AppWindow, -} from "lucide-react" - -interface AccessRequestListProps { - requests: Task[] - onApprove: (task: Task) => void - onReject: (task: Task) => void -} - -function getStatusBadge(status?: TaskStatusCode | string) { - switch (status) { - case "requested": - return ( - - Pending - - ) - case "ready": - return ( - - Action Required - - ) - case "draft": - return ( - - Draft - - ) - case "accepted": - return ( - - Approved - - ) - case "rejected": - return ( - - Rejected - - ) - case "cancelled": - return ( - - Cancelled - - ) - case "completed": - return ( - - Completed - - ) - default: - return {status ?? "unknown"} - } -} - -/** Determine the requester type from the Task references */ -function getRequesterType(task: Task): "practitioner" | "app" | "unknown" { - const ref = task.requester?.reference ?? "" - if (ref.startsWith("Practitioner/")) return "practitioner" - // If requester is a Patient/ or Device/ reference, this likely came from an app on their behalf - if (ref.startsWith("Patient/") || ref.startsWith("Device/") || ref.startsWith("Organization/")) return "app" - return "unknown" -} - -/** Get a meaningful requester name, avoiding confusing "Patient" labels */ -function getRequesterDisplay(task: Task): { name: string; source?: string } { - const requesterRef = task.requester?.reference ?? "" - const requesterDisplay = task.requester?.display ?? "" - const ownerDisplay = task.owner?.display ?? "" - const type = getRequesterType(task) - - // Best case: requester is a Practitioner with a display name - if (type === "practitioner" && requesterDisplay) { - return { name: requesterDisplay } - } - if (type === "practitioner" && requesterRef) { - return { name: requesterRef.replace("Practitioner/", "Dr. ") } - } - - // If the owner is a Practitioner (task was created by a practitioner app) - const ownerRef = task.owner?.reference ?? "" - if (ownerRef.startsWith("Practitioner/")) { - return { name: ownerDisplay || ownerRef.replace("Practitioner/", "Dr. ") } - } - - // Requester is a Patient or Device โ€” this came from an app - // Show the app/source info instead of the confusing "Patient ..." label - const sourceApp = task.meta?.source - if (sourceApp) { - return { name: sourceApp, source: "via app" } - } - - // For DTR tasks, try to get a meaningful name from the code - const codeDisplay = task.code?.coding?.[0]?.display - if (codeDisplay && codeDisplay !== "Access Request") { - return { name: codeDisplay } - } - - // If the display contains "via" (e.g. "Patient via Dora Health App"), - // extract the app name as the meaningful part - if (requesterDisplay.toLowerCase().includes("via ")) { - const appName = requesterDisplay.split(/via\s+/i)[1] - if (appName) return { name: appName.trim(), source: "via app" } - } - - // Last resort: show the reference type cleanly - if (requesterDisplay) return { name: requesterDisplay } - if (requesterRef) return { name: requesterRef.replace(/\//g, " ") } - - return { name: "Unknown" } -} - -/** Return a human-readable explanation of what the task is asking the patient to do */ -function getTaskExplanation(task: Task): string | null { - const isDTR = task.code?.coding?.some(c => c.code === "complete-questionnaire" || c.system?.includes("dtr")) - const isAccessRequest = task.code?.coding?.some(c => c.code === "access-request") - const type = getRequesterType(task) - const fromApp = type === "app" || type === "unknown" - - if (isDTR || task.description?.toLowerCase().includes("questionnaire")) { - return "You are being asked to complete a form. This may be required for prior authorization or documentation of a treatment." - } - if (isAccessRequest) { - if (fromApp) { - return "An application is requesting permission to access your health records on behalf of a care provider. Review the details and decide whether to approve." - } - return "A healthcare provider is requesting permission to view your health records. You can approve or reject this request." - } - if (task.status === "requested" || task.status === "ready") { - return "A request has been made that requires your review and decision." - } - return null -} - -function getPeriod(task: Task): string { - const start = getRequestPeriodStart(task) - const end = getRequestPeriodEnd(task) - if (!start) return "No period set" - const startFmt = format(new Date(start), "MMM d, yyyy") - const endFmt = end ? format(new Date(end), "MMM d, yyyy") : "No expiry" - return `${startFmt} โ€” ${endFmt}` -} - -function RequesterIcon({ type }: { type: "practitioner" | "app" | "unknown" }) { - if (type === "practitioner") return - if (type === "app") return - return -} - -export function AccessRequestList({ - requests, - onApprove, - onReject, -}: AccessRequestListProps) { - if (requests.length === 0) { - return ( -
- -

No access requests

-

- When someone requests access to your data, it will appear here for your approval. -

-
- ) - } - - return ( -
- {requests.map((task) => { - const isActionable = task.status === "requested" || task.status === "ready" - const resourceTypes = getRequestResourceTypes(task) - const action = getRequestAction(task) - const explanation = getTaskExplanation(task) - const requesterType = getRequesterType(task) - const requester = getRequesterDisplay(task) - - return ( - - - - - - {requester.name} - {requester.source && ( - - ({requester.source}) - - )} - - - {getStatusBadge(task.status)} - - - {/* Contextual explanation */} - {explanation && ( -
- - {explanation} -
- )} - -
- - - {getPeriod(task)} - - - - {action} - -
- - {/* Resource types */} - {resourceTypes.length > 0 && ( -
- {resourceTypes.map((rt) => ( - - {rt} - - ))} -
- )} - - {/* Reason */} - {task.description && task.description !== "Request to access patient data" && ( -

- “{task.description}” -

- )} - - {/* Actions for pending/ready requests */} - {isActionable && ( -
- - -
- )} -
-
- ) - })} -
- ) -} diff --git a/apps/consent-app/src/components/AuditTimeline.tsx b/apps/consent-app/src/components/AuditTimeline.tsx deleted file mode 100644 index b9bbe7e5b..000000000 --- a/apps/consent-app/src/components/AuditTimeline.tsx +++ /dev/null @@ -1,335 +0,0 @@ -import { useState, useEffect, useCallback } from "react" -import type { Consent } from "fhir/r4" -import { format } from "date-fns" -import { - ShieldOff, - PlusCircle, - Eye, - Ban, - RefreshCw, -} from "lucide-react" -import { Badge, Button, Spinner, ScrollArea } from "@proxy-smart/shared-ui" -import { config } from "@/config" -import { smartAuth } from "@/lib/smart-auth" - -// โ”€โ”€ Types โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - -interface AuditTimelineProps { - consents: Consent[] - patientId?: string -} - -/** Consent-derived event (created / revoked) */ -interface ConsentEvent { - id: string - source: "consent" - type: "created" | "revoked" - date: Date - actor: string - consentId: string - resourceTypes: string[] -} - -/** Backend access log event */ -interface AccessEvent { - id: string - source: "access" - type: "permit" | "deny" - date: Date - clientId: string - userId: string | null - username: string | null - resourceType: string | null - method: string - reason: string -} - -type TimelineEvent = ConsentEvent | AccessEvent - -// โ”€โ”€ Helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - -function deriveConsentEvents(consents: Consent[]): ConsentEvent[] { - const events: ConsentEvent[] = [] - - for (const c of consents) { - const actor = - c.provision?.actor?.[0]?.reference?.display ?? - c.provision?.actor?.[0]?.reference?.reference ?? - "Unknown" - const resourceTypes = - c.provision?.class?.map((cl) => cl.code ?? "").filter(Boolean) ?? [] - const startDate = c.provision?.period?.start - ? new Date(c.provision.period.start) - : c.meta?.lastUpdated - ? new Date(c.meta.lastUpdated) - : new Date() - - events.push({ - id: `${c.id}-created`, - source: "consent", - type: "created", - date: startDate, - actor, - consentId: c.id ?? "unknown", - resourceTypes, - }) - - if (c.status !== "active") { - events.push({ - id: `${c.id}-revoked`, - source: "consent", - type: "revoked", - date: c.meta?.lastUpdated ? new Date(c.meta.lastUpdated) : new Date(), - actor, - consentId: c.id ?? "unknown", - resourceTypes, - }) - } - } - - return events -} - -// โ”€โ”€ Sub-tab selector โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - -type AuditTab = "all" | "consent" | "access" - -// โ”€โ”€ Component โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - -export function AuditTimeline({ consents, patientId }: AuditTimelineProps) { - const [tab, setTab] = useState("all") - const [accessEvents, setAccessEvents] = useState([]) - const [accessLoading, setAccessLoading] = useState(false) - const [accessError, setAccessError] = useState(null) - - const consentEvents = deriveConsentEvents(consents) - - const fetchAccessLog = useCallback(async () => { - if (!patientId) return - setAccessLoading(true) - setAccessError(null) - try { - const authFetch = smartAuth.createAuthenticatedFetch() - const base = config.proxyBase || window.location.origin - const url = `${base}/monitoring/consent/patients/${encodeURIComponent(patientId)}/access-log?limit=200` - const resp = await authFetch(url) - if (!resp.ok) throw new Error(`HTTP ${resp.status}`) - const data = await resp.json() - const mapped: AccessEvent[] = (data.events ?? []).map( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (e: any) => ({ - id: e.id, - source: "access" as const, - type: e.decision as "permit" | "deny", - date: new Date(e.timestamp), - clientId: e.clientId, - userId: e.userId ?? null, - username: e.username ?? null, - resourceType: e.resourceType ?? null, - method: e.method, - reason: e.reason, - }), - ) - setAccessEvents(mapped) - } catch (err) { - setAccessError(err instanceof Error ? err.message : "Failed to load access log") - } finally { - setAccessLoading(false) - } - }, [patientId]) - - useEffect(() => { - if (!patientId) return - Promise.resolve() - .then(() => { - setAccessLoading(true) - setAccessError(null) - const authFetch = smartAuth.createAuthenticatedFetch() - const base = config.proxyBase || window.location.origin - const url = `${base}/monitoring/consent/patients/${encodeURIComponent(patientId)}/access-log?limit=200` - return authFetch(url) - }) - .then((resp) => { - if (!resp.ok) throw new Error(`HTTP ${resp.status}`) - return resp.json() - }) - .then((data: { events?: Array> }) => { - const mapped: AccessEvent[] = (data.events ?? []).map( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (e: any) => ({ - id: e.id, - source: "access" as const, - type: e.decision as "permit" | "deny", - date: new Date(e.timestamp), - clientId: e.clientId, - userId: e.userId ?? null, - username: e.username ?? null, - resourceType: e.resourceType ?? null, - method: e.method, - reason: e.reason, - }), - ) - setAccessEvents(mapped) - }) - .catch((err) => { - setAccessError(err instanceof Error ? err.message : "Failed to load access log") - }) - .finally(() => { - setAccessLoading(false) - }) - }, [patientId]) - - // โ”€โ”€ Merge & filter โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - - const filteredEvents: TimelineEvent[] = (() => { - const events: TimelineEvent[] = [] - if (tab === "all" || tab === "consent") events.push(...consentEvents) - if (tab === "all" || tab === "access") events.push(...accessEvents) - return events.sort((a, b) => b.date.getTime() - a.date.getTime()) - })() - - // โ”€โ”€ Render โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - - return ( -
- {/* Sub-tab bar */} -
- {(["all", "consent", "access"] as const).map((t) => ( - - ))} - - -
- - {/* Error */} - {accessError && ( -
- Failed to load access log: {accessError} -
- )} - - {/* Loading */} - {accessLoading && accessEvents.length === 0 && ( -
- -
- )} - - {/* Empty state */} - {!accessLoading && filteredEvents.length === 0 && ( -
-

No {tab === "all" ? "audit" : tab} activity yet.

-
- )} - - {/* Timeline */} - {filteredEvents.length > 0 && ( - -
-
- - {filteredEvents.map((event) => ( -
- -
- -
-
- ))} -
- - )} -
- ) -} - -// โ”€โ”€ Timeline dot โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - -function TimelineDot({ event }: { event: TimelineEvent }) { - const styles = { - created: { icon: PlusCircle, color: "text-emerald-500", bg: "bg-emerald-50 dark:bg-emerald-950" }, - revoked: { icon: ShieldOff, color: "text-red-500", bg: "bg-red-50 dark:bg-red-950" }, - permit: { icon: Eye, color: "text-blue-500", bg: "bg-blue-50 dark:bg-blue-950" }, - deny: { icon: Ban, color: "text-amber-500", bg: "bg-amber-50 dark:bg-amber-950" }, - } - const s = styles[event.type] ?? styles.permit - const Icon = s.icon - - return ( -
- -
- ) -} - -// โ”€โ”€ Event content โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - -function EventContent({ event }: { event: TimelineEvent }) { - if (event.source === "consent") { - return ( - <> -

- {event.type === "created" && "Consent granted to "} - {event.type === "revoked" && "Consent revoked for "} - {event.actor} -

-

- {format(event.date, "MMM d, yyyy 'at' h:mm a")} - {event.resourceTypes.length > 0 && ( - <> · {event.resourceTypes.join(", ")} - )} -

- - ) - } - - // Access event - const who = event.username || event.clientId - return ( - <> -

- {event.type === "permit" ? ( - <> - {who} - {" accessed "} - {event.method} - {event.resourceType && ( - {event.resourceType} - )} - - ) : ( - <> - {who} - {" was denied "} - {event.method} - {event.resourceType && ( - {event.resourceType} - )} - - )} -

-

- {format(event.date, "MMM d, yyyy 'at' h:mm a")} - <> · {event.reason} -

- - ) -} diff --git a/apps/consent-app/src/components/ConsentBuilder.tsx b/apps/consent-app/src/components/ConsentBuilder.tsx deleted file mode 100644 index 519f40ff2..000000000 --- a/apps/consent-app/src/components/ConsentBuilder.tsx +++ /dev/null @@ -1,371 +0,0 @@ -import { useState, useCallback, useEffect } from "react" -import type { Consent, Practitioner } from "fhir/r4" -import { Button, Card, CardContent, CardHeader, CardTitle, Input, Label, Badge, Spinner } from "@proxy-smart/shared-ui" -import { usePractitioners } from "@/hooks/usePractitioners" -import { buildR4Consent, RESOURCE_TYPE_OPTIONS, type ConsentDraft } from "@/lib/consent-builder" -import { formatHumanName } from "@/lib/fhir-client" -import { ArrowLeft, ArrowRight, Check, Search, FileJson, User, Eye, Clock } from "lucide-react" -import { format } from "date-fns" - -type Step = "practitioner" | "resources" | "period" | "review" -const STEPS: Step[] = ["practitioner", "resources", "period", "review"] - -interface ConsentBuilderProps { - patientId: string - patientDisplay: string - personReference: string - onSubmit: (consent: Consent) => Promise - onCancel: () => void -} - -export function ConsentBuilder({ - patientId, - patientDisplay, - personReference, - onSubmit, - onCancel, -}: ConsentBuilderProps) { - const [step, setStep] = useState("practitioner") - const [submitting, setSubmitting] = useState(false) - - // Draft state - const [selectedPractitioner, setSelectedPractitioner] = useState(null) - const [selectedResourceTypes, setSelectedResourceTypes] = useState([ - "Observation", - "Condition", - "MedicationRequest", - ]) - const [action, setAction] = useState<"access" | "disclose">("access") - const [periodStart, setPeriodStart] = useState(() => format(new Date(), "yyyy-MM-dd")) - const [periodEnd, setPeriodEnd] = useState(() => - format(new Date(Date.now() + 365 * 24 * 60 * 60 * 1000), "yyyy-MM-dd") - ) - - // Practitioner search - const { practitioners, loading: searchLoading, search } = usePractitioners() - const [searchQuery, setSearchQuery] = useState("") - - const handleSearch = useCallback(() => { - search(searchQuery) - }, [search, searchQuery]) - - // Load practitioners on mount (shows all in demo mode) - useEffect(() => { - search("") - }, [search]) - - const stepIndex = STEPS.indexOf(step) - const canGoNext = - step === "practitioner" ? selectedPractitioner !== null : - step === "resources" ? selectedResourceTypes.length > 0 : - step === "period" ? periodStart !== "" : - true - - const goNext = () => { - const nextIdx = stepIndex + 1 - if (nextIdx < STEPS.length) setStep(STEPS[nextIdx]) - } - - const goBack = () => { - const prevIdx = stepIndex - 1 - if (prevIdx >= 0) setStep(STEPS[prevIdx]) - else onCancel() - } - - const handleSubmit = async () => { - if (!selectedPractitioner?.id) return - setSubmitting(true) - try { - const draft: ConsentDraft = { - patientId, - practitionerId: selectedPractitioner.id, - practitionerDisplay: formatHumanName(selectedPractitioner.name), - resourceTypes: selectedResourceTypes, - action, - periodStart, - periodEnd: periodEnd || undefined, - } - const consent = buildR4Consent(draft, personReference) - await onSubmit(consent) - } finally { - setSubmitting(false) - } - } - - const toggleResourceType = (code: string) => { - setSelectedResourceTypes((prev) => - prev.includes(code) ? prev.filter((c) => c !== code) : [...prev, code] - ) - } - - return ( -
- {/* Header */} -
- -
-

New Consent

-

- For {patientDisplay} · Step {stepIndex + 1} of {STEPS.length} -

-
-
- - {/* Progress bar */} -
- {STEPS.map((s, i) => ( -
- ))} -
- - {/* Step 1: Practitioner selection */} - {step === "practitioner" && ( - - - - - Select Practitioner - - - -
- setSearchQuery(e.target.value)} - onKeyDown={(e) => e.key === "Enter" && handleSearch()} - /> - -
- -
- {practitioners.map((p) => { - const isSelected = selectedPractitioner?.id === p.id - return ( - - ) - })} - {!searchLoading && practitioners.length === 0 && searchQuery && ( -

- No practitioners found. Try a different search term. -

- )} -
- - {selectedPractitioner && ( -
-

Selected:

-

{formatHumanName(selectedPractitioner.name)}

-
- )} -
-
- )} - - {/* Step 2: Resource types */} - {step === "resources" && ( - - - - - Access Scope - - - -
- -
- {RESOURCE_TYPE_OPTIONS.map(({ code, label }) => { - const checked = selectedResourceTypes.includes(code) - return ( - - ) - })} -
-
- -
- -
- {(["access", "disclose"] as const).map((a) => ( - - ))} -
-
-
-
- )} - - {/* Step 3: Period */} - {step === "period" && ( - - - - - Time Period - - - -
-
- - setPeriodStart(e.target.value)} - /> -
-
- - setPeriodEnd(e.target.value)} - /> -

Leave empty for no expiry

-
-
-
-
- )} - - {/* Step 4: Review */} - {step === "review" && ( - - - - - Review Consent - - - -
-
- Patient - {patientDisplay} -
-
- Practitioner - - {selectedPractitioner ? formatHumanName(selectedPractitioner.name) : "โ€”"} - -
-
- Action - - {action === "access" ? "Read Only" : "Read + Share"} - -
-
- Period - - {periodStart} โ€” {periodEnd || "No expiry"} - -
-
- Resource Types -
- {selectedResourceTypes.map((rt) => ( - - {rt} - - ))} -
-
-
- -
-              {JSON.stringify(
-                selectedPractitioner?.id
-                  ? buildR4Consent(
-                      {
-                        patientId,
-                        practitionerId: selectedPractitioner.id,
-                        practitionerDisplay: formatHumanName(selectedPractitioner.name),
-                        resourceTypes: selectedResourceTypes,
-                        action,
-                        periodStart,
-                        periodEnd: periodEnd || undefined,
-                      },
-                      personReference
-                    )
-                  : {},
-                null,
-                2
-              )}
-            
-
-
- )} - - {/* Navigation */} -
- - {step === "review" ? ( - - ) : ( - - )} -
-
- ) -} diff --git a/apps/consent-app/src/components/ConsentDetail.tsx b/apps/consent-app/src/components/ConsentDetail.tsx deleted file mode 100644 index 3127be7cf..000000000 --- a/apps/consent-app/src/components/ConsentDetail.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import type { Consent } from "fhir/r4" -import { Card, CardContent, CardHeader, CardTitle, Badge, Button } from "@proxy-smart/shared-ui" -import { format } from "date-fns" -import { ArrowLeft, ShieldCheck, ShieldOff, Clock, User, Eye, FileJson } from "lucide-react" -import { useState } from "react" - -interface ConsentDetailProps { - consent: Consent - onBack: () => void - onRevoke: (consent: Consent) => void -} - -export function ConsentDetail({ consent, onBack, onRevoke }: ConsentDetailProps) { - const [showJson, setShowJson] = useState(false) - - const actor = consent.provision?.actor?.[0] - const actorDisplay = actor?.reference?.display ?? actor?.reference?.reference ?? "Unknown" - const resourceTypes = consent.provision?.class?.map((c) => c.code ?? "").filter(Boolean) ?? [] - const period = consent.provision?.period - const action = consent.provision?.action?.[0]?.coding?.[0]?.code ?? "access" - const isActive = consent.status === "active" - - return ( -
-
- -

Consent Details

- {isActive ? ( - Active - ) : ( - Revoked - )} -
- -
- - - - - Practitioner - - - -

{actorDisplay}

-

- Action: {action} -

-
-
- - - - - - Period - - - -

- From:{" "} - {period?.start ? format(new Date(period.start), "MMMM d, yyyy") : "Not set"} -

-

- Until:{" "} - {period?.end ? format(new Date(period.end), "MMMM d, yyyy") : "No expiry"} -

-
-
- - - - - - Accessible Resource Types - - - -
- {resourceTypes.length > 0 ? ( - resourceTypes.map((rt) => ( - - {rt} - - )) - ) : ( - All resource types - )} -
-
-
-
- -
- {isActive && ( - - )} - -
- - {showJson && ( - - -
-              {JSON.stringify(consent, null, 2)}
-            
-
-
- )} -
- ) -} diff --git a/apps/consent-app/src/components/ConsentFilters.tsx b/apps/consent-app/src/components/ConsentFilters.tsx deleted file mode 100644 index fafae93c3..000000000 --- a/apps/consent-app/src/components/ConsentFilters.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { Input } from "@proxy-smart/shared-ui" -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@proxy-smart/shared-ui" -import { Search } from "lucide-react" -import type { ConsentStatusFilter, ConsentSortKey } from "@/hooks/useConsents" - -interface ConsentFiltersProps { - searchTerm: string - onSearchChange: (value: string) => void - statusFilter: ConsentStatusFilter - onStatusChange: (value: ConsentStatusFilter) => void - sortKey: ConsentSortKey - onSortChange: (value: ConsentSortKey) => void - resultCount: number -} - -export function ConsentFilters({ - searchTerm, - onSearchChange, - statusFilter, - onStatusChange, - sortKey, - onSortChange, - resultCount, -}: ConsentFiltersProps) { - return ( -
-
- - onSearchChange(e.target.value)} - className="pl-9" - /> -
-
- - - -
- - {resultCount} result{resultCount !== 1 ? "s" : ""} - -
- ) -} diff --git a/apps/consent-app/src/components/ConsentList.tsx b/apps/consent-app/src/components/ConsentList.tsx deleted file mode 100644 index 54716a757..000000000 --- a/apps/consent-app/src/components/ConsentList.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import type { Consent } from "fhir/r4" -import { Badge, Button, Card, CardContent, CardHeader, CardTitle, CardAction } from "@proxy-smart/shared-ui" -import { format } from "date-fns" -import { ShieldCheck, ShieldOff, User, Clock, Eye } from "lucide-react" - -interface ConsentListProps { - consents: Consent[] - onRevoke: (consent: Consent) => void - onView: (consent: Consent) => void - /** Optional resolver for practitioner display names */ - getActorName?: (consent: Consent) => string -} - -function getStatusBadge(status?: string) { - switch (status) { - case "active": - return Active - case "inactive": - return Revoked - case "proposed": - return Proposed - default: - return {status ?? "unknown"} - } -} - -function getActorDisplay(consent: Consent): string { - const actor = consent.provision?.actor?.[0] - return actor?.reference?.display ?? actor?.reference?.reference ?? "Unknown practitioner" -} - -function getResourceTypes(consent: Consent): string[] { - return consent.provision?.class?.map((c) => c.code ?? "").filter(Boolean) ?? [] -} - -function getPeriod(consent: Consent): string { - const period = consent.provision?.period - if (!period) return "No period set" - const start = period.start ? format(new Date(period.start), "MMM d, yyyy") : "?" - const end = period.end ? format(new Date(period.end), "MMM d, yyyy") : "No expiry" - return `${start} โ€” ${end}` -} - -export function ConsentList({ consents, onRevoke, onView, getActorName }: ConsentListProps) { - if (consents.length === 0) { - return ( -
- -

No consents found

-

Create a new consent to grant a practitioner access to your data.

-
- ) - } - - return ( -
- {consents.map((consent) => ( - - - - - {getActorName ? getActorName(consent) : getActorDisplay(consent)} - - - {getStatusBadge(consent.status)} - - - -
- - - {getPeriod(consent)} - -
- - {getResourceTypes(consent).map((rt) => ( - - {rt} - - ))} - {getResourceTypes(consent).length === 0 && All resource types} -
-
-
- - {consent.status === "active" && ( - - )} -
-
-
- ))} -
- ) -} diff --git a/apps/consent-app/src/components/ConsentStats.tsx b/apps/consent-app/src/components/ConsentStats.tsx deleted file mode 100644 index a31b82858..000000000 --- a/apps/consent-app/src/components/ConsentStats.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { Card, CardContent } from "@proxy-smart/shared-ui" -import { ShieldCheck, ShieldOff, AlertTriangle, FileStack } from "lucide-react" - -interface ConsentStatsProps { - total: number - active: number - revoked: number - expiringSoon: number -} - -export function ConsentStats({ total, active, revoked, expiringSoon }: ConsentStatsProps) { - const cards = [ - { label: "Total Consents", value: total, icon: FileStack, color: "text-foreground" }, - { label: "Active", value: active, icon: ShieldCheck, color: "text-emerald-600 dark:text-emerald-400" }, - { label: "Revoked", value: revoked, icon: ShieldOff, color: "text-muted-foreground" }, - { label: "Expiring Soon", value: expiringSoon, icon: AlertTriangle, color: expiringSoon > 0 ? "text-amber-600 dark:text-amber-400" : "text-muted-foreground" }, - ] - - return ( -
- {cards.map(({ label, value, icon: Icon, color }) => ( - - -
- - {label} -
-

{value}

-
-
- ))} -
- ) -} diff --git a/apps/consent-app/src/components/Dashboard.tsx b/apps/consent-app/src/components/Dashboard.tsx deleted file mode 100644 index a85324c2b..000000000 --- a/apps/consent-app/src/components/Dashboard.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import { useState } from "react" -import type { Patient } from "fhir/r4" -import { Card, CardContent, CardHeader, CardTitle, CardAction, Spinner } from "@proxy-smart/shared-ui" -import { PersonCard } from "@/components/PersonCard" -import { PatientDetail } from "@/components/PatientDetail" -import { PractitionerDashboard } from "@/components/PractitionerDashboard" -import { usePerson } from "@/hooks/usePerson" -import { usePatients } from "@/hooks/usePatients" -import { formatHumanName } from "@/lib/fhir-client" -import { - Users, - Calendar, - Hash, - ChevronRight, -} from "lucide-react" - -export function Dashboard() { - const { result, loading: personLoading, error: personError } = usePerson() - // Only extract linked patients when fhirUser is a Person - const personResource = result?.resourceType === "Person" ? result.resource : null - const { patients: linkedPatients, loading: patientsLoading } = usePatients(personResource) - const [selectedPatient, setSelectedPatient] = useState(null) - - // Practitioner fhirUser โ†’ show practitioner dashboard - if (result?.resourceType === "Practitioner") { - return - } - - // When fhirUser IS the Patient, go directly to PatientDetail (no list) - const directPatient = result?.resourceType === "Patient" ? result.resource : null - - const activePatient = selectedPatient ?? directPatient - - if (activePatient && result) { - const ref = result.resourceType === "Person" - ? `Person/${result.resource.id}` - : `Patient/${result.resource.id}` - return ( - { - setSelectedPatient(null) - }} - hideBack={!!directPatient} - /> - ) - } - - if (personLoading || patientsLoading) { - return ( -
- -

Loading your health records...

-
- ) - } - - if (personError) { - return ( -
-

Failed to load identity

-

{personError}

-
- ) - } - - if (!result) { - return ( -
-

No identity found.

-
- ) - } - - return ( -
- {/* Person identity card โ€” only shown when fhirUser is a Person */} - {result.resourceType === "Person" && } - - {/* Patients list */} -
-
- -

Your Patient Records

-
- - {linkedPatients.length === 0 ? ( - - - -

No linked patients

-

Your Person resource has no linked Patient records.

-
-
- ) : ( -
- {linkedPatients.map((patient) => { - const name = formatHumanName(patient.name) - const mrn = patient.identifier?.[0]?.value - - return ( - - - - ) - })} -
- )} -
-
- ) -} diff --git a/apps/consent-app/src/components/PatientDetail.tsx b/apps/consent-app/src/components/PatientDetail.tsx deleted file mode 100644 index 4e591f04e..000000000 --- a/apps/consent-app/src/components/PatientDetail.tsx +++ /dev/null @@ -1,326 +0,0 @@ -import { useState } from "react" -import type { Patient, Consent, Task } from "fhir/r4" -import { Card, CardContent, CardHeader, CardTitle, CardAction, Badge, Button, Spinner } from "@proxy-smart/shared-ui" -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@proxy-smart/shared-ui" -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@proxy-smart/shared-ui" -import { formatHumanName } from "@/lib/fhir-client" -import { useConsents } from "@/hooks/useConsents" -import { useAccessRequests } from "@/hooks/useAccessRequests" -import { ConsentList } from "@/components/ConsentList" -import { ConsentDetail } from "@/components/ConsentDetail" -import { ConsentBuilder } from "@/components/ConsentBuilder" -import { ConsentStats } from "@/components/ConsentStats" -import { ConsentFilters } from "@/components/ConsentFilters" -import { AuditTimeline } from "@/components/AuditTimeline" -import { AccessRequestList } from "@/components/AccessRequestList" -import { usePractitionerNames } from "@/hooks/usePractitionerNames" -import { - ArrowLeft, - User, - Calendar, - Hash, - PlusCircle, - ShieldCheck, - Send, -} from "lucide-react" - -interface PatientDetailProps { - patient: Patient - personReference: string - onBack: () => void - hideBack?: boolean -} - -export function PatientDetail({ patient, personReference, onBack, hideBack }: PatientDetailProps) { - const [view, setView] = useState<"list" | "detail" | "create">("list") - const [selectedConsent, setSelectedConsent] = useState(null) - const [revokeTarget, setRevokeTarget] = useState(null) - const [approveTarget, setApproveTarget] = useState(null) - const [rejectTarget, setRejectTarget] = useState(null) - const { - consents, - allConsents, - loading, - error, - stats, - statusFilter, - setStatusFilter, - sortKey, - setSortKey, - searchTerm, - setSearchTerm, - createNewConsent, - revoke, - } = useConsents(patient.id ?? null) - const { - requests, - loading: requestsLoading, - stats: requestStats, - approveRequest, - rejectRequest, - } = useAccessRequests(patient.id ? { by: "patient", patientId: patient.id } : null) - const { getActorName } = usePractitionerNames(allConsents) - - const name = formatHumanName(patient.name) - const mrn = patient.identifier?.[0]?.value - - const confirmRevoke = async () => { - if (!revokeTarget) return - await revoke(revokeTarget) - setRevokeTarget(null) - if (view === "detail") setView("list") - } - - const confirmApprove = async () => { - if (!approveTarget) return - await approveRequest(approveTarget, personReference) - setApproveTarget(null) - } - - const confirmReject = async () => { - if (!rejectTarget) return - await rejectRequest(rejectTarget) - setRejectTarget(null) - } - - const handleCreate = async (consent: Consent) => { - await createNewConsent(consent) - setView("list") - } - - if (view === "detail" && selectedConsent) { - return ( - { - setSelectedConsent(null) - setView("list") - }} - onRevoke={setRevokeTarget} - /> - ) - } - - if (view === "create") { - return ( - setView("list")} - /> - ) - } - - return ( -
- {/* Header */} -
-
- {!hideBack && ( - - )} -

{name}

-
- -
- - {/* Patient info */} - - - - - Patient Information - - - - {stats.active} active consent{stats.active !== 1 ? "s" : ""} - - - - -
- {patient.birthDate && ( - - - Born {patient.birthDate} - - )} - {mrn && ( - - - MRN: {mrn} - - )} - {patient.gender && ( - {patient.gender} - )} -
-
-
- - {/* Stats */} - - - {/* Tabs: Consents | Access Requests | Audit Trail */} - - - Consents - - - Requests - {requestStats.pending > 0 && ( - - {requestStats.pending} - - )} - - Audit Trail - - - - {/* Filters */} - - - {loading ? ( -
- -
- ) : error ? ( - - - {error} - - - ) : ( - { - setSelectedConsent(consent) - setView("detail") - }} - getActorName={getActorName} - /> - )} -
- - - {requestsLoading ? ( -
- -
- ) : ( - - )} -
- - - - -
- - {/* Revoke confirmation dialog */} - !open && setRevokeTarget(null)}> - - - Revoke Consent - - This will revoke access for{" "} - - {revokeTarget?.provision?.actor?.[0]?.reference?.display ?? "this practitioner"} - - . They will no longer be able to access this patient's data. This action can be undone by creating a new consent. - - - - - - - - - - {/* Approve request confirmation dialog */} - !open && setApproveTarget(null)}> - - - Approve Access Request - - This will grant{" "} - - {approveTarget?.requester?.display ?? "this practitioner"} - {" "} - access to your data. A consent record will be created automatically. - - - - - - - - - - {/* Reject request confirmation dialog */} - !open && setRejectTarget(null)}> - - - Reject Access Request - - This will reject the access request from{" "} - - {rejectTarget?.requester?.display ?? "this practitioner"} - - . They will not be granted access to your data. - - - - - - - - -
- ) -} diff --git a/apps/consent-app/src/components/PersonCard.tsx b/apps/consent-app/src/components/PersonCard.tsx deleted file mode 100644 index 289e30885..000000000 --- a/apps/consent-app/src/components/PersonCard.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { formatHumanName, type Person } from "@/lib/fhir-client" -import { Card, CardContent, CardHeader, CardTitle, Badge } from "@proxy-smart/shared-ui" -import { User, Mail, Phone, Calendar } from "lucide-react" - -export function PersonCard({ person }: { person: Person }) { - const name = formatHumanName(person.name) - const email = person.telecom?.find((t) => t.system === "email")?.value - const phone = person.telecom?.find((t) => t.system === "phone")?.value - const patientCount = person.link?.filter((l) => l.target?.reference?.startsWith("Patient/")).length ?? 0 - - return ( - - - - - {name} - - - -
- {email && ( - - - {email} - - )} - {phone && ( - - - {phone} - - )} - {person.birthDate && ( - - - {person.birthDate} - - )} - {patientCount} linked patient{patientCount !== 1 ? "s" : ""} -
-
-
- ) -} diff --git a/apps/consent-app/src/components/PractitionerDashboard.tsx b/apps/consent-app/src/components/PractitionerDashboard.tsx deleted file mode 100644 index 5792e1906..000000000 --- a/apps/consent-app/src/components/PractitionerDashboard.tsx +++ /dev/null @@ -1,239 +0,0 @@ -import { useState } from "react" -import type { Practitioner, Task } from "fhir/r4" -import { Card, CardContent, CardHeader, CardTitle, CardAction, Badge, Button, Spinner } from "@proxy-smart/shared-ui" -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@proxy-smart/shared-ui" -import { AccessRequestForm } from "@/components/AccessRequestForm" -import { formatHumanName } from "@/lib/fhir-client" -import { useAccessRequests } from "@/hooks/useAccessRequests" -import { - getRequestResourceTypes, - getRequestPeriodStart, - getRequestPeriodEnd, -} from "@/lib/task-builder" -import { format } from "date-fns" -import { - User, - Stethoscope, - PlusCircle, - Send, - CheckCircle, - XCircle, - Ban, - Clock, -} from "lucide-react" - -interface PractitionerDashboardProps { - practitioner: Practitioner -} - -function getStatusBadge(status?: string) { - switch (status) { - case "requested": - return Pending - case "accepted": - return Approved - case "rejected": - return Rejected - case "cancelled": - return Cancelled - default: - return {status ?? "unknown"} - } -} - -export function PractitionerDashboard({ practitioner }: PractitionerDashboardProps) { - const [view, setView] = useState<"list" | "create">("list") - const practitionerRef = `Practitioner/${practitioner.id}` - const practitionerName = formatHumanName(practitioner.name) - - const { - requests, - loading, - stats, - createRequest, - cancelRequest, - } = useAccessRequests({ by: "requester", practitionerRef }) - - const handleCreate = async (task: Task) => { - await createRequest(task) - setView("list") - } - - if (view === "create") { - return ( - setView("list")} - /> - ) - } - - return ( -
- {/* Identity card */} - - - - - {practitionerName} - - - Practitioner - - - -
- {practitioner.identifier?.[0]?.value && ( - ID: {practitioner.identifier[0].value} - )} - {practitioner.qualification?.map((q, i) => ( - {q.code?.text ?? q.code?.coding?.[0]?.display} - ))} -
-
-
- - {/* Stats */} -
- - -

{stats.total}

-

Total Requests

-
-
- - -

{stats.pending}

-

Pending

-
-
- - -

{stats.approved}

-

Approved

-
-
- - -

{stats.rejected}

-

Rejected

-
-
-
- - {/* Action + list */} -
-

Your Access Requests

- -
- - - - All - - Pending{stats.pending > 0 && ` (${stats.pending})`} - - Resolved - - - {(["all", "pending", "resolved"] as const).map((tab) => ( - - {loading ? ( -
- -
- ) : ( - - tab === "pending" - ? r.status === "requested" - : tab === "resolved" - ? r.status !== "requested" - : true, - )} - onCancel={cancelRequest} - /> - )} -
- ))} -
-
- ) -} - -function RequestCards({ - requests, - onCancel, -}: { - requests: Task[] - onCancel: (task: Task) => Promise -}) { - if (requests.length === 0) { - return ( -
- -

No requests

-
- ) - } - - return ( -
- {requests.map((task) => { - const resourceTypes = getRequestResourceTypes(task) - const start = getRequestPeriodStart(task) - const end = getRequestPeriodEnd(task) - const period = start - ? `${format(new Date(start), "MMM d, yyyy")} โ€” ${end ? format(new Date(end), "MMM d, yyyy") : "No expiry"}` - : "No period" - - return ( - - - - - {task.for?.display ?? task.for?.reference ?? "Unknown patient"} - - {getStatusBadge(task.status)} - - -
- - - {period} - -
-
- {resourceTypes.map((rt) => ( - {rt} - ))} -
- {task.description && task.description !== "Request to access patient data" && ( -

- “{task.description}” -

- )} - {task.status === "requested" && ( -
- -
- )} -
-
- ) - })} -
- ) -} diff --git a/apps/consent-app/src/config.ts b/apps/consent-app/src/config.ts deleted file mode 100644 index 800ff8427..000000000 --- a/apps/consent-app/src/config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { createSmartAppConfig } from '@proxy-smart/shared-ui' - -export const config = createSmartAppConfig({ - clientId: 'consent-app', - scopes: 'openid fhirUser patient/*.*', -}) diff --git a/apps/consent-app/src/hooks/useAccessRequests.ts b/apps/consent-app/src/hooks/useAccessRequests.ts deleted file mode 100644 index b71142bac..000000000 --- a/apps/consent-app/src/hooks/useAccessRequests.ts +++ /dev/null @@ -1,214 +0,0 @@ -import { useState, useEffect, useCallback, useMemo } from "react" -import { toast } from "sonner" -import { - searchTasksByPatient, - searchTasksByRequester, - createTask as apiCreateTask, - updateTask as apiUpdateTask, - createConsent as apiCreateConsent, - notifyAccessRequest, - type Task, -} from "@/lib/fhir-client" -import type { TaskStatusCode } from "hl7.fhir.uv.smart-app-launch-generated/valuesets/ValueSet-TaskStatus" -import { buildR4Consent, type ConsentDraft } from "@/lib/consent-builder" -import { - getRequestResourceTypes, - getRequestAction, - getRequestPeriodStart, - getRequestPeriodEnd, -} from "@/lib/task-builder" - -type SearchMode = - | { by: "patient"; patientId: string } - | { by: "requester"; practitionerRef: string } - -export function useAccessRequests(mode: SearchMode | null) { - const [requests, setRequests] = useState([]) - const [loading, setLoading] = useState(false) - const [error, setError] = useState(null) - - // Stabilize dependencies โ€” extract primitives so useCallback doesn't depend on object identity - const modeBy = mode?.by ?? null - const modeKey = mode ? (mode.by === "patient" ? mode.patientId : mode.practitionerRef) : null - - const fetchRequests = useCallback(async () => { - if (!modeBy || !modeKey) return - - setLoading(true) - setError(null) - try { - const results = - modeBy === "patient" - ? await searchTasksByPatient(modeKey) - : await searchTasksByRequester(modeKey) - setRequests(results) - } catch (err) { - const msg = err instanceof Error ? err.message : "Failed to load access requests" - setError(msg) - toast.error("Failed to load access requests", { description: msg }) - } finally { - setLoading(false) - } - }, [modeBy, modeKey]) - - useEffect(() => { - if (!modeBy || !modeKey) return - Promise.resolve() - .then(() => { - setLoading(true) - setError(null) - return modeBy === "patient" - ? searchTasksByPatient(modeKey) - : searchTasksByRequester(modeKey) - }) - .then((results) => setRequests(results)) - .catch((err) => { - const msg = err instanceof Error ? err.message : "Failed to load access requests" - setError(msg) - toast.error("Failed to load access requests", { description: msg }) - }) - .finally(() => setLoading(false)) - }, [modeBy, modeKey]) - - // โ”€โ”€ Create (practitioner) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - - const createRequest = useCallback(async (task: Task) => { - try { - const created = await apiCreateTask(task) - setRequests((prev) => [created, ...prev]) - toast.success("Access request sent", { description: "The patient will be notified." }) - - // Fire-and-forget email notification โ€” don't block UI on failure - const patientRef = task.for?.reference - if (patientRef) { - notifyAccessRequest({ - patientReference: patientRef, - patientName: task.for?.display, - practitionerName: task.requester?.display || "A practitioner", - reason: task.description, - resourceTypes: getRequestResourceTypes(task), - }).catch(() => { /* notification failure is non-critical */ }) - } - - return created - } catch (err) { - const msg = err instanceof Error ? err.message : "Failed to send request" - toast.error("Failed to send request", { description: msg }) - throw err - } - }, []) - - // โ”€โ”€ Approve (patient) โ€” creates Consent, updates Task to accepted โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - - const approveRequest = useCallback(async (task: Task, personReference: string) => { - const id = task.id - if (!id) throw new Error("Task has no id") - - const patientRef = task.for?.reference - const patientId = patientRef?.replace("Patient/", "") - const practitionerRef = task.requester?.reference - const practitionerId = practitionerRef?.replace("Practitioner/", "") - if (!patientId || !practitionerId) throw new Error("Task missing patient or requester") - - try { - // 1. Build and create Consent from the Task data - const draft: ConsentDraft = { - patientId, - practitionerId, - practitionerDisplay: task.requester?.display, - resourceTypes: getRequestResourceTypes(task), - action: (getRequestAction(task) as "access" | "disclose") ?? "access", - periodStart: getRequestPeriodStart(task) ?? new Date().toISOString().split("T")[0], - periodEnd: getRequestPeriodEnd(task), - } - const consent = buildR4Consent(draft, personReference) - const createdConsent = await apiCreateConsent(consent) - - // 2. Update Task to accepted with consent reference in output - const updated: Task = { - ...task, - status: "accepted" satisfies TaskStatusCode, - output: [ - ...(task.output ?? []), - { - type: { - coding: [{ system: "http://proxy-smart.dev/task-output", code: "consent-reference" }], - }, - valueReference: { reference: `Consent/${createdConsent.id}` }, - }, - ], - } - await apiUpdateTask(id, updated) - - setRequests((prev) => - prev.map((r) => (r.id === id ? { ...r, status: "accepted" as const, output: updated.output } : r)), - ) - toast.success("Request approved", { description: "Consent has been created and access granted." }) - } catch (err) { - const msg = err instanceof Error ? err.message : "Failed to approve request" - toast.error("Failed to approve request", { description: msg }) - throw err - } - }, []) - - // โ”€โ”€ Reject (patient) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - - const rejectRequest = useCallback(async (task: Task) => { - const id = task.id - if (!id) throw new Error("Task has no id") - - try { - const updated: Task = { ...task, status: "rejected" satisfies TaskStatusCode } - await apiUpdateTask(id, updated) - setRequests((prev) => - prev.map((r) => (r.id === id ? { ...r, status: "rejected" as const } : r)), - ) - toast.success("Request rejected") - } catch (err) { - const msg = err instanceof Error ? err.message : "Failed to reject request" - toast.error("Failed to reject request", { description: msg }) - throw err - } - }, []) - - // โ”€โ”€ Cancel (practitioner) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - - const cancelRequest = useCallback(async (task: Task) => { - const id = task.id - if (!id) throw new Error("Task has no id") - - try { - const updated: Task = { ...task, status: "cancelled" satisfies TaskStatusCode } - await apiUpdateTask(id, updated) - setRequests((prev) => - prev.map((r) => (r.id === id ? { ...r, status: "cancelled" as const } : r)), - ) - toast.success("Request cancelled") - } catch (err) { - const msg = err instanceof Error ? err.message : "Failed to cancel request" - toast.error("Failed to cancel request", { description: msg }) - throw err - } - }, []) - - // โ”€โ”€ Derived stats โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - - const stats = useMemo(() => ({ - total: requests.length, - pending: requests.filter((r) => r.status === "requested").length, - approved: requests.filter((r) => r.status === "accepted").length, - rejected: requests.filter((r) => r.status === "rejected").length, - }), [requests]) - - return { - requests, - loading, - error, - stats, - refetch: fetchRequests, - createRequest, - approveRequest, - rejectRequest, - cancelRequest, - } -} diff --git a/apps/consent-app/src/hooks/useConsents.ts b/apps/consent-app/src/hooks/useConsents.ts deleted file mode 100644 index 2daa4bd8f..000000000 --- a/apps/consent-app/src/hooks/useConsents.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { useState, useEffect, useCallback, useMemo } from "react" -import { toast } from "sonner" -import { - searchConsents, - createConsent as apiCreateConsent, - revokeConsent as apiRevokeConsent, - type Consent, -} from "@/lib/fhir-client" - -export type ConsentStatusFilter = "all" | "active" | "inactive" -export type ConsentSortKey = "date-desc" | "date-asc" | "status" - -export function useConsents(patientId: string | null) { - const [consents, setConsents] = useState([]) - const [loading, setLoading] = useState(false) - const [error, setError] = useState(null) - const [statusFilter, setStatusFilter] = useState("all") - const [sortKey, setSortKey] = useState("date-desc") - const [searchTerm, setSearchTerm] = useState("") - - const fetchConsents = useCallback(async () => { - if (!patientId) return - - setLoading(true) - setError(null) - - try { - const results = await searchConsents(patientId) - setConsents(results) - } catch (err) { - const msg = err instanceof Error ? err.message : "Failed to load consents" - setError(msg) - toast.error("Failed to load consents", { description: msg }) - } finally { - setLoading(false) - } - }, [patientId]) - - useEffect(() => { - if (!patientId) return - Promise.resolve() - .then(() => { - setLoading(true) - setError(null) - return searchConsents(patientId) - }) - .then((results) => setConsents(results)) - .catch((err) => { - const msg = err instanceof Error ? err.message : "Failed to load consents" - setError(msg) - toast.error("Failed to load consents", { description: msg }) - }) - .finally(() => setLoading(false)) - }, [patientId]) - - // Derived: filtered and sorted consents - const filtered = useMemo(() => { - let result = consents - - // Status filter - if (statusFilter === "active") result = result.filter((c) => c.status === "active") - else if (statusFilter === "inactive") result = result.filter((c) => c.status !== "active") - - // Text search (practitioner name or resource types) - if (searchTerm.trim()) { - const q = searchTerm.toLowerCase() - result = result.filter((c) => { - const actor = c.provision?.actor?.[0]?.reference?.display ?? c.provision?.actor?.[0]?.reference?.reference ?? "" - const types = c.provision?.class?.map((cl) => cl.code ?? "").join(" ") ?? "" - return actor.toLowerCase().includes(q) || types.toLowerCase().includes(q) - }) - } - - // Sort - result = [...result].sort((a, b) => { - if (sortKey === "status") { - const sa = a.status === "active" ? 0 : 1 - const sb = b.status === "active" ? 0 : 1 - return sa - sb - } - const da = new Date(a.provision?.period?.start ?? 0).getTime() - const db = new Date(b.provision?.period?.start ?? 0).getTime() - return sortKey === "date-asc" ? da - db : db - da - }) - - return result - }, [consents, statusFilter, sortKey, searchTerm]) - - // Stats - const stats = useMemo(() => { - const now = new Date() - const sevenDays = 7 * 24 * 60 * 60 * 1000 - return { - total: consents.length, - active: consents.filter((c) => c.status === "active").length, - revoked: consents.filter((c) => c.status !== "active").length, - expiringSoon: consents.filter((c) => { - if (c.status !== "active") return false - const end = c.provision?.period?.end - if (!end) return false - const endDate = new Date(end).getTime() - return endDate > now.getTime() && endDate - now.getTime() < sevenDays - }).length, - } - }, [consents]) - - const createNewConsent = useCallback(async (consent: Consent) => { - try { - const created = await apiCreateConsent(consent) - setConsents((prev) => [created, ...prev]) - toast.success("Consent created", { description: "New consent has been recorded." }) - return created - } catch (err) { - const msg = err instanceof Error ? err.message : "Failed to create consent" - toast.error("Failed to create consent", { description: msg }) - throw err - } - }, []) - - const revoke = useCallback(async (consent: Consent) => { - const id = consent.id - if (!id) throw new Error("Consent has no id") - - try { - await apiRevokeConsent(id, consent) - setConsents((prev) => - prev.map((c) => (c.id === id ? { ...c, status: "inactive" as const } : c)) - ) - toast.success("Consent revoked", { description: "Access has been revoked." }) - } catch (err) { - const msg = err instanceof Error ? err.message : "Failed to revoke consent" - toast.error("Failed to revoke consent", { description: msg }) - throw err - } - }, []) - - return { - consents: filtered, - allConsents: consents, - loading, - error, - stats, - statusFilter, - setStatusFilter, - sortKey, - setSortKey, - searchTerm, - setSearchTerm, - refetch: fetchConsents, - createNewConsent, - revoke, - } -} diff --git a/apps/consent-app/src/hooks/usePatients.ts b/apps/consent-app/src/hooks/usePatients.ts deleted file mode 100644 index e35c8ce27..000000000 --- a/apps/consent-app/src/hooks/usePatients.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { useState, useEffect, useCallback } from "react" -import { getPatient, extractPatientLinks, type Patient, type Person } from "@/lib/fhir-client" - -export function usePatients(person: Person | null) { - const [patients, setPatients] = useState([]) - const [loading, setLoading] = useState(false) - const [error, setError] = useState(null) - - const fetchPatients = useCallback(async () => { - if (!person) return - - setLoading(true) - setError(null) - - try { - const links = extractPatientLinks(person) - const results = await Promise.allSettled( - links.map((l) => { - const id = l.reference.replace("Patient/", "") - return getPatient(id) - }) - ) - const loaded = results - .filter((r): r is PromiseFulfilledResult => r.status === "fulfilled") - .map((r) => r.value) - setPatients(loaded) - } catch (err) { - setError(err instanceof Error ? err.message : "Failed to load patients") - } finally { - setLoading(false) - } - }, [person]) - - useEffect(() => { - if (!person) return - Promise.resolve() - .then(() => { - setLoading(true) - setError(null) - const links = extractPatientLinks(person) - return Promise.allSettled( - links.map((l) => { - const id = l.reference.replace("Patient/", "") - return getPatient(id) - }) - ) - }) - .then((results) => { - const loaded = results - .filter((r): r is PromiseFulfilledResult => r.status === "fulfilled") - .map((r) => r.value) - setPatients(loaded) - }) - .catch((err) => { - setError(err instanceof Error ? err.message : "Failed to load patients") - }) - .finally(() => setLoading(false)) - }, [person]) - - return { patients, loading, error, refetch: fetchPatients } -} diff --git a/apps/consent-app/src/hooks/usePerson.ts b/apps/consent-app/src/hooks/usePerson.ts deleted file mode 100644 index d2556ef17..000000000 --- a/apps/consent-app/src/hooks/usePerson.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { useState, useEffect, useCallback } from "react" -import { getPerson, getPatient, getPractitioner, type Person, type Patient, type Practitioner } from "@/lib/fhir-client" -import { smartAuth } from "@/lib/smart-auth" - -export type FhirUserResult = - | { resourceType: "Person"; resource: Person } - | { resourceType: "Patient"; resource: Patient } - | { resourceType: "Practitioner"; resource: Practitioner } - -export function usePerson() { - const [result, setResult] = useState(null) - const [loading, setLoading] = useState(true) - const [error, setError] = useState(null) - - const fetchPerson = useCallback(async () => { - setLoading(true) - setError(null) - - try { - const token = smartAuth.getToken() - if (!token?.fhirUser) { - throw new Error("No fhirUser claim in token") - } - // fhirUser is a FHIR reference like "Patient/test-patient" or "Person/123" or "Practitioner/456" - const [resourceType, id] = token.fhirUser.split("/") - if (!resourceType || !id) { - throw new Error(`Invalid fhirUser reference: ${token.fhirUser}`) - } - - if (resourceType === "Patient") { - const resource = await getPatient(id) - setResult({ resourceType: "Patient", resource }) - } else if (resourceType === "Practitioner") { - const resource = await getPractitioner(id) - setResult({ resourceType: "Practitioner", resource }) - } else { - const resource = await getPerson(id) - setResult({ resourceType: "Person", resource }) - } - } catch (err) { - setError(err instanceof Error ? err.message : "Failed to load user") - } finally { - setLoading(false) - } - }, []) - - useEffect(() => { - fetchPerson() - }, [fetchPerson]) - - return { result, loading, error, refetch: fetchPerson } -} diff --git a/apps/consent-app/src/hooks/usePractitionerNames.ts b/apps/consent-app/src/hooks/usePractitionerNames.ts deleted file mode 100644 index c696d71f6..000000000 --- a/apps/consent-app/src/hooks/usePractitionerNames.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { useState, useEffect, useRef, useCallback } from "react" -import type { Consent } from "fhir/r4" -import { getPractitioner, formatHumanName } from "@/lib/fhir-client" - -/** - * Resolves practitioner display names for consent actor references. - * Caches results to avoid re-fetching. Only fetches when the reference - * lacks a display name (i.e. it's a bare "Practitioner/"). - */ -export function usePractitionerNames(consents: Consent[]) { - const [names, setNames] = useState>(new Map()) - const resolvedRef = useRef>(new Set()) - - const resolve = useCallback(async (refs: string[]) => { - const toResolve = refs.filter((r) => !resolvedRef.current.has(r)) - if (toResolve.length === 0) return - - // Mark as in-flight so we don't re-fetch - for (const r of toResolve) resolvedRef.current.add(r) - - const results = await Promise.allSettled( - toResolve.map(async (ref) => { - const id = ref.replace("Practitioner/", "") - const practitioner = await getPractitioner(id) - return { ref, name: formatHumanName(practitioner.name) } - }) - ) - - setNames((prev) => { - const next = new Map(prev) - for (const r of results) { - if (r.status === "fulfilled") { - next.set(r.value.ref, r.value.name) - } - } - return next - }) - }, []) - - useEffect(() => { - const bareRefs: string[] = [] - for (const consent of consents) { - const actor = consent.provision?.actor?.[0] - const ref = actor?.reference?.reference - const display = actor?.reference?.display - // Only resolve if we have a Practitioner/ reference without a display name - if (ref?.startsWith("Practitioner/") && !display) { - bareRefs.push(ref) - } - } - if (bareRefs.length > 0) resolve(bareRefs) - }, [consents, resolve]) - - /** Get display name for a consent's primary actor */ - const getActorName = useCallback( - (consent: Consent): string => { - const actor = consent.provision?.actor?.[0] - const display = actor?.reference?.display - if (display) return display - const ref = actor?.reference?.reference - if (ref && names.has(ref)) return names.get(ref)! - return ref ?? "Unknown practitioner" - }, - [names] - ) - - return { getActorName, names } -} diff --git a/apps/consent-app/src/hooks/usePractitioners.ts b/apps/consent-app/src/hooks/usePractitioners.ts deleted file mode 100644 index 3e3bc63c0..000000000 --- a/apps/consent-app/src/hooks/usePractitioners.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { useState, useCallback } from "react" -import { searchPractitioners as apiSearch, type Practitioner } from "@/lib/fhir-client" - -export function usePractitioners() { - const [practitioners, setPractitioners] = useState([]) - const [loading, setLoading] = useState(false) - const [error, setError] = useState(null) - - const search = useCallback(async (query: string) => { - if (!query.trim()) { - setPractitioners([]) - return - } - - setLoading(true) - setError(null) - - try { - const results = await apiSearch(query) - setPractitioners(results) - } catch (err) { - setError(err instanceof Error ? err.message : "Search failed") - } finally { - setLoading(false) - } - }, []) - - return { practitioners, loading, error, search } -} diff --git a/apps/consent-app/src/index.css b/apps/consent-app/src/index.css deleted file mode 100644 index 95b83d050..000000000 --- a/apps/consent-app/src/index.css +++ /dev/null @@ -1,56 +0,0 @@ -@import "tailwindcss"; -@import "tw-animate-css"; -@import "shadcn/tailwind.css"; -@import "@fontsource-variable/geist"; -@source "../node_modules/@proxy-smart/shared-ui/src"; -@import "@proxy-smart/shared-ui/theme.css"; - -@custom-variant dark (&:is(.dark *)); - -@theme inline { - --radius-sm: calc(var(--radius) - 4px); - --radius-md: calc(var(--radius) - 2px); - --radius-lg: var(--radius); - --radius-xl: calc(var(--radius) + 4px); - --color-background: var(--background); - --color-foreground: var(--foreground); - --color-card: var(--card); - --color-card-foreground: var(--card-foreground); - --color-popover: var(--popover); - --color-popover-foreground: var(--popover-foreground); - --color-primary: var(--primary); - --color-primary-foreground: var(--primary-foreground); - --color-secondary: var(--secondary); - --color-secondary-foreground: var(--secondary-foreground); - --color-muted: var(--muted); - --color-muted-foreground: var(--muted-foreground); - --color-accent: var(--accent); - --color-accent-foreground: var(--accent-foreground); - --color-destructive: var(--destructive); - --color-border: var(--border); - --color-input: var(--input); - --color-ring: var(--ring); - --color-maxhealth: var(--maxhealth); - --color-maxhealth-foreground: var(--maxhealth-foreground); - --font-sans: 'Geist Variable', sans-serif; - --color-sidebar-ring: var(--sidebar-ring); - --color-sidebar-border: var(--sidebar-border); - --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); - --color-sidebar-accent: var(--sidebar-accent); - --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); - --color-sidebar-primary: var(--sidebar-primary); - --color-sidebar-foreground: var(--sidebar-foreground); - --color-sidebar: var(--sidebar); - --color-chart-5: var(--chart-5); - --color-chart-4: var(--chart-4); - --color-chart-3: var(--chart-3); - --color-chart-2: var(--chart-2); - --color-chart-1: var(--chart-1); - --radius-2xl: calc(var(--radius) * 1.8); - --radius-3xl: calc(var(--radius) * 2.2); - --radius-4xl: calc(var(--radius) * 2.6); -} - -html { - @apply font-sans; -} \ No newline at end of file diff --git a/apps/consent-app/src/lib/auth-error.ts b/apps/consent-app/src/lib/auth-error.ts deleted file mode 100644 index 3a3d42d86..000000000 --- a/apps/consent-app/src/lib/auth-error.ts +++ /dev/null @@ -1,2 +0,0 @@ -// Re-export from shared-ui to avoid duplication -export { onAuthError, reportAuthError } from "@proxy-smart/shared-ui" diff --git a/apps/consent-app/src/lib/consent-builder.ts b/apps/consent-app/src/lib/consent-builder.ts deleted file mode 100644 index b96439c2a..000000000 --- a/apps/consent-app/src/lib/consent-builder.ts +++ /dev/null @@ -1,117 +0,0 @@ -import type { Consent, CodeableConcept } from "fhir/r4" - -export type ConsentDraft = { - patientId: string - practitionerId: string - practitionerDisplay?: string - resourceTypes: string[] - action: "access" | "disclose" - periodStart: string - periodEnd?: string -} - -const CONSENT_SCOPE: CodeableConcept = { - coding: [ - { - system: "http://terminology.hl7.org/CodeSystem/consentscope", - code: "patient-privacy", - display: "Privacy Consent", - }, - ], -} - -const CONSENT_CATEGORY: CodeableConcept[] = [ - { - coding: [ - { - system: "http://loinc.org", - code: "57016-8", - display: "Privacy policy acknowledgment Document", - }, - ], - }, -] - -/** - * Build a FHIR R4 Consent resource from a draft. - */ -export function buildR4Consent( - draft: ConsentDraft, - performerReference: string, -): Consent { - const consent: Consent = { - resourceType: "Consent", - status: "active", - scope: CONSENT_SCOPE, - category: CONSENT_CATEGORY, - patient: { reference: `Patient/${draft.patientId}` }, - dateTime: new Date().toISOString(), - performer: [{ reference: performerReference }], - policyRule: { - coding: [ - { - system: "http://terminology.hl7.org/CodeSystem/v3-ActCode", - code: "OPTIN", - display: "opt-in", - }, - ], - }, - provision: { - type: "permit", - period: { - start: draft.periodStart, - ...(draft.periodEnd ? { end: draft.periodEnd } : {}), - }, - actor: [ - { - role: { - coding: [ - { - system: "http://terminology.hl7.org/CodeSystem/v3-ParticipationType", - code: "PRCP", - display: "primary information recipient", - }, - ], - }, - reference: { - reference: `Practitioner/${draft.practitionerId}`, - ...(draft.practitionerDisplay - ? { display: draft.practitionerDisplay } - : {}), - }, - }, - ], - action: [ - { - coding: [ - { - system: "http://terminology.hl7.org/CodeSystem/consentaction", - code: draft.action, - display: draft.action === "access" ? "Access" : "Disclose", - }, - ], - }, - ], - class: draft.resourceTypes.map((code) => ({ - system: "http://hl7.org/fhir/resource-types", - code, - })), - }, - } - - return consent -} - -/** Common FHIR resource types relevant for consent */ -export const RESOURCE_TYPE_OPTIONS = [ - { code: "Observation", label: "Observations" }, - { code: "Condition", label: "Conditions" }, - { code: "MedicationRequest", label: "Medications" }, - { code: "DiagnosticReport", label: "Diagnostic Reports" }, - { code: "Encounter", label: "Encounters" }, - { code: "AllergyIntolerance", label: "Allergies" }, - { code: "Procedure", label: "Procedures" }, - { code: "Immunization", label: "Immunizations" }, - { code: "CarePlan", label: "Care Plans" }, - { code: "DocumentReference", label: "Documents" }, -] as const diff --git a/apps/consent-app/src/lib/fhir-client.ts b/apps/consent-app/src/lib/fhir-client.ts deleted file mode 100644 index e12804be1..000000000 --- a/apps/consent-app/src/lib/fhir-client.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { smartAuth, fhirBaseUrl } from "@/lib/smart-auth" -import { config } from "@/config" -import { createAuthFetch } from "@proxy-smart/shared-ui" -import { - FhirResourceReader, - FhirResourceWriter, -} from "@babelfhir-ts/client-r4" -import type { - Patient, - Person, - Consent, - Practitioner, - Task, -} from "fhir/r4" - -export type { Patient, Person, Consent, Practitioner, Task } - -// โ”€โ”€ Generated FHIR client with authenticated fetch โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - -const authFetch = createAuthFetch(smartAuth) - -const persons = new FhirResourceReader(fhirBaseUrl, "Person", authFetch) -const patients = new FhirResourceReader(fhirBaseUrl, "Patient", authFetch) -const practitioners = new FhirResourceReader(fhirBaseUrl, "Practitioner", authFetch) -const consents = new FhirResourceReader(fhirBaseUrl, "Consent", authFetch) -const consentWriter = new FhirResourceWriter(fhirBaseUrl, "Consent", authFetch) -const tasks = new FhirResourceReader(fhirBaseUrl, "Task", authFetch) -const taskWriter = new FhirResourceWriter(fhirBaseUrl, "Task", authFetch) - -// โ”€โ”€ Resource operations โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - -export async function getPerson(id: string): Promise { - return persons.read(id) -} - -export async function getPatient(id: string): Promise { - return patients.read(id) -} - -export async function getPractitioner(id: string): Promise { - return practitioners.read(id) -} - -export async function searchPractitioners(name: string): Promise { - return practitioners.searchAll({ name, _count: 20 }) -} - -export async function searchConsents(patientId: string): Promise { - return consents.searchAll({ - patient: `Patient/${patientId}`, - _count: 50, - _sort: "-date", - }) -} - -export async function createConsent(consent: Consent): Promise { - return consentWriter.create(consent) -} - -export async function updateConsent(id: string, consent: Consent): Promise { - return consentWriter.update({ ...consent, id } as Consent & { id: string }) -} - -export async function revokeConsent(id: string, consent: Consent): Promise { - const revoked: Consent = { ...consent, status: "inactive" } - return updateConsent(id, revoked) -} - -// โ”€โ”€ Task operations โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - -export async function searchTasksByPatient(patientId: string): Promise { - return tasks.searchAll({ - patient: `Patient/${patientId}`, - code: "access-request", - _count: 50, - _sort: "-authored-on", - }) -} - -export async function searchTasksByRequester(practitionerRef: string): Promise { - return tasks.searchAll({ - requester: practitionerRef, - code: "access-request", - _count: 50, - _sort: "-authored-on", - }) -} - -export async function createTask(task: Task): Promise { - return taskWriter.create(task) -} - -export async function updateTask(id: string, task: Task): Promise { - return taskWriter.update({ ...task, id } as Task & { id: string }) -} - -// โ”€โ”€ Patient search (for practitioner use) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - -export async function searchPatients(query: string): Promise { - return patients.searchAll({ name: query, _count: 20 }) -} - -// โ”€โ”€ Helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - -/** Extract Patient references from Person.link[] */ -export function extractPatientLinks(person: Person): Array<{ reference: string; display?: string }> { - if (!person.link) return [] - return person.link - .filter((l) => l.target?.reference?.startsWith("Patient/")) - .map((l) => ({ - reference: l.target!.reference!, - display: l.target?.display, - })) -} - -// Re-export from shared-ui to avoid duplication -export { formatHumanName } from "@proxy-smart/shared-ui" - -// โ”€โ”€ Backend API notifications โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - -const backendBase = `${config.proxyBase}/${config.proxyPrefix}` - -export interface NotifyAccessRequestParams { - patientReference: string - patientName?: string - practitionerName: string - reason?: string - resourceTypes?: string[] -} - -/** - * Notify a patient via email that a practitioner has requested access. - * Fire-and-forget โ€” failures are logged but don't block the UI flow. - */ -export async function notifyAccessRequest(params: NotifyAccessRequestParams): Promise { - try { - const res = await authFetch(`${backendBase}/api/consent/notify-access-request`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(params), - }) - if (!res.ok) return false - const data = await res.json() as { sent?: boolean } - return data.sent === true - } catch { - return false - } -} diff --git a/apps/consent-app/src/lib/smart-auth.ts b/apps/consent-app/src/lib/smart-auth.ts deleted file mode 100644 index a884c423f..000000000 --- a/apps/consent-app/src/lib/smart-auth.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { config } from "@/config" -import { SmartAuth } from "@babelfhir-ts/client-r4" -import { createSmartAuth } from "@proxy-smart/shared-ui" - -export const { smartAuth, fhirBaseUrl } = createSmartAuth({ - config, - SmartAuth, - storagePrefix: "consent_app_", -}) diff --git a/apps/consent-app/src/lib/task-builder.ts b/apps/consent-app/src/lib/task-builder.ts deleted file mode 100644 index e6ef5ad39..000000000 --- a/apps/consent-app/src/lib/task-builder.ts +++ /dev/null @@ -1,113 +0,0 @@ -import type { Task } from "fhir/r4" -import type { TaskStatusCode } from "hl7.fhir.uv.smart-app-launch-generated/valuesets/ValueSet-TaskStatus" - -export type AccessRequestDraft = { - patientId: string - patientDisplay?: string - practitionerId: string - practitionerDisplay?: string - resourceTypes: string[] - action: "access" | "disclose" - periodStart: string - periodEnd?: string - reason?: string -} - -const ACCESS_REQUEST_CODE = { - coding: [ - { - system: "http://proxy-smart.dev/task-type", - code: "access-request", - display: "Access Request", - }, - ], -} - -/** - * Build a FHIR R4 Task resource representing a practitioner's request - * to access a patient's data. - */ -export function buildAccessRequestTask(draft: AccessRequestDraft): Task { - const task: Task = { - resourceType: "Task", - status: "requested" satisfies TaskStatusCode, - intent: "order" satisfies Task["intent"], - code: ACCESS_REQUEST_CODE, - description: draft.reason || "Request to access patient data", - for: { - reference: `Patient/${draft.patientId}`, - ...(draft.patientDisplay ? { display: draft.patientDisplay } : {}), - }, - requester: { - reference: `Practitioner/${draft.practitionerId}`, - ...(draft.practitionerDisplay ? { display: draft.practitionerDisplay } : {}), - }, - authoredOn: new Date().toISOString(), - input: [ - { - type: { - coding: [{ system: "http://proxy-smart.dev/task-input", code: "resource-types" }], - }, - valueString: draft.resourceTypes.join(","), - }, - { - type: { - coding: [{ system: "http://proxy-smart.dev/task-input", code: "action" }], - }, - valueString: draft.action, - }, - { - type: { - coding: [{ system: "http://proxy-smart.dev/task-input", code: "period-start" }], - }, - valueString: draft.periodStart, - }, - ...(draft.periodEnd - ? [ - { - type: { - coding: [{ system: "http://proxy-smart.dev/task-input", code: "period-end" }], - }, - valueString: draft.periodEnd, - }, - ] - : []), - ], - } - return task -} - -// โ”€โ”€ Helpers to extract structured data from a Task โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - -function getInputValue(task: Task, code: string): string | undefined { - return task.input?.find( - (i) => i.type?.coding?.[0]?.code === code, - )?.valueString -} - -export function getRequestResourceTypes(task: Task): string[] { - const val = getInputValue(task, "resource-types") - return val ? val.split(",").filter(Boolean) : [] -} - -export function getRequestAction(task: Task): string { - return getInputValue(task, "action") ?? "access" -} - -export function getRequestPeriodStart(task: Task): string | undefined { - return getInputValue(task, "period-start") -} - -export function getRequestPeriodEnd(task: Task): string | undefined { - return getInputValue(task, "period-end") -} - -export function getRequestConsentRef(task: Task): string | undefined { - return task.output?.find( - (o) => o.type?.coding?.[0]?.code === "consent-reference", - )?.valueReference?.reference -} - -export function isAccessRequest(task: Task): boolean { - return task.code?.coding?.some((c) => c.code === "access-request") ?? false -} diff --git a/apps/consent-app/src/vite-env.d.ts b/apps/consent-app/src/vite-env.d.ts deleted file mode 100644 index 1d1cf1743..000000000 --- a/apps/consent-app/src/vite-env.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -/// - -interface ImportMetaEnv { - readonly VITE_PROXY_BASE: string - readonly VITE_PROXY_PREFIX: string - readonly VITE_FHIR_SERVER_ID: string - readonly VITE_FHIR_VERSION: string - readonly VITE_CLIENT_ID: string - readonly VITE_REDIRECT_URI: string - readonly VITE_SCOPES: string -} - -interface ImportMeta { - readonly env: ImportMetaEnv -} diff --git a/apps/consent-app/vite.config.ts b/apps/consent-app/vite.config.ts deleted file mode 100644 index 0669a0f3c..000000000 --- a/apps/consent-app/vite.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { createSmartViteConfig } from '../../config/vite-config' - -export default createSmartViteConfig( - { base: '/apps/consent/', port: 5174 }, - __dirname, -) diff --git a/apps/dtr-app/README.md b/apps/dtr-app/README.md deleted file mode 100644 index b0c4dabcb..000000000 --- a/apps/dtr-app/README.md +++ /dev/null @@ -1,149 +0,0 @@ -# Proxy Smart DTR App - -Da Vinci DTR (Documentation Templates & Rules) โ€” a SMART on FHIR app for prior authorization documentation, questionnaire rendering, and PAS (Prior Authorization Support) claim submission. - -## BabelFHIR-TS in Action - -This app is a real-world demonstration of [BabelFHIR-TS](https://github.com/Max-Health-Inc/BabelFHIR-TS) โ€” a code generator that converts FHIR Implementation Guide (IG) profiles into **type-safe TypeScript interfaces** with built-in validation. - -### The Problem - -FHIR R4 base types (`Claim`, `ClaimResponse`, `Coverage`, etc.) are generic โ€” they represent every possible shape a resource could take. But Da Vinci PAS [IG 2.0.1](http://hl7.org/fhir/us/davinci-pas/STU2/) constrains those resources heavily: - -- A `PASClaim` must have `status: "active"`, `use: "preauthorization"`, required `identifier[]`, and items with specific extension structures -- A `PASClaimResponse` requires `status: "active"`, `use: "preauthorization"`, with adjudication items carrying review action extensions -- `PASCoverage` locks `status: "active"` and mandates `payor`, `relationship`, and `subscriber` as Must Support - -Without BabelFHIR-TS, developers either: -1. **Use raw `fhir/r4` types** โ€” no compiler help, profile violations caught only at runtime (or worse, by the payer) -2. **Hand-write profile interfaces** โ€” tedious, error-prone, drifts from the IG spec - -### The Solution - -One command generates the entire PAS type system: - -```bash -npx babelfhir-ts install hl7.fhir.us.davinci-pas@2.0.1 -``` - -This produces `hl7.fhir.us.davinci-pas-generated` โ€” a local npm package with **150+ TypeScript interfaces**, runtime validators, and helper classes covering every profile in the IG. - -### How This App Uses It - -#### Type-Safe Claim Construction (`pas-builder.ts`) - -The claim builder returns `PASClaim` instead of a generic `Claim`. The compiler enforces PAS constraints at build time: - -```typescript -import type { PASClaim } from "hl7.fhir.us.davinci-pas-generated" - -export function buildPasClaim({ patient, service }): PASClaim { - // TypeScript enforces: - // - status MUST be "active" (not "draft", "cancelled", etc.) - // - use MUST be "preauthorization" - // - identifier[] is required (not optional like base Claim) - // - item[].productOrService requires data-absent-reason coding - // - item[].category is required (optional in base Claim) - const claim: PASClaim = { - resourceType: "Claim", - status: "active", // โ† literal type, not string - use: "preauthorization", // โ† literal type, not string - identifier: [{ system: "...", value: crypto.randomUUID() }], - // ... - } - return claim -} -``` - -Try changing `status: "active"` to `status: "draft"` โ€” the compiler rejects it immediately. - -#### FHIR Client with PAS Types (`fhir-client.ts`) - -The FHIR client uses PAS-profiled types for every PAS-relevant operation: - -```typescript -import type { - PASClaim, PASCoverage, PASOrganization, - PASServiceRequest, PASBeneficiary, PASInsurer, - PASRequestBundle, PASResponseBundle, - // ... 13 PAS types total -} from "hl7.fhir.us.davinci-pas-generated" -import type { PASClaimResponse } from "hl7.fhir.us.davinci-pas-generated/PASClaimResponse" - -// Submit returns PASClaimResponse with typed adjudication + review actions -export async function submitClaim(claim: PASClaim): Promise { ... } - -// Coverage search returns PASCoverage (status locked to "active", payor required) -export async function searchCoverage(patientId: string): Promise { ... } - -// Organization fetch returns PASOrganization (active + name are required) -export async function getOrganization(id: string): Promise { ... } -``` - -Generic EHR resources (`Patient`, `Questionnaire`, `Condition`) correctly stay as base `fhir/r4` types โ€” they aren't PAS-profiled. - -#### Compile-Time IG Compliance - -The PAS IG requires `item[].productOrService` to use a specific `data-absent-reason` coding, with the actual service code in an extension. BabelFHIR-TS enforces this: - -```typescript -// This is the ONLY shape the compiler accepts for productOrService: -productOrService: { - coding: [{ - system: "http://terminology.hl7.org/CodeSystem/data-absent-reason", // โ† literal - code: "not-applicable", // โ† literal - }], -}, -// Real service code goes in the extension (PAS IG pattern): -extension: [{ - url: "http://hl7.org/fhir/us/davinci-pas/StructureDefinition/extension-requestedService", - valueReference: { display: "99213 - Office Visit" }, -}], -``` - -Without generated types, nothing stops you from putting a CPT code directly in `productOrService` โ€” which would be rejected by a PAS-compliant payer. - -### What You Get - -| Capability | Without BabelFHIR-TS | With BabelFHIR-TS | -|---|---|---| -| Profile constraints | Runtime errors from payer | Compile-time type errors | -| Required fields | Easy to forget, silent failures | Compiler enforces them | -| Literal value types | `status: string` | `status: "active"` | -| Extension patterns | No guidance, manual wiring | Typed extension interfaces | -| IG version tracking | Manual, error-prone | Package versioned to IG (2.0.1) | -| Onboarding | Read the IG spec | Read the types | - -### Generated Package Contents - -The `hl7.fhir.us.davinci-pas-generated@2.0.1` package includes: - -- **150+ TypeScript interfaces** โ€” one per profile, extension, and backbone element -- **Runtime validators** โ€” `validatePASClaim(resource)` returns `{ errors, warnings }` -- **Helper classes** โ€” `PASClaimClass`, `PASClaimResponseClass` for building resources programmatically -- **Value set enums** โ€” coded types like `X12278DiagnosisTypeCode`, `X12278RequestedServiceTypeCode` -- **FHIR client stubs** โ€” `FhirClient`, `FhirReadClient`, `FhirWriteClient` for typed FHIR operations - -## Stack - -- **React 19** + **TypeScript 5.9** + **Vite 8** -- **shadcn/ui** component library -- **SMART on FHIR** launch via Proxy Smart backend -- **fhir/r4** for base FHIR types -- **hl7.fhir.us.davinci-pas-generated** for PAS profile types (generated by BabelFHIR-TS) - -## Development - -```bash -# Install dependencies -bun install - -# Generate PAS types from the IG (requires babelfhir-ts) -npx babelfhir-ts install hl7.fhir.us.davinci-pas@2.0.1 - -# Start dev server -bun dev - -# Type-check -npx tsc --noEmit -``` diff --git a/apps/dtr-app/components.json b/apps/dtr-app/components.json deleted file mode 100644 index 5c23ec491..000000000 --- a/apps/dtr-app/components.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "$schema": "https://ui.shadcn.com/schema.json", - "style": "radix-nova", - "rsc": false, - "tsx": true, - "tailwind": { - "config": "", - "css": "src/index.css", - "baseColor": "neutral", - "cssVariables": true, - "prefix": "" - }, - "iconLibrary": "lucide", - "rtl": false, - "aliases": { - "components": "@/components", - "utils": "@/lib/utils", - "ui": "@/components/ui", - "lib": "@/lib", - "hooks": "@/hooks" - }, - "menuColor": "default", - "menuAccent": "subtle", - "registries": {} -} diff --git a/apps/dtr-app/eslint.config.js b/apps/dtr-app/eslint.config.js deleted file mode 100644 index f75dee039..000000000 --- a/apps/dtr-app/eslint.config.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * DTR App ESLint config โ€” extends shared React config. - */ -import { defineConfig } from 'eslint/config' -import { reactConfig } from '../../eslint-config/react.js' -import path from 'path' -import { fileURLToPath } from 'url' - -const __dirname = path.dirname(fileURLToPath(import.meta.url)) - -export default defineConfig( - ...reactConfig({ tsconfigRootDir: __dirname }), -) diff --git a/apps/dtr-app/index.html b/apps/dtr-app/index.html deleted file mode 100644 index 5c3a4ac8b..000000000 --- a/apps/dtr-app/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - Prior Authorization โ€” Proxy Smart DTR - - -
- - - diff --git a/apps/dtr-app/lib/hl7.fhir.us.davinci-dtr-generated.tgz b/apps/dtr-app/lib/hl7.fhir.us.davinci-dtr-generated.tgz deleted file mode 100644 index 3facd3997..000000000 Binary files a/apps/dtr-app/lib/hl7.fhir.us.davinci-dtr-generated.tgz and /dev/null differ diff --git a/apps/dtr-app/lib/hl7.fhir.us.davinci-pas-generated.tgz b/apps/dtr-app/lib/hl7.fhir.us.davinci-pas-generated.tgz deleted file mode 100644 index 5a340aba6..000000000 Binary files a/apps/dtr-app/lib/hl7.fhir.us.davinci-pas-generated.tgz and /dev/null differ diff --git a/apps/dtr-app/package.json b/apps/dtr-app/package.json deleted file mode 100644 index e3dc69d74..000000000 --- a/apps/dtr-app/package.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "name": "proxy-smart-dtr-app", - "displayName": "Proxy Smart DTR App", - "description": "Da Vinci DTR (Documentation Templates & Rules) โ€” SMART on FHIR app for prior authorization documentation, questionnaire rendering, and CQL-based auto-population.", - "private": true, - "version": "0.1.0-alpha.202605150936.c7b6b88c", - "type": "module", - "scripts": { - "dev": "vite --port 5175", - "build": "tsc -b && vite build", - "typecheck": "tsc -b", - "lint": "eslint .", - "lint:fix": "eslint . --fix", - "preview": "vite preview --port 5175" - }, - "dependencies": { - "@aehrc/smart-forms-renderer": "^1.3.1", - "@babelfhir-ts/client-r4": "^0.2.4", - "@fontsource-variable/geist": "^5.2.8", - "@proxy-smart/shared-ui": "github:Max-Health-Inc/shared-ui", - "@radix-ui/react-accordion": "^1.2.12", - "@radix-ui/react-checkbox": "^1.3.3", - "@radix-ui/react-dialog": "^1.1.15", - "@radix-ui/react-label": "^2.1.8", - "@radix-ui/react-progress": "^1.1.8", - "@radix-ui/react-radio-group": "^1.3.8", - "@radix-ui/react-select": "^2.2.6", - "@radix-ui/react-separator": "^1.1.8", - "@radix-ui/react-slot": "^1.2.4", - "@radix-ui/react-switch": "^1.2.6", - "@radix-ui/react-tabs": "^1.1.13", - "@radix-ui/react-tooltip": "^1.2.8", - "@tailwindcss/vite": "^4.2.4", - "@types/fhir": "^0.0.42", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "date-fns": "^4.1.0", - "hl7.fhir.us.davinci-dtr-generated": "./lib/hl7.fhir.us.davinci-dtr-generated.tgz", - "hl7.fhir.us.davinci-pas-generated": "./lib/hl7.fhir.us.davinci-pas-generated.tgz", - "lucide-react": "^1.14.0", - "next-themes": "^0.4.6", - "radix-ui": "^1.4.3", - "react": "^19.2.5", - "react-dom": "^19.2.5", - "shadcn": "^4.6.0", - "sonner": "^2.0.7", - "tailwind-merge": "^3.5.0", - "tailwindcss": "^4.2.4" - }, - "devDependencies": { - "@eslint/js": "^10.0.1", - "@types/node": "^25.6.0", - "@types/react": "^19.2.14", - "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react-swc": "^4.3.0", - "eslint": "^10.3.0", - "eslint-plugin-react-hooks": "^7.1.1", - "eslint-plugin-react-refresh": "^0.5.2", - "globals": "^17.6.0", - "tw-animate-css": "^1.4.0", - "typescript": "6.0.3", - "typescript-eslint": "^8.59.1", - "vite": "^8.0.10" - } -} diff --git a/apps/dtr-app/public/proxy-smart.svg b/apps/dtr-app/public/proxy-smart.svg deleted file mode 100644 index 2d1322846..000000000 --- a/apps/dtr-app/public/proxy-smart.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/apps/dtr-app/public/smart-manifest.json b/apps/dtr-app/public/smart-manifest.json deleted file mode 100644 index 50e9bb524..000000000 --- a/apps/dtr-app/public/smart-manifest.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "client_name": "Prior Authorization", - "client_id": "dtr-app", - "description": "Documentation, Templates & Rules โ€” Prior Authorization workflow with questionnaire rendering", - "client_uri": "http://localhost:5175", - "logo_uri": "http://localhost:5175/proxy-smart.svg", - "redirect_uris": ["http://localhost:5175/callback"], - "scope": "openid fhirUser launch user/*.read patient/*.read user/Claim.cud user/QuestionnaireResponse.cud", - "grant_types": ["authorization_code"], - "response_types": ["code"], - "token_endpoint_auth_method": "none", - "category": "clinical", - "icon": "document-text" -} diff --git a/apps/dtr-app/src/App.tsx b/apps/dtr-app/src/App.tsx deleted file mode 100644 index dad851d3e..000000000 --- a/apps/dtr-app/src/App.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { useState, useCallback } from "react" -import { SmartAppShell } from "@proxy-smart/shared-ui" -import { smartAuth } from "@/lib/smart-auth" -import type { LaunchMode } from "hl7.fhir.us.davinci-dtr-generated/fhir-client" -import { Dashboard } from "@/components/Dashboard" -import { FileCheck } from "lucide-react" -import "./index.css" - -export default function App() { - const [launchMode, setLaunchMode] = useState("standalone") - const onAuthenticated = useCallback(() => setLaunchMode(smartAuth.getLaunchMode()), []) - - return ( - smartAuth.startStandaloneLaunch(), - }} - header={{ - title: "Prior Authorization", - icon: FileCheck, - maxWidth: "max-w-5xl", - children: ( - - {launchMode === "ehr" ? "EHR Launch" : "Standalone"} - - ), - }} - title="Prior Authorization" - description="Submit and track prior authorization requests. Sign in to access patient records and documentation forms." - icon={FileCheck} - maxWidth="max-w-5xl" - > - - - ) -} diff --git a/apps/dtr-app/src/components/CoverageCard.tsx b/apps/dtr-app/src/components/CoverageCard.tsx deleted file mode 100644 index 421d4a35b..000000000 --- a/apps/dtr-app/src/components/CoverageCard.tsx +++ /dev/null @@ -1,125 +0,0 @@ -/** - * CoverageCard โ€” Displays the patient's insurance coverage(s) - * - * Uses PASCoverage typed fields: payor reference, relationship code, - * plan class, period, and identifier (member ID). - */ -import { useEffect, useState } from "react" -import { Card, CardContent, CardHeader, CardTitle, Badge, Spinner } from "@proxy-smart/shared-ui" -import { searchCoverage, type PASCoverage } from "@/lib/fhir-client" -import type { SubscriberRelationshipCode } from "hl7.fhir.us.davinci-dtr-generated/valuesets/ValueSet-SubscriberRelationship" -import { Shield, Building2, Calendar, CreditCard, Users } from "lucide-react" - -interface CoverageCardProps { - patientId: string -} - -export function CoverageCard({ patientId }: CoverageCardProps) { - const [coverages, setCoverages] = useState([]) - const [loading, setLoading] = useState(true) - - useEffect(() => { - searchCoverage(patientId) - .then(setCoverages) - .catch(() => setCoverages([])) - .finally(() => setLoading(false)) - }, [patientId]) - - if (loading) { - return ( - - - - Loading coverage... - - - ) - } - - if (coverages.length === 0) { - return ( - - - - No active coverage found - - - ) - } - - return ( -
- {coverages.map((cov) => ( - - ))} -
- ) -} - -function CoverageItem({ coverage }: { coverage: PASCoverage }) { - const payorName = coverage.payor?.[0]?.display ?? "Unknown Payer" - const memberId = coverage.identifier?.[0]?.value - const relationship = coverage.relationship?.coding?.[0]?.code ?? "self" - const planClass = coverage.class?.find(c => c.type?.coding?.[0]?.code === "plan") - const groupClass = coverage.class?.find(c => c.type?.coding?.[0]?.code === "group") - const periodStart = coverage.period?.start - const periodEnd = coverage.period?.end - - const relationLabel: Record = { - self: "Self", - spouse: "Spouse", - child: "Child", - parent: "Parent", - common: "Common Law", - other: "Other", - injured: "Injured Party", - } - - return ( - - -
- - - {planClass?.name ?? coverage.type?.text ?? "Health Insurance"} - - Active -
-
- -
- - {payorName} -
- -
- {memberId && ( -
- - ID: {memberId} -
- )} -
- - {relationLabel[relationship] ?? relationship} -
- {groupClass && ( -
- Group: {groupClass.name ?? groupClass.value} -
- )} - {(periodStart || periodEnd) && ( -
- - - {periodStart && new Date(periodStart).toLocaleDateString()} - {periodStart && periodEnd && " โ€“ "} - {periodEnd && new Date(periodEnd).toLocaleDateString()} - -
- )} -
-
-
- ) -} diff --git a/apps/dtr-app/src/components/Dashboard.tsx b/apps/dtr-app/src/components/Dashboard.tsx deleted file mode 100644 index 63764617c..000000000 --- a/apps/dtr-app/src/components/Dashboard.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { useState } from "react" -import { Tabs, TabsContent, TabsList, TabsTrigger, Spinner } from "@proxy-smart/shared-ui" -import { smartAuth } from "@/lib/smart-auth" -import type { LaunchMode } from "hl7.fhir.us.davinci-dtr-generated/fhir-client" -import { PatientSearch } from "@/components/PatientSearch" -import { PatientBanner } from "@/components/PatientBanner" -import { CoverageCard } from "@/components/CoverageCard" -import { PaRequestList } from "@/components/PaRequestList" -import { NewPaWorkflow } from "@/components/NewPaWorkflow" -import { QuestionnaireBrowser } from "@/components/QuestionnaireBrowser" -import { usePatientContext } from "@/hooks/usePatientContext" - -interface DashboardProps { - launchMode: LaunchMode -} - -export function Dashboard({ launchMode }: DashboardProps) { - const token = smartAuth.getToken() - // In EHR launch, patient context comes from the token - const ehrPatientId = launchMode === "ehr" ? token?.patient ?? null : null - const { patient, loading, error, setPatient } = usePatientContext(ehrPatientId) - const [activeTab, setActiveTab] = useState("requests") - - // Standalone mode: show patient search if no patient selected - if (!patient && launchMode === "standalone") { - return ( - setPatient(p)} - /> - ) - } - - if (loading) { - return ( -
- -

Loading patient context...

-
- ) - } - - if (error || !patient) { - return ( -
-

Failed to load patient

-

{error}

-
- ) - } - - return ( -
- setPatient(null) : undefined} - /> - - - - - - PA Requests - New Request - Questionnaires - - - - - - - - setActiveTab("requests")} - /> - - - - - - -
- ) -} diff --git a/apps/dtr-app/src/components/NewPaWorkflow.tsx b/apps/dtr-app/src/components/NewPaWorkflow.tsx deleted file mode 100644 index d3e15ab53..000000000 --- a/apps/dtr-app/src/components/NewPaWorkflow.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import { useState } from "react" -import type { Patient, Questionnaire, QuestionnaireResponse } from "fhir/r4" -import { Button, Badge } from "@proxy-smart/shared-ui" -import { toast } from "sonner" -import { ServiceSelector, type SelectedService } from "@/components/ServiceSelector" -import { QuestionnaireRenderer } from "@/components/QuestionnaireRenderer" -import { SmartFormsQuestionnaireRenderer } from "@/components/SmartFormsQuestionnaireRenderer" -import { PaReviewSubmit } from "@/components/PaReviewSubmit" -import { createQuestionnaireResponse, submitClaim } from "@/lib/fhir-client" -import { buildPasClaim } from "@/lib/pas-builder" -import { ArrowLeft, ArrowRight } from "lucide-react" - -interface NewPaWorkflowProps { - patient: Patient - onComplete: () => void -} - -type WorkflowStep = "service" | "documentation" | "review" - -const STEPS: { key: WorkflowStep; label: string }[] = [ - { key: "service", label: "Service" }, - { key: "documentation", label: "Documentation" }, - { key: "review", label: "Review & Submit" }, -] - -export function NewPaWorkflow({ patient, onComplete }: NewPaWorkflowProps) { - const [step, setStep] = useState("service") - const [selectedService, setSelectedService] = useState(null) - const [questionnaire, setQuestionnaire] = useState(null) - const [questionnaireResponse, setQuestionnaireResponse] = useState(null) - const [submitting, setSubmitting] = useState(false) - - const currentIndex = STEPS.findIndex((s) => s.key === step) - - const handleServiceSelect = (service: SelectedService, q: Questionnaire | null) => { - setSelectedService(service) - setQuestionnaire(q) - setStep("documentation") - } - - const handleDocumentationComplete = (qr: QuestionnaireResponse) => { - setQuestionnaireResponse(qr) - setStep("review") - } - - const handleSubmit = async () => { - if (!selectedService || !questionnaireResponse) return - - setSubmitting(true) - try { - // Save QuestionnaireResponse - const savedQr = await createQuestionnaireResponse(questionnaireResponse) - - // Build and submit PAS Claim - const claim = buildPasClaim({ - patient, - service: selectedService, - questionnaireResponseId: savedQr.id, - }) - - try { - await submitClaim(claim) - toast.success("Prior authorization submitted", { - description: "Your PA request has been sent to the payer.", - }) - } catch { - // Claim/$submit may not be available on all servers โ€” the QR was saved - toast.success("Documentation saved", { - description: "QuestionnaireResponse recorded. Claim/$submit not available on this server.", - }) - } - - onComplete() - } catch (err) { - const msg = err instanceof Error ? err.message : "Submission failed" - toast.error("Failed to submit", { description: msg }) - } finally { - setSubmitting(false) - } - } - - return ( -
- {/* Progress */} -
- {STEPS.map((s, i) => ( -
- - {i + 1} - - - {s.label} - - {i < STEPS.length - 1 && ( - - )} -
- ))} -
- - {/* Step content */} - {step === "service" && ( - - )} - - {step === "documentation" && ( -
- - {questionnaire ? ( - - ) : ( - - )} -
- )} - - {step === "review" && ( -
- - -
- )} -
- ) -} diff --git a/apps/dtr-app/src/components/PaRequestList.tsx b/apps/dtr-app/src/components/PaRequestList.tsx deleted file mode 100644 index c59e149cd..000000000 --- a/apps/dtr-app/src/components/PaRequestList.tsx +++ /dev/null @@ -1,319 +0,0 @@ -import { useState, useEffect } from "react" -import { Card, CardContent, CardHeader, CardTitle, CardAction, Badge, Button, Spinner } from "@proxy-smart/shared-ui" -import { searchClaims, searchClaimResponses, searchTasks, type PASClaim, type PASClaimResponse, type PASTask } from "@/lib/fhir-client" -import type { ReviewAction } from "hl7.fhir.us.davinci-pas-generated" -import { getX12278DiagnosisTypeConcept } from "hl7.fhir.us.davinci-pas-generated/valuesets/ValueSet-X12278DiagnosisType" -import { getPASTaskCodesConcept } from "hl7.fhir.us.davinci-pas-generated/valuesets/ValueSet-PASTaskCodes" -import { getPASSupportingInfoTypeConcept } from "hl7.fhir.us.davinci-pas-generated/valuesets/ValueSet-PASSupportingInfoType" -import { type RemittanceOutcomeCode } from "hl7.fhir.us.davinci-pas-generated/valuesets/ValueSet-RemittanceOutcome" -import { type HrexTaskStatusCode } from "hl7.fhir.us.davinci-pas-generated/valuesets/ValueSet-HrexTaskStatus" -import { format } from "date-fns" -import { FileText, Clock, CheckCircle, XCircle, AlertTriangle, Eye, Paperclip, Info } from "lucide-react" - -interface PaRequestListProps { - patientId: string -} - -interface PaDisplayItem { - claim: PASClaim - response?: PASClaimResponse - tasks: PASTask[] -} - -/** Maps RemittanceOutcome codes to UI status display */ -const OUTCOME_STATUS_MAP: Record = { - complete: { label: "Approved", variant: "success", icon: CheckCircle }, - error: { label: "Denied", variant: "destructive", icon: XCircle }, - partial: { label: "Partial", variant: "warning", icon: AlertTriangle }, - queued: { label: "Pended", variant: "secondary", icon: Clock }, -} - -/** Maps HRex Task status codes to display labels */ -const TASK_STATUS_LABELS: Partial> = { - requested: "Requested", - accepted: "Accepted", - rejected: "Rejected", - "in-progress": "In Progress", - failed: "Failed", - completed: "Completed", - "on-hold": "On Hold", -} - -/** Maps PAS task codes to human-readable labels */ -const TASK_CODE_LABELS: Record = { - "attachment-request-questionnaire": "Questionnaire Request", - "attachment-request-code": "Coded Attachment Request", -} - -function getStatusInfo(item: PaDisplayItem) { - const outcome = item.response?.outcome as RemittanceOutcomeCode | undefined - if (outcome && outcome in OUTCOME_STATUS_MAP) { - return OUTCOME_STATUS_MAP[outcome] - } - return item.response - ? { label: "Responded", variant: "outline" as const, icon: FileText } - : { label: "Submitted", variant: "secondary" as const, icon: Clock } -} - -function getTaskStatusLabel(status: string): string { - return TASK_STATUS_LABELS[status as HrexTaskStatusCode] ?? status -} - -function getTaskCodeLabel(code: string | undefined): string { - if (!code) return "Attachment Request" - const pasLabel = getPASTaskCodesConcept(code)?.code - return TASK_CODE_LABELS[pasLabel ?? code] ?? pasLabel ?? code -} - -export function PaRequestList({ patientId }: PaRequestListProps) { - const [items, setItems] = useState([]) - const [loading, setLoading] = useState(true) - const [error, setError] = useState(null) - const [expandedId, setExpandedId] = useState(null) - - useEffect(() => { - let cancelled = false - - Promise.all([searchClaims(patientId), searchClaimResponses(patientId), searchTasks(patientId)]) - .then(([claims, responses, tasks]) => { - if (cancelled) return - // Match ClaimResponses to Claims - const responseMap = new Map() - for (const r of responses) { - const claimRef = r.request?.reference - if (claimRef) responseMap.set(claimRef, r) - } - - // Match Tasks to Claims via focus reference - const taskMap = new Map() - for (const t of tasks) { - const focusRef = t.focus?.reference - if (focusRef) { - const existing = taskMap.get(focusRef) ?? [] - existing.push(t) - taskMap.set(focusRef, existing) - } - } - - const combined: PaDisplayItem[] = claims.map((claim) => ({ - claim, - response: responseMap.get(`Claim/${claim.id}`), - tasks: taskMap.get(`Claim/${claim.id}`) ?? [], - })) - setItems(combined) - }) - .catch((err) => { - if (cancelled) return - setError(err instanceof Error ? err.message : "Failed to load PA requests") - }) - .finally(() => { - if (!cancelled) setLoading(false) - }) - - return () => { cancelled = true } - }, [patientId]) - - if (loading) { - return ( -
- -
- ) - } - - if (error) { - return ( - - {error} - - ) - } - - if (items.length === 0) { - return ( -
- -

No prior authorization requests

-

Start a new PA request from the “New Request” tab.

-
- ) - } - - return ( -
- {items.map((item) => { - const status = getStatusInfo(item) - const StatusIcon = status.icon - const created = item.claim.created - ? format(new Date(item.claim.created), "MMM d, yyyy") - : "Unknown date" - const procedureCodes = item.claim.procedure?.map( - (p) => (p.procedureCodeableConcept?.coding?.[0]?.display ?? p.procedureCodeableConcept?.coding?.[0]?.code ?? "") - ).filter(Boolean) ?? [] - const diagnosisCodes = item.claim.diagnosis?.map( - (d) => { - const display = d.diagnosisCodeableConcept?.coding?.[0]?.display ?? d.diagnosisCodeableConcept?.coding?.[0]?.code ?? "" - const typeCode = d.type?.[0]?.coding?.[0]?.code - const typeLabel = typeCode ? getX12278DiagnosisTypeConcept(typeCode)?.code : undefined - return typeLabel ? `${display} (${typeLabel})` : display - } - ).filter(Boolean) ?? [] - const supportingInfoItems = item.claim.supportingInfo?.map((si) => { - const categoryCode = si.category?.coding?.[0]?.code - const categoryLabel = categoryCode ? getPASSupportingInfoTypeConcept(categoryCode)?.code : undefined - return categoryLabel ?? categoryCode ?? "" - }).filter(Boolean) ?? [] - const expanded = expandedId === item.claim.id - - return ( - - - - - PA Request โ€” {created} - - - - {status.label} - - - - -
- {procedureCodes.length > 0 && ( -
- Procedures: - {procedureCodes.map((code, i) => ( - {code} - ))} -
- )} - {diagnosisCodes.length > 0 && ( -
- Diagnoses: - {diagnosisCodes.map((code, i) => ( - {code} - ))} -
- )} - {supportingInfoItems.length > 0 && ( -
- Supporting Info: - {supportingInfoItems.map((cat, i) => ( - {cat} - ))} -
- )} - - {expanded && item.response && ( -
- {/* Item-level adjudication details */} - {item.response.item?.map((ri, idx) => { - const reviewAction = ri.adjudication?.[0]?.extension?.find( - (e): e is ReviewAction => e.url === "http://hl7.org/fhir/us/davinci-pas/StructureDefinition/extension-reviewAction" - ) - const reviewCode = reviewAction?.extension?.find(e => e.url === "http://hl7.org/fhir/us/davinci-pas/StructureDefinition/extension-reviewActionCode") - return ( -
-

Item {ri.itemSequence}

- {reviewCode?.valueCodeableConcept && ( -

- Review Action: {reviewCode.valueCodeableConcept.coding?.[0]?.display ?? reviewCode.valueCodeableConcept.coding?.[0]?.code} -

- )} - {ri.extension?.filter(e => e.url === "http://hl7.org/fhir/us/davinci-pas/StructureDefinition/extension-itemPreAuthPeriod").map((ext, i) => ( -

- Auth Period: {ext.valuePeriod?.start && format(new Date(ext.valuePeriod.start), "MMM d, yyyy")} - {ext.valuePeriod?.end && ` โ€” ${format(new Date(ext.valuePeriod.end), "MMM d, yyyy")}`} -

- ))} - {ri.extension?.filter(e => e.url === "http://hl7.org/fhir/us/davinci-pas/StructureDefinition/extension-authorizationNumber").map((ext, i) => ( -

- Auth #: {ext.valueString} -

- ))} -
- ) - })} - {/* Errors */} - {item.response.error?.map((err, idx) => ( -
-

- Error: {err.code?.coding?.[0]?.display ?? err.code?.coding?.[0]?.code ?? "Unknown"} -

-
- ))} - {/* Communication requests */} - {item.response.communicationRequest && item.response.communicationRequest.length > 0 && ( -
-

- - Payer requests additional information ({item.response.communicationRequest.length} item{item.response.communicationRequest.length !== 1 ? "s" : ""}) -

- {item.response.communicationRequest.map((cr, i) => ( -

- {cr.display ?? cr.reference ?? "Communication request"} -

- ))} -
- )} - {/* Raw response fallback */} -
- Raw FHIR Response -
-                        {JSON.stringify(item.response, null, 2)}
-                      
-
-
- )} - - {/* Attachment request tasks */} - {expanded && item.tasks.length > 0 && ( -
-

- Attachment Requests -

- {item.tasks.map((task, idx) => { - const taskCode = task.code?.coding?.[0]?.code - return ( -
-
-

- {getTaskCodeLabel(taskCode)} -

- - {getTaskStatusLabel(task.status ?? "")} - -
- {task.description && ( -

{task.description}

- )} - {task.lastModified && ( -

- Updated: {format(new Date(task.lastModified), "MMM d, yyyy")} -

- )} -
- ) - })} -
- )} - -
- -
-
-
-
- ) - })} -
- ) -} diff --git a/apps/dtr-app/src/components/PaReviewSubmit.tsx b/apps/dtr-app/src/components/PaReviewSubmit.tsx deleted file mode 100644 index 9f25a18bb..000000000 --- a/apps/dtr-app/src/components/PaReviewSubmit.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import type { Patient, QuestionnaireResponse } from "fhir/r4" -import { useEffect, useState } from "react" -import { Card, CardContent, CardHeader, CardTitle, Button, Badge, Spinner, Separator } from "@proxy-smart/shared-ui" -import { formatHumanName, searchCoverage, type PASCoverage } from "@/lib/fhir-client" -import { getUSCoreDemographics } from "@/lib/patient-extensions" -import { type SelectedService } from "@/components/ServiceSelector" -import { Send, User, Stethoscope, FileText, CheckCircle, Shield } from "lucide-react" - -interface PaReviewSubmitProps { - patient: Patient - service: SelectedService - questionnaireResponse: QuestionnaireResponse - onSubmit: () => Promise - submitting: boolean -} - -export function PaReviewSubmit({ - patient, - service, - questionnaireResponse, - onSubmit, - submitting, -}: PaReviewSubmitProps) { - const responseItems = questionnaireResponse.item ?? [] - const answeredCount = responseItems.filter((i) => i.answer?.length).length - const { race, ethnicity, birthSexDisplay } = getUSCoreDemographics(patient) - const [coverage, setCoverage] = useState(null) - - useEffect(() => { - if (patient.id) { - searchCoverage(patient.id).then((c) => setCoverage(c[0] ?? null)).catch(() => {}) - } - }, [patient.id]) - - return ( - - - - - Review & Submit - - - - {/* Patient */} -
- -
-

Patient

-

{formatHumanName(patient.name)}

-
- {patient.birthDate && ( - DOB: {patient.birthDate} - )} - {patient.gender && ( - {patient.gender} - )} - {birthSexDisplay && birthSexDisplay !== patient.gender && ( - Birth Sex: {birthSexDisplay} - )} - {race?.text && ( - {race.text} - )} - {ethnicity?.text && ( - {ethnicity.text} - )} -
-
-
- - {/* Coverage */} - {coverage && ( -
- -
-

Insurance

-

{coverage.payor?.[0]?.display ?? "Payer"}

-
- {coverage.identifier?.[0]?.value && ( - - ID: {coverage.identifier[0].value} - - )} - {coverage.class?.find(c => c.type?.coding?.[0]?.code === "plan")?.name && ( - - {coverage.class.find(c => c.type?.coding?.[0]?.code === "plan")!.name} - - )} -
-
-
- )} - - {/* Service */} -
- -
-

Procedure Requested

-

- {service.procedure.code} - {service.procedure.display} -

- {service.diagnosisText && ( -

- Diagnosis: {service.diagnosisText} -

- )} -
-
- - - - {/* Documentation summary */} -
-
- -

Documentation

- - {answeredCount} answer{answeredCount !== 1 ? "s" : ""} - -
-
- {responseItems.map((item) => { - if (!item.answer?.length) return null - const answer = item.answer[0] - const displayValue = - answer.valueString ?? - answer.valueCoding?.display ?? - answer.valueDate ?? - (answer.valueBoolean !== undefined ? (answer.valueBoolean ? "Yes" : "No") : null) ?? - answer.valueInteger?.toString() ?? - answer.valueDecimal?.toString() ?? - "โ€”" - - return ( -
- {item.text ?? item.linkId} - {displayValue} -
- ) - })} -
-
- - - - - -

- This will submit a FHIR Claim resource with use=preauthorization to the payer endpoint via PAS (Da Vinci Prior Authorization Support). -

-
-
- ) -} diff --git a/apps/dtr-app/src/components/PatientBanner.tsx b/apps/dtr-app/src/components/PatientBanner.tsx deleted file mode 100644 index 44c624bf3..000000000 --- a/apps/dtr-app/src/components/PatientBanner.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import type { Patient } from "fhir/r4" -import type { LaunchMode } from "hl7.fhir.us.davinci-dtr-generated/fhir-client" -import { Card, CardContent, Button, Badge, Tooltip, TooltipContent, TooltipTrigger } from "@proxy-smart/shared-ui" -import { formatHumanName } from "@/lib/fhir-client" -import { getUSCoreDemographics } from "@/lib/patient-extensions" -import { User, Calendar, Hash, X, Globe, Heart } from "lucide-react" - -interface PatientBannerProps { - patient: Patient - launchMode: LaunchMode - onClear?: () => void -} - -export function PatientBanner({ patient, launchMode, onClear }: PatientBannerProps) { - const name = formatHumanName(patient.name) - const mrn = patient.identifier?.[0]?.value - const { race, ethnicity, birthSexDisplay } = getUSCoreDemographics(patient) - - return ( - - -
-
- -
-
-
- {name} - - {launchMode === "ehr" ? "EHR Context" : "Selected"} - -
-
- {patient.birthDate && ( - - - {patient.birthDate} - - )} - {mrn && ( - - - MRN: {mrn} - - )} - {patient.gender && {patient.gender}} - {birthSexDisplay && birthSexDisplay !== patient.gender && ( - - - - - Birth Sex: {birthSexDisplay} - - - US Core Birth Sex Extension - - )} - {race?.text && ( - - - - - {race.text} - - - - {race.ombCategories.length > 0 - ? `OMB: ${race.ombCategories.join(", ")}${race.detailed.length ? ` ยท Detailed: ${race.detailed.join(", ")}` : ""}` - : "US Core Race Extension"} - - - )} - {ethnicity?.text && ( - - - {ethnicity.text} - - - {ethnicity.ombCategories.length > 0 - ? `OMB: ${ethnicity.ombCategories.join(", ")}` - : "US Core Ethnicity Extension"} - - - )} -
-
-
- {onClear && ( - - )} -
-
- ) -} diff --git a/apps/dtr-app/src/components/PatientSearch.tsx b/apps/dtr-app/src/components/PatientSearch.tsx deleted file mode 100644 index 01cf8259e..000000000 --- a/apps/dtr-app/src/components/PatientSearch.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import { useState, useCallback, useRef } from "react" -import type { Patient } from "fhir/r4" -import { searchPatients, searchPatientByIdentifier, formatHumanName } from "@/lib/fhir-client" -import { getUSCoreDemographics } from "@/lib/patient-extensions" -import { Input, Card, CardContent, Badge, Spinner } from "@proxy-smart/shared-ui" -import { Search, User, Calendar, Hash, ChevronRight } from "lucide-react" - -interface PatientSearchProps { - onSelect: (patient: Patient) => void -} - -export function PatientSearch({ onSelect }: PatientSearchProps) { - const [query, setQuery] = useState("") - const [results, setResults] = useState([]) - const [loading, setLoading] = useState(false) - const [searched, setSearched] = useState(false) - const debounceRef = useRef>(undefined) - - const doSearch = useCallback(async (q: string) => { - if (!q.trim()) { - setResults([]) - setSearched(false) - return - } - - setLoading(true) - setSearched(true) - try { - // Search by name first, fall back to identifier - let patients = await searchPatients(q) - if (patients.length === 0 && /^\d/.test(q)) { - patients = await searchPatientByIdentifier(q) - } - setResults(patients) - } catch { - setResults([]) - } finally { - setLoading(false) - } - }, []) - - const handleInput = (value: string) => { - setQuery(value) - clearTimeout(debounceRef.current) - debounceRef.current = setTimeout(() => doSearch(value), 400) - } - - return ( -
-
-

Select Patient

-

- Search by patient name or MRN to begin a prior authorization request. -

-
- -
- - handleInput(e.target.value)} - className="pl-9" - autoFocus - /> -
- - {loading && ( -
- -
- )} - - {!loading && searched && results.length === 0 && ( -

- No patients found for “{query}” -

- )} - - {!loading && results.length > 0 && ( -
- {results.map((patient) => { - const name = formatHumanName(patient.name) - const mrn = patient.identifier?.[0]?.value - const { race, ethnicity, birthSexDisplay } = getUSCoreDemographics(patient) - return ( - - - - ) - })} -
- )} -
- ) -} diff --git a/apps/dtr-app/src/components/QuestionnaireBrowser.tsx b/apps/dtr-app/src/components/QuestionnaireBrowser.tsx deleted file mode 100644 index 89275fa56..000000000 --- a/apps/dtr-app/src/components/QuestionnaireBrowser.tsx +++ /dev/null @@ -1,337 +0,0 @@ -/** - * Questionnaire Browser - * - * Generalized DTR interface for browsing, rendering, and submitting any - * FHIR Questionnaire on the server. Supports all DTR use cases: - * - Claims attachments (CDex) - * - Medical necessity documentation - * - Risk adjustment / quality reporting - * - Any payer-published SDC Questionnaire - */ -import { useState, useEffect, useCallback, useRef, useMemo } from "react" -import type { Patient, Questionnaire, QuestionnaireResponse } from "fhir/r4" -import { Card, CardContent, CardHeader, CardTitle, Button, Badge, Input, Spinner } from "@proxy-smart/shared-ui" -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@proxy-smart/shared-ui" -import { SmartFormsQuestionnaireRenderer } from "@/components/SmartFormsQuestionnaireRenderer" -import { searchQuestionnaires, createQuestionnaireResponse } from "@/lib/fhir-client" -import type { PublicationStatusCode } from "hl7.fhir.us.davinci-dtr-generated/valuesets/ValueSet-PublicationStatus" -import { toast } from "sonner" -import { - FileQuestion, - Search, - ArrowLeft, - CheckCircle, - Calendar, - Building2, - Tag, - Filter, - PenTool, - BookOpen, - Workflow, -} from "lucide-react" -import { getQuestionnaireMetadata } from "@/lib/questionnaire-extensions" - -interface QuestionnaireBrowserProps { - patient: Patient -} - -type BrowserView = "list" | "fill" | "done" - -export function QuestionnaireBrowser({ patient }: QuestionnaireBrowserProps) { - const [view, setView] = useState("list") - const [questionnaires, setQuestionnaires] = useState([]) - const [loading, setLoading] = useState(true) - const [searchQuery, setSearchQuery] = useState("") - const [publisherFilter, setPublisherFilter] = useState("all") - const [statusFilter, setStatusFilter] = useState("all") - const [selectedQ, setSelectedQ] = useState(null) - const didFetch = useRef(false) - - // Fetch available questionnaires - useEffect(() => { - if (didFetch.current) return - didFetch.current = true - - async function load() { - try { - const results = await searchQuestionnaires() - setQuestionnaires(results) - } catch (err) { - console.error("Failed to load questionnaires:", err) - } finally { - setLoading(false) - } - } - load() - }, []) - - const handleSelect = useCallback((q: Questionnaire) => { - setSelectedQ(q) - setView("fill") - }, []) - - const handleComplete = useCallback( - async (qr: QuestionnaireResponse) => { - try { - await createQuestionnaireResponse(qr) - toast.success("Documentation saved", { - description: `QuestionnaireResponse for "${selectedQ?.title ?? "form"}" recorded.`, - }) - setView("done") - } catch (err) { - const msg = err instanceof Error ? err.message : "Save failed" - toast.error("Failed to save", { description: msg }) - } - }, - [selectedQ] - ) - - const handleBack = useCallback(() => { - setSelectedQ(null) - setView("list") - }, []) - - // Extract unique publishers (payers) for filtering - const publishers = useMemo(() => { - const names = new Set() - for (const q of questionnaires) { - if (q.publisher) names.add(q.publisher) - } - return Array.from(names).sort() - }, [questionnaires]) - - // Filter questionnaires by search query, publisher, and status - const filtered = questionnaires.filter((q) => { - if (publisherFilter !== "all" && q.publisher !== publisherFilter) return false - if (statusFilter !== "all" && q.status !== statusFilter) return false - if (!searchQuery) return true - const query = searchQuery.toLowerCase() - return ( - q.title?.toLowerCase().includes(query) || - q.name?.toLowerCase().includes(query) || - q.publisher?.toLowerCase().includes(query) || - q.description?.toLowerCase().includes(query) || - q.id?.toLowerCase().includes(query) - ) - }) - - // โ”€โ”€ Done view โ”€โ”€ - if (view === "done") { - return ( - - - -

Documentation Saved

-

- The completed QuestionnaireResponse has been saved to the FHIR - server and is available for downstream workflows. -

- -
-
- ) - } - - // โ”€โ”€ Fill view โ”€โ”€ - if (view === "fill" && selectedQ) { - return ( -
- - -
- ) - } - - // โ”€โ”€ List view โ”€โ”€ - return ( -
-
-
- - setSearchQuery(e.target.value)} - className="pl-9" - /> -
- {publishers.length > 1 && ( - - )} - - {filtered.length} available -
- - {loading ? ( - - - -

- Loading questionnaires from FHIR server... -

-
-
- ) : filtered.length === 0 ? ( - - - -

- {searchQuery - ? "No questionnaires match your search." - : "No questionnaires available on this FHIR server."} -

-
-
- ) : ( -
- {filtered.map((q) => ( - handleSelect(q)} - /> - ))} -
- )} -
- ) -} - -// โ”€โ”€ Questionnaire Card โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - -function QuestionnaireCard({ - questionnaire, - onSelect, -}: { - questionnaire: Questionnaire - onSelect: () => void -}) { - const itemCount = countItems(questionnaire.item ?? []) - const meta = getQuestionnaireMetadata(questionnaire) - const statusColor = - questionnaire.status === ("active" satisfies PublicationStatusCode) - ? "text-emerald-600" - : questionnaire.status === ("draft" satisfies PublicationStatusCode) - ? "text-amber-600" - : "text-muted-foreground" - - return ( - - -
- - - {questionnaire.title ?? questionnaire.name ?? questionnaire.id} - -
- {meta.signatureRequired && ( - - - Signature - - )} - {meta.hasCql && ( - - - CQL - - )} - {meta.modular && ( - - - Modular - - )} - - {questionnaire.status} - -
-
-
- - {questionnaire.description && ( -

- {questionnaire.description} -

- )} -
- {questionnaire.publisher && ( - - - {questionnaire.publisher} - - )} - {questionnaire.date && ( - - - {new Date(questionnaire.date).toLocaleDateString()} - - )} - - - {itemCount} item{itemCount !== 1 ? "s" : ""} - - {meta.launchContextNames.length > 0 && ( - - Context: {meta.launchContextNames.join(", ")} - - )} -
-
-
- ) -} - -function countItems(items: Questionnaire["item"]): number { - if (!items) return 0 - let count = 0 - for (const item of items) { - count += 1 - if (item.item) count += countItems(item.item) - } - return count -} diff --git a/apps/dtr-app/src/components/QuestionnaireRenderer.tsx b/apps/dtr-app/src/components/QuestionnaireRenderer.tsx deleted file mode 100644 index ab3b64a4c..000000000 --- a/apps/dtr-app/src/components/QuestionnaireRenderer.tsx +++ /dev/null @@ -1,596 +0,0 @@ -import { useState, useMemo } from "react" -import type { - Patient, - Questionnaire, - QuestionnaireResponse, - QuestionnaireItem, - QuestionnaireResponseItem, -} from "fhir/r4" -import { Card, CardContent, CardHeader, CardTitle, Button, Input, Label, Badge } from "@proxy-smart/shared-ui" -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, - Separator, -} from "@proxy-smart/shared-ui" -import { formatHumanName } from "@/lib/fhir-client" -import { type SelectedService } from "@/components/ServiceSelector" -import { GENERIC_PA_ITEMS } from "@/lib/generic-questionnaire" -import { getItemExtensions, type ItemExtensions } from "@/lib/dtr-extensions" -import type { QuestionnaireAnswersStatusCode } from "hl7.fhir.us.davinci-dtr-generated/valuesets/ValueSet-QuestionnaireAnswersStatus" -import { ArrowRight, FileQuestion, Info, HelpCircle, ChevronDown, ChevronRight, ExternalLink } from "lucide-react" - -interface QuestionnaireRendererProps { - /** FHIR Questionnaire from payer โ€” null if none found (use generic) */ - questionnaire: Questionnaire | null - patient: Patient - service: SelectedService - onComplete: (response: QuestionnaireResponse) => void -} - -type AnswerMap = Map - -export function QuestionnaireRenderer({ - questionnaire, - patient, - service, - onComplete, -}: QuestionnaireRendererProps) { - const items = useMemo( - () => questionnaire?.item ?? GENERIC_PA_ITEMS, - [questionnaire] - ) - - const [answers, setAnswers] = useState(() => { - const map = new Map() - map.set("patient-name", formatHumanName(patient.name)) - map.set("patient-dob", patient.birthDate ?? "") - map.set("patient-gender", patient.gender ?? "") - map.set("patient-id", patient.identifier?.[0]?.value ?? patient.id ?? "") - map.set("procedure-code", service.procedure.code) - map.set("procedure-display", service.procedure.display) - map.set("diagnosis", service.diagnosisText) - map.set("clinical-notes", service.notes) - return map - }) - - const updateAnswer = (linkId: string, value: string) => { - setAnswers((prev) => { - const next = new Map(prev) - next.set(linkId, value) - return next - }) - } - - const handleSubmit = () => { - const responseItems: QuestionnaireResponseItem[] = items - .map((item) => buildResponseItem(item, answers)) - .filter(Boolean) as QuestionnaireResponseItem[] - - const qr: QuestionnaireResponse = { - resourceType: "QuestionnaireResponse", - questionnaire: questionnaire?.url ?? questionnaire?.id - ? `Questionnaire/${questionnaire?.id}` - : undefined, - status: "completed" satisfies QuestionnaireAnswersStatusCode, - subject: { reference: `Patient/${patient.id}` }, - authored: new Date().toISOString(), - item: responseItems, - } - - onComplete(qr) - } - - const isGeneric = !questionnaire - - return ( - - - - - {questionnaire?.title ?? "Prior Authorization Documentation"} - - {isGeneric && ( -
- - No payer-specific questionnaire found. Using standard PA documentation form. -
- )} - {!isGeneric && ( - - Payer Questionnaire: {questionnaire.publisher ?? questionnaire.name ?? questionnaire.id} - - )} -
- - {/* Auto-populated section */} -
-

- Auto-populated from patient context -

-
-
-

Patient Name

-

{answers.get("patient-name")}

-
-
-

Date of Birth

-

{answers.get("patient-dob") || "โ€”"}

-
-
-

Procedure

-

- {answers.get("procedure-code")} โ€” {answers.get("procedure-display")} -

-
-
-

Diagnosis

-

{answers.get("diagnosis") || "โ€”"}

-
-
-
- - - - {/* Questionnaire items */} -
-

- Required Documentation -

- {items.map((item) => ( - updateAnswer(item.linkId, v)} - answers={answers} - onChangeNested={updateAnswer} - /> - ))} -
- - -
-
- ) -} - -// โ”€โ”€ Individual item renderer with DTR extension awareness โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - -interface QuestionnaireItemFieldProps { - item: QuestionnaireItem - value: string - onChange: (value: string) => void - answers: AnswerMap - onChangeNested: (linkId: string, value: string) => void -} - -function QuestionnaireItemField({ item, value, onChange, answers, onChangeNested }: QuestionnaireItemFieldProps) { - const ext = getItemExtensions(item) - const [collapsed, setCollapsed] = useState(ext.collapsible === "default-closed") - - // Hidden items: skip rendering entirely - if (ext.hidden) return null - - const required = item.required ?? false - const labelText = ext.shortText ?? item.text ?? item.linkId - const label = `${labelText}${required ? " *" : ""}` - - // Display items with category-aware styling - if (item.type === "display") { - return - } - - // Group items with collapsible support - if (item.type === "group") { - return ( - setCollapsed(!collapsed)} - answers={answers} - onChangeNested={onChangeNested} - /> - ) - } - - // Render based on item control extension first, then fall back to type - const control = ext.itemControl - - if (item.type === "boolean") { - return - } - - if (item.type === "choice" || item.type === "open-choice") { - if (control === "radio-button") { - return - } - if (control === "check-box") { - return - } - if (control === "autocomplete") { - return - } - // Default: dropdown - return - } - - if (item.type === "date") { - return - } - - if (item.type === "integer" || item.type === "decimal") { - if (control === "slider") { - return - } - return - } - - if (item.type === "text") { - return - } - - // string, url, reference, etc. - return -} - -// โ”€โ”€ Typed field components โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - -function DisplayItem({ text, category, supportLinks }: { text?: string; category?: string; supportLinks: string[] }) { - const baseClass = "text-sm p-3 rounded-md" - const categoryClass = - category === "instructions" ? `${baseClass} bg-blue-50 dark:bg-blue-950/30 text-blue-800 dark:text-blue-200 border border-blue-200 dark:border-blue-900` : - category === "help" ? `${baseClass} bg-emerald-50 dark:bg-emerald-950/30 text-emerald-800 dark:text-emerald-200 border border-emerald-200 dark:border-emerald-900` : - category === "security" ? `${baseClass} bg-amber-50 dark:bg-amber-950/30 text-amber-800 dark:text-amber-200 border border-amber-200 dark:border-amber-900` : - `${baseClass} text-muted-foreground bg-muted/50` - - const icon = category === "help" ? : category === "instructions" ? : null - - return ( -
-
- {icon} - {text} -
- {supportLinks.length > 0 && ( -
- {supportLinks.map((link, i) => ( - - More info - - ))} -
- )} -
- ) -} - -function GroupItem({ item, ext, collapsed, onToggle, answers, onChangeNested }: { - item: QuestionnaireItem; ext: ItemExtensions; collapsed: boolean - onToggle: () => void; answers: AnswerMap; onChangeNested: (linkId: string, value: string) => void -}) { - const isCollapsible = !!ext.collapsible - const CollapseIcon = collapsed ? ChevronRight : ChevronDown - - return ( -
-
- {isCollapsible && } -

{item.text}

- {item.required && Required} -
- {(!isCollapsible || !collapsed) && ( -
- {item.item?.map((child) => ( - onChangeNested(child.linkId, v)} - answers={answers} - onChangeNested={onChangeNested} - /> - ))} -
- )} -
- ) -} - -function BooleanField({ label, value, onChange }: { label: string; value: string; onChange: (v: string) => void }) { - return ( -
- - -
- ) -} - -function ChoiceField({ item, label, value, onChange }: { - item: QuestionnaireItem; label: string; value: string; onChange: (v: string) => void -}) { - return ( -
- - -
- ) -} - -function RadioField({ item, label, value, onChange, ext }: { - item: QuestionnaireItem; label: string; value: string; onChange: (v: string) => void; ext: ItemExtensions -}) { - const horizontal = ext.choiceOrientation === "horizontal" - return ( -
- -
- {item.answerOption?.map((opt, i) => { - const display = opt.valueCoding?.display ?? opt.valueString ?? opt.valueCoding?.code ?? `Option ${i + 1}` - const val = opt.valueCoding?.code ?? opt.valueString ?? display - return ( - - ) - })} -
-
- ) -} - -function CheckboxField({ item, label, value, onChange, ext }: { - item: QuestionnaireItem; label: string; value: string; onChange: (v: string) => void; ext: ItemExtensions -}) { - const selected = value ? value.split(",") : [] - const horizontal = ext.choiceOrientation === "horizontal" - - const toggle = (val: string) => { - const next = selected.includes(val) ? selected.filter(v => v !== val) : [...selected, val] - onChange(next.join(",")) - } - - return ( -
- -
- {item.answerOption?.map((opt, i) => { - const display = opt.valueCoding?.display ?? opt.valueString ?? opt.valueCoding?.code ?? `Option ${i + 1}` - const val = opt.valueCoding?.code ?? opt.valueString ?? display - return ( - - ) - })} -
-
- ) -} - -function AutocompleteField({ item, label, value, onChange }: { - item: QuestionnaireItem; label: string; value: string; onChange: (v: string) => void -}) { - const listId = `${item.linkId}-list` - return ( -
- - onChange(e.target.value)} - placeholder="Type to search..." - /> - - {item.answerOption?.map((opt, i) => { - const display = opt.valueCoding?.display ?? opt.valueString ?? opt.valueCoding?.code ?? `Option ${i + 1}` - const val = opt.valueCoding?.code ?? opt.valueString ?? display - return - })} - -
- ) -} - -function DateField({ label, value, onChange, ext }: { - label: string; value: string; onChange: (v: string) => void; ext: ItemExtensions -}) { - return ( -
- - onChange(e.target.value)} /> - {ext.entryFormat && ( -

Format: {ext.entryFormat}

- )} -
- ) -} - -function NumericField({ label, value, onChange, ext, itemType }: { - label: string; value: string; onChange: (v: string) => void; ext: ItemExtensions; itemType: string -}) { - const step = itemType === "decimal" - ? ext.maxDecimalPlaces ? `0.${"0".repeat(ext.maxDecimalPlaces - 1)}1` : "0.01" - : "1" - const unit = ext.unit - - return ( -
- -
- onChange(e.target.value)} - /> - {unit && ( - - {unit.display ?? unit.code} - - )} -
- {(ext.minValue !== undefined || ext.maxValue !== undefined) && ( -

- {ext.minValue !== undefined && ext.maxValue !== undefined - ? `Range: ${ext.minValue} โ€“ ${ext.maxValue}` - : ext.minValue !== undefined ? `Minimum: ${ext.minValue}` : `Maximum: ${ext.maxValue}`} - {unit ? ` ${unit.display ?? unit.code}` : ""} -

- )} -
- ) -} - -function SliderField({ label, value, onChange, ext, itemType }: { - label: string; value: string; onChange: (v: string) => void; ext: ItemExtensions; itemType: string -}) { - const min = ext.minValue ?? 0 - const max = ext.maxValue ?? 100 - const step = ext.sliderStep ?? (itemType === "decimal" ? 0.1 : 1) - const unit = ext.unit - - return ( -
-
- - - {value || min}{unit ? ` ${unit.display ?? unit.code}` : ""} - -
- onChange(e.target.value)} - className="w-full accent-primary" - /> -
- {min} - {max} -
-
- ) -} - -function TextAreaField({ label, value, onChange, ext }: { - label: string; value: string; onChange: (v: string) => void; ext: ItemExtensions -}) { - return ( -
- -