diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f0fc1ac --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +src/shasta/voice/web/dist/** linguist-generated=true +src/shasta/voice/web/dist/** -diff diff --git a/.github/workflows/voice-bundle.yml b/.github/workflows/voice-bundle.yml new file mode 100644 index 0000000..d180b05 --- /dev/null +++ b/.github/workflows/voice-bundle.yml @@ -0,0 +1,34 @@ +name: voice-bundle + +on: + pull_request: + paths: + - 'src/shasta/voice/web/src/**' + - 'src/shasta/voice/web/package.json' + - 'src/shasta/voice/web/package-lock.json' + - 'src/shasta/voice/web/vite.config.ts' + +jobs: + rebuild-and-diff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: 'src/shasta/voice/web/package-lock.json' + - name: Install + working-directory: src/shasta/voice/web + run: npm ci + - name: Build + working-directory: src/shasta/voice/web + run: npm run build + - name: Verify committed bundle is up to date + run: | + if ! git diff --quiet src/shasta/voice/web/dist/; then + echo "::error::The committed React bundle is out of date. Run 'npm run build' in src/shasta/voice/web/ and commit the result." + git status src/shasta/voice/web/dist/ + git diff src/shasta/voice/web/dist/ | head -100 + exit 1 + fi diff --git a/CHANGELOG.md b/CHANGELOG.md index f910fc0..6e2c008 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,40 @@ All notable changes to Shasta are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.9.0] — 2026-05-05 — Voice console (opt-in) + +### Added +- **Voice-driven compliance console** — opt-in module at `src/shasta/voice/` + delivering a hands-free, conversational interface to your scan data. + Run `python -m shasta.voice` after a scan to open a browser-based + voice console at `localhost:8090` and talk to your compliance posture + across SOC 2, ISO 27001, HIPAA, ISO 42001, and EU AI Act. + - Browser-direct WebRTC to OpenAI Realtime API; FastAPI backend mints + ephemeral tokens and serves 14 tool endpoints. Backend never handles + audio. + - Voice-driven dashboard: dashboard cards (FindingsList, FindingDetail, + ComplianceScore, MultiFrameworkScore, ControlSummary, RiskList, + RiskDetail, ActionToast) mount in response to assistant tool calls. + - Read-only over `ShastaDB` for findings/scores/controls/scans, plus + light writes for risk-register operations (`add_risk_item`, + `update_risk`). Heavy ops (scans, reports, Terraform generation, + policy generation) remain in the Claude Code skills — voice + redirects to them. + - Pre-built React bundle ships in the wheel, so users do not need + Node.js installed at runtime. + - Install with `pip install shasta[voice]`. Requires `OPENAI_API_KEY`. + - 74 voice-specific tests (~85% coverage on `src/shasta/voice/`). + - CI workflow (`.github/workflows/voice-bundle.yml`) verifies the + committed React bundle stays in sync with `web/src/`. + - Spec: `docs/superpowers/specs/2026-05-05-shastavoice-design.md`. + - Plan: `docs/superpowers/plans/2026-05-05-shastavoice.md`. + +### Changed +- `ShastaDB._conn` now opens its SQLite connection with + `check_same_thread=False` so the voice console's FastAPI app can share + the connection across the request worker pool. Behavior is unchanged + for the existing single-threaded dashboard. (`src/shasta/db/schema.py`) + ## [1.6.1] — 2026-04-11 — Prod-scan bug sweep Five bugs surfaced during a live SOC 2 / ISO 27001 / HIPAA / Whitney scan diff --git a/CLAUDE.md b/CLAUDE.md index b95c984..b0c5348 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,7 +1,7 @@ # Shasta — Multi-Cloud Compliance Automation ## What is this? -Shasta is a Claude Code-native SOC 2 and ISO 27001 compliance platform. It scans AWS and Azure environments, maps findings to compliance controls, generates remediation guidance (with Terraform), and produces compliance policies and reports. +Shasta is a Claude Code-native multi-cloud compliance platform. It scans AWS and Azure environments, maps findings to compliance controls (SOC 2, ISO 27001, HIPAA, ISO 42001, EU AI Act), generates remediation guidance (with Terraform), produces compliance policies and reports, and ships an optional voice-driven dashboard (`python -m shasta.voice`) for hands-free posture queries. ## Tech stack - Python 3.11+, boto3, azure-identity, azure-mgmt-*, msgraph-sdk, rich, pydantic, jinja2, weasyprint @@ -15,15 +15,19 @@ Shasta is a Claude Code-native SOC 2 and ISO 27001 compliance platform. It scans - `src/shasta/compliance/ai/` — AI governance frameworks (ISO 42001, EU AI Act, NIST AI RMF) - `src/shasta/aws/ai_checks.py` — AWS AI service checks (Bedrock, SageMaker) - `src/shasta/azure/ai_checks.py` — Azure AI service checks (Azure OpenAI, Azure ML) +- `src/shasta/dashboard/` — read-only HTML compliance dashboard (FastAPI + Jinja, port 8080) +- `src/shasta/voice/` — opt-in voice console (FastAPI + React + OpenAI Realtime, port 8090); install with `pip install -e ".[voice]"` - `.claude/skills/` — Claude Code skill definitions -- `tests/` — pytest test suite (uses moto for AWS mocking, unittest.mock for Azure) +- `tests/` — pytest test suite (uses moto for AWS mocking, unittest.mock for Azure); voice-specific tests live under `tests/voice/` - `data/` — runtime data (gitignored) ## Commands -- Install: `pip install -e ".[dev]"` (core) or `pip install -e ".[dev,azure]"` (with Azure) -- Test: `pytest` +- Install: `pip install -e ".[dev]"` (core), `pip install -e ".[dev,azure]"` (with Azure), or `pip install -e ".[dev,azure,voice]"` (with voice console) +- Test: `pytest` (or `pytest tests/voice/` for voice-only) - Lint: `ruff check src/ tests/` - Format: `ruff format src/ tests/` +- Run dashboard: `python -m shasta.dashboard` (HTML, port 8080) +- Run voice console: `python -m shasta.voice` (requires `OPENAI_API_KEY` and a populated `data/shasta.db`; port 8090) ## Conventions - Use pydantic models for all data structures diff --git a/README.md b/README.md index 11dd672..089fd44 100644 --- a/README.md +++ b/README.md @@ -296,6 +296,20 @@ See [docs/DEPLOYMENT.md](./docs/DEPLOYMENT.md) for the complete setup guide incl > **You don't need to memorize slash commands.** Shasta and Whitney are AI-native — just describe what you need in plain English inside Claude Code. Say *"Connect to my AWS and run a full SOC 2 gap analysis with remediation Terraform"* and Claude orchestrates everything. See **[docs/CONVERSATIONS.md](./docs/CONVERSATIONS.md)** for 15 real conversation examples. +### Voice Console (optional) + +Talk to your compliance posture instead of clicking through dashboards. + +```bash +pip install shasta[voice] # adds FastAPI + uvicorn + httpx +export OPENAI_API_KEY=sk-... # required for OpenAI Realtime API +python -m shasta.voice # opens browser at http://localhost:8090 +``` + +Requires a recent scan in `data/shasta.db` (run `/scan` in Claude Code first). The voice assistant has read access to all your findings, compliance scores (SOC 2, ISO 27001, HIPAA, ISO 42001, EU AI Act), and risk register, plus light writes for adding/updating risk-register items. Heavy operations (scans, reports, Terraform generation) remain in the Claude Code skills — voice will redirect you to them. + +📹 **Demo:** [`docs/media/shasta-voice-demo.mp4`](./docs/media/shasta-voice-demo.mp4) — 60-second walkthrough showing posture queries, finding drilldowns, and risk-register writes against a real scan. + --- ## Skills Reference diff --git a/TRUST.md b/TRUST.md index f731354..c0148e2 100644 --- a/TRUST.md +++ b/TRUST.md @@ -23,7 +23,7 @@ Shasta and Whitney together ship the following, all integrity-tested: - **221 check functions** (221 cloud compliance + 0 AI governance — Whitney now ships as a separate repo at [github.com/transilienceai/whitney](https://github.com/transilienceai/whitney); install with `pip install whitney` for source-code scanning) - **112 Terraform remediation templates** (81 AWS + 31 Azure) -- **632 tests** that all pass on every commit +- **706 tests** that all pass on every commit None of the claims in this README are written by hand and hoped-for — every numeric claim is AST-counted from source by an integrity test diff --git a/docs/media/shasta-voice-demo.mp4 b/docs/media/shasta-voice-demo.mp4 new file mode 100644 index 0000000..3886088 Binary files /dev/null and b/docs/media/shasta-voice-demo.mp4 differ diff --git a/docs/superpowers/plans/2026-05-05-shastavoice.md b/docs/superpowers/plans/2026-05-05-shastavoice.md new file mode 100644 index 0000000..ac66b74 --- /dev/null +++ b/docs/superpowers/plans/2026-05-05-shastavoice.md @@ -0,0 +1,4067 @@ +# ShastaVoice Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add a voice-driven dynamic compliance dashboard to Shasta as an opt-in sibling module (`src/shasta/voice/`) that reads real `ShastaDB` data and lets users speak to their compliance posture across SOC 2 / ISO 27001 / HIPAA / ISO 42001 / EU AI Act, plus light writes to the risk register. + +**Architecture:** Browser-direct WebRTC to OpenAI Realtime API. New FastAPI sub-app on port 8090 that mints ephemeral tokens and exposes 14 tool endpoints. Tool functions are thin wrappers over a `store.py` facade that composes existing Shasta primitives (`ShastaDB`, `compliance.scorer`, `compliance.mapper`, ISO/HIPAA equivalents). Pre-built React bundle ships in the wheel — no Node required at runtime. + +**Tech Stack:** Python 3.11+, FastAPI, Pydantic v2, httpx, pytest. React 18 + Vite + TypeScript, Zustand, Framer Motion. OpenAI Realtime (`gpt-realtime`). + +**Working directory:** `E:\Projects\Vanta` (the Shasta repo on the user's machine). + +**VoiceApp source for reused code:** `E:\Projects\Misc\VoiceApp\` — the prior prototype. Several React modules transfer verbatim; tasks reference them by path rather than re-pasting all the source. + +--- + +## Repo layout (additive) + +``` +E:/Projects/Vanta/ + pyproject.toml # MODIFY (add [voice] extra + package-data) + README.md # MODIFY (add voice section) + .gitattributes # MODIFY (mark web/dist as generated) + src/shasta/ + voice/ # NEW MODULE + __init__.py + __main__.py # `python -m shasta.voice` + cli.py # main() — argparse + checks + uvicorn + app.py # FastAPI app + CORS + static files + session.py # /session/token + realtime_config.py # Distiller + 14 tool schemas + VAD + observability.py # JSON tool-call logging + models.py # Pydantic I/O models for tools + store.py # Facade over ShastaDB + scoring/mapper + tools/ + __init__.py + findings.py + scores.py + controls.py + risks.py + scans.py + router.py # FastAPI router + 14 endpoints + web/ + package.json + vite.config.ts + tsconfig.json + tsconfig.node.json + index.html + src/ + main.tsx + App.tsx + styles/{tokens.css, global.css} + voice/{connection.ts, events.ts, cardDispatcher.ts} + tools/{types.ts, relay.ts} + state/session.ts + components/{Header.tsx, MicChrome.tsx, Transcript.tsx, CardSlot.tsx} + components/cards/{ + SeverityBadge.tsx, ActionToast.tsx, + FindingsList.tsx, FindingDetail.tsx, + ComplianceScore.tsx, MultiFrameworkScore.tsx, + ControlSummary.tsx, RiskList.tsx, RiskDetail.tsx + } + dist/ # COMMITTED build artifact + tests/voice/ + __init__.py + conftest.py # Seeded temp SQLite fixture + test_models.py + test_store_reads.py + test_store_writes.py + test_tools_findings.py + test_tools_scores.py + test_tools_controls.py + test_tools_risks.py + test_tools_scans.py + test_tool_endpoints.py + test_session.py + test_realtime_config.py + test_observability.py + test_cli.py +``` + +**Total surface:** ~16 Python files, ~25 React files, ~12 test files. + +--- + +## Working assumptions (verified from Shasta source) + +- `src/shasta/evidence/models.py` exports: `Severity` (CRITICAL/HIGH/MEDIUM/LOW/INFO), `ComplianceStatus` (PASS/FAIL/PARTIAL/NOT_ASSESSED/NOT_APPLICABLE), `CloudProvider` (AWS/AZURE), `CheckDomain`, `Finding`, `ScanResult`, `ScanSummary`, `Evidence`. Enum `.value`s are lowercase. +- `Finding` field names (snake_case): `id`, `check_id`, `title`, `description`, `severity`, `status`, `domain`, `resource_type`, `resource_id`, `region`, `account_id`, `cloud_provider`, `remediation`, `details` (dict), `soc2_controls`, `cis_aws_controls`, `cis_azure_controls`, `mcsb_controls`, `iso27001_controls`, `hipaa_controls`, `timestamp`. +- `src/shasta/db/schema.py` exports `ShastaDB` with: `initialize()`, `save_scan(scan)`, `save_evidence(ev)`, `get_latest_scan(account_id?)`, `get_scan_history(account_id?, limit=10)`, `save_risk_items(items, account_id)` — uses INSERT OR REPLACE so same call adds or updates, `get_risk_items(account_id)`, `get_recent_scan(max_age_minutes, account_id?)`, `close()`. +- `src/shasta/compliance/scorer.py`: `calculate_score(findings) -> ComplianceScore` (dataclass with `total_controls`, `passing`, `failing`, `partial`, `not_assessed`, `requires_policy`, `score_percentage`, `grade`, `total_findings`, `findings_passed`, `findings_failed`, `findings_partial`). +- `src/shasta/compliance/mapper.py`: `enrich_findings_with_controls(findings)` (mutates), `get_control_summary(findings) -> dict[str, dict]`. +- ISO/HIPAA equivalents under `compliance/iso27001_*.py` and `compliance/hipaa_*.py` (signatures mirror SOC 2). +- AI governance scoring under `compliance/ai/scorer.py` — used conditionally when AI findings exist. +- **There is no single-row `add_risk_item` on ShastaDB** — `save_risk_items` takes a list and does INSERT OR REPLACE. Voice writes will construct a single-element list. +- **Risk items expect a `RiskItem` model with attributes** (`item.risk_id`, `item.title`, etc.) — verify the model location early in Task 4 and import accordingly. + +--- + +## Task 0: Branch + voice module skeleton + +**Files:** +- Create branch `feat/voice-console` +- Create: `src/shasta/voice/__init__.py` (empty) +- Create: `src/shasta/voice/__main__.py` +- Create: `tests/voice/__init__.py` (empty) +- Modify: `.gitattributes` + +- [ ] **Step 1: Create feature branch from main** + +```bash +cd E:/Projects/Vanta +git status # confirm clean working tree on main; if not, ASK USER before continuing +git checkout -b feat/voice-console +``` + +If `git status` shows uncommitted work, STOP and report — don't switch branches with dirty state. + +- [ ] **Step 2: Create empty package init files** + +```bash +mkdir -p src/shasta/voice/tools src/shasta/voice/web src/shasta/voice/web/src tests/voice +touch src/shasta/voice/__init__.py +touch src/shasta/voice/tools/__init__.py +touch tests/voice/__init__.py +``` + +(On PowerShell: `New-Item -ItemType File -Force ` instead of `touch`. Bash via the tool also works.) + +- [ ] **Step 3: Create __main__.py** + +`src/shasta/voice/__main__.py`: +```python +"""Entry point for `python -m shasta.voice`.""" +from shasta.voice.cli import main + +if __name__ == "__main__": + main() +``` + +- [ ] **Step 4: Update .gitattributes** + +Append to `E:/Projects/Vanta/.gitattributes` (create if missing): +``` +src/shasta/voice/web/dist/** linguist-generated=true +src/shasta/voice/web/dist/** -diff +``` + +- [ ] **Step 5: Commit** + +```bash +git add src/shasta/voice/__init__.py src/shasta/voice/__main__.py src/shasta/voice/tools/__init__.py tests/voice/__init__.py .gitattributes +git commit -m "feat(voice): scaffold voice module skeleton" +``` + +--- + +## Task 1: pyproject.toml — add `voice` extra and package-data + +**Files:** +- Modify: `pyproject.toml` + +- [ ] **Step 1: Add `voice` extra** + +Find `[project.optional-dependencies]` (already has `azure`, `dashboard`, `semgrep`, `dev`). Add a new `voice` extra: + +```toml +voice = [ + "fastapi>=0.115.0", + "uvicorn[standard]>=0.32.0", + "httpx>=0.27.0", +] +``` + +- [ ] **Step 2: Add package-data declaration** + +Append (or augment, if `[tool.hatch.build]` already exists): + +```toml +[tool.hatch.build.targets.wheel] +packages = ["src/shasta"] + +[tool.hatch.build.targets.wheel.force-include] +"src/shasta/voice/web/dist" = "shasta/voice/web/dist" +``` + +(Shasta uses `hatchling` as its build backend per existing `[build-system]` block. If that block is missing or different, match the existing build-system convention.) + +- [ ] **Step 3: Install the new extra** + +```bash +pip install -e ".[dev,voice]" +``` + +Expected: installs FastAPI, uvicorn, httpx without errors. + +- [ ] **Step 4: Commit** + +```bash +git add pyproject.toml +git commit -m "chore(voice): add voice optional-dependency and package-data" +``` + +--- + +## Task 2: Pydantic I/O models for tools + +**Files:** +- Create: `src/shasta/voice/models.py` +- Create: `tests/voice/test_models.py` + +These wrap Shasta's domain models (`Finding`, etc.) into the JSON shapes that ride the WebRTC data channel and feed React types. We define them explicitly because (a) tools return narrowed projections (e.g., FindingSummary without `details`), and (b) some tools return composite shapes (`MultiFrameworkScore`) that don't exist in Shasta core. + +- [ ] **Step 1: Write the failing test** + +`tests/voice/test_models.py`: +```python +from datetime import datetime, timezone + +from shasta.voice.models import ( + ActionResult, + ComplianceScoreView, + ControlSummaryView, + FindingDetailView, + FindingSummary, + MultiFrameworkScoreView, + RiskItemView, + ScanSummaryView, + ScoreTrendView, +) + + +def test_finding_summary_minimal(): + f = FindingSummary( + id="abc123", + check_id="iam-mfa-enabled", + title="MFA missing on user", + severity="critical", + status="fail", + domain="iam", + resource_id="arn:aws:iam::1:user/x", + cloud_provider="aws", + soc2_controls=["CC6.1"], + iso27001_controls=[], + hipaa_controls=[], + ) + assert f.severity == "critical" + assert f.soc2_controls == ["CC6.1"] + + +def test_finding_detail_extends_summary(): + d = FindingDetailView( + id="abc123", + check_id="iam-mfa-enabled", + title="t", + severity="critical", + status="fail", + domain="iam", + resource_id="r", + cloud_provider="aws", + soc2_controls=[], + iso27001_controls=[], + hipaa_controls=[], + description="desc", + remediation="fix", + region="us-east-1", + account_id="1", + details={"foo": "bar"}, + timestamp=datetime(2026, 5, 5, tzinfo=timezone.utc), + ) + assert d.description == "desc" + assert d.details == {"foo": "bar"} + + +def test_compliance_score_view(): + s = ComplianceScoreView( + framework="soc2", + score_percentage=82.5, + grade="B", + total_controls=40, + passing=30, + failing=8, + partial=2, + not_assessed=0, + total_findings=120, + findings_failed=15, + ) + assert s.framework == "soc2" + assert s.score_percentage == 82.5 + + +def test_multi_framework_score_view(): + m = MultiFrameworkScoreView( + frameworks=[ + ComplianceScoreView(framework="soc2", score_percentage=82.5, grade="B", total_controls=40, passing=30, failing=8, partial=2, not_assessed=0, total_findings=120, findings_failed=15), + ], + not_enabled=["hipaa"], + ) + assert len(m.frameworks) == 1 + assert m.not_enabled == ["hipaa"] + + +def test_score_trend_view(): + t = ScoreTrendView( + framework="soc2", + points=[ + {"scan_id": "s1", "completed_at": "2026-05-01T00:00:00Z", "score_percentage": 78.0}, + {"scan_id": "s2", "completed_at": "2026-05-04T00:00:00Z", "score_percentage": 82.5}, + ], + delta=4.5, + ) + assert t.delta == 4.5 + assert len(t.points) == 2 + + +def test_control_summary_view(): + c = ControlSummaryView( + framework="soc2", + control_id="CC6.1", + title="Logical access security", + overall_status="fail", + pass_count=2, + fail_count=3, + partial_count=1, + finding_ids=["a", "b", "c"], + ) + assert c.overall_status == "fail" + assert c.fail_count == 3 + + +def test_risk_item_view(): + r = RiskItemView( + risk_id="R-001", + title="t", + description="d", + category="cat", + likelihood="medium", + impact="high", + risk_score=6, + risk_level="high", + treatment="mitigate", + treatment_plan="plan", + status="open", + soc2_controls=["CC6.1"], + related_finding=None, + ) + assert r.risk_score == 6 + + +def test_action_result(): + res = ActionResult(success=True, message="ok", record_id="R-001") + assert res.success is True + + +def test_scan_summary_view(): + s = ScanSummaryView( + scan_id="s1", + account_id="1", + cloud_provider="aws", + completed_at=datetime(2026, 5, 5, tzinfo=timezone.utc), + total_findings=34, + critical_count=4, + high_count=11, + medium_count=15, + low_count=4, + passed=20, + failed=14, + ) + assert s.total_findings == 34 +``` + +- [ ] **Step 2: Run test to verify it fails** + +```bash +cd E:/Projects/Vanta +pytest tests/voice/test_models.py -v +``` +Expected: FAIL — `ModuleNotFoundError: No module named 'shasta.voice.models'` + +- [ ] **Step 3: Implement `src/shasta/voice/models.py`** + +```python +"""Pydantic I/O models for voice tools — JSON-serializable views over Shasta core models.""" +from __future__ import annotations + +from datetime import datetime +from typing import Any, Literal + +from pydantic import BaseModel, Field + +Severity = Literal["critical", "high", "medium", "low", "info"] +Status = Literal["pass", "fail", "partial", "not_assessed", "not_applicable"] +Cloud = Literal["aws", "azure"] +Framework = Literal["soc2", "iso27001", "hipaa", "iso42001", "eu_ai_act", "ai_governance"] + + +class FindingSummary(BaseModel): + id: str + check_id: str + title: str + severity: Severity + status: Status + domain: str + resource_id: str + cloud_provider: Cloud + soc2_controls: list[str] = Field(default_factory=list) + iso27001_controls: list[str] = Field(default_factory=list) + hipaa_controls: list[str] = Field(default_factory=list) + + +class FindingDetailView(FindingSummary): + description: str + remediation: str = "" + region: str + account_id: str + details: dict[str, Any] = Field(default_factory=dict) + timestamp: datetime + + +class ComplianceScoreView(BaseModel): + framework: Framework + score_percentage: float + grade: str + total_controls: int + passing: int + failing: int + partial: int + not_assessed: int + total_findings: int + findings_failed: int + + +class MultiFrameworkScoreView(BaseModel): + frameworks: list[ComplianceScoreView] = Field(default_factory=list) + not_enabled: list[Framework] = Field(default_factory=list) + + +class ScoreTrendView(BaseModel): + framework: Framework + points: list[dict[str, Any]] = Field(default_factory=list) + delta: float # latest - earliest + + +class ControlSummaryView(BaseModel): + framework: Framework + control_id: str + title: str + overall_status: str + pass_count: int + fail_count: int + partial_count: int + finding_ids: list[str] = Field(default_factory=list) + + +class RiskItemView(BaseModel): + risk_id: str + title: str + description: str + category: str + likelihood: str + impact: str + risk_score: int + risk_level: str + treatment: str + treatment_plan: str | None = None + status: str + soc2_controls: list[str] = Field(default_factory=list) + related_finding: str | None = None + + +class ScanSummaryView(BaseModel): + scan_id: str + account_id: str + cloud_provider: Cloud + completed_at: datetime | None + total_findings: int + critical_count: int + high_count: int + medium_count: int + low_count: int + passed: int + failed: int + + +class ActionResult(BaseModel): + success: bool + message: str + record_id: str | None = None +``` + +- [ ] **Step 4: Run test to verify it passes** + +```bash +pytest tests/voice/test_models.py -v +``` +Expected: 9 passing. + +- [ ] **Step 5: Commit** + +```bash +git add src/shasta/voice/models.py tests/voice/test_models.py +git commit -m "feat(voice): Pydantic I/O models for tool inputs and outputs" +``` + +--- + +## Task 3: Test SQLite fixture (seeded scan) + +**Files:** +- Create: `tests/voice/conftest.py` + +This fixture is the test backbone — every store and tool test uses it. It seeds a fresh SQLite at `tmp_path` with a curated `ScanResult + Findings + RiskItems` payload exercising every code path: multiple severities, multiple statuses, multiple cloud providers, findings mapped to multiple frameworks, risk items in multiple states. + +- [ ] **Step 1: Write conftest.py** + +`tests/voice/conftest.py`: +```python +"""Shared test fixtures for voice tests — seeded SQLite at tmp_path.""" +from __future__ import annotations + +from datetime import UTC, datetime, timedelta +from pathlib import Path + +import pytest + +from shasta.db.schema import ShastaDB +from shasta.evidence.models import ( + CheckDomain, + CloudProvider, + ComplianceStatus, + Finding, + ScanResult, + Severity, +) + + +def _ago(hours: float) -> datetime: + return datetime.now(UTC) - timedelta(hours=hours) + + +def _make_finding( + *, + id: str, + check_id: str, + title: str, + severity: Severity, + status: ComplianceStatus, + domain: CheckDomain, + resource_id: str, + soc2: list[str] | None = None, + iso27001: list[str] | None = None, + hipaa: list[str] | None = None, + cloud: CloudProvider = CloudProvider.AWS, + description: str = "desc", + remediation: str = "fix", +) -> Finding: + return Finding( + id=id, + check_id=check_id, + title=title, + description=description, + severity=severity, + status=status, + domain=domain, + resource_type="AWS::IAM::User", + resource_id=resource_id, + region="us-east-1", + account_id="123456789012", + cloud_provider=cloud, + remediation=remediation, + soc2_controls=soc2 or [], + iso27001_controls=iso27001 or [], + hipaa_controls=hipaa or [], + timestamp=_ago(1), + ) + + +@pytest.fixture +def seeded_db_path(tmp_path: Path) -> Path: + """Return a path to a fresh SQLite seeded with realistic scan + findings + risks.""" + db_path = tmp_path / "shasta-test.db" + db = ShastaDB(db_path=db_path) + db.initialize() + + findings = [ + # 4 critical, mixed clouds and frameworks + _make_finding(id="f-001", check_id="iam-mfa-enabled", title="MFA missing on root", + severity=Severity.CRITICAL, status=ComplianceStatus.FAIL, + domain=CheckDomain.IAM, resource_id="arn:aws:iam::123:root", + soc2=["CC6.1"], iso27001=["A.5.15"], hipaa=["164.312(a)(1)"]), + _make_finding(id="f-002", check_id="s3-public-access-block", title="S3 bucket allows public access", + severity=Severity.CRITICAL, status=ComplianceStatus.FAIL, + domain=CheckDomain.STORAGE, resource_id="arn:aws:s3:::prod-data", + soc2=["CC6.1", "CC6.6"], iso27001=["A.5.10"]), + _make_finding(id="f-003", check_id="azure-sql-tls", title="SQL TLS below 1.2", + severity=Severity.CRITICAL, status=ComplianceStatus.FAIL, + domain=CheckDomain.ENCRYPTION, resource_id="/subscriptions/x/sql/y", + cloud=CloudProvider.AZURE, soc2=["CC6.7"]), + _make_finding(id="f-004", check_id="cloudtrail-enabled", title="CloudTrail disabled in audit account", + severity=Severity.CRITICAL, status=ComplianceStatus.FAIL, + domain=CheckDomain.LOGGING, resource_id="arn:aws:cloudtrail::123:trail/audit", + soc2=["CC7.1", "CC7.2"]), + # 3 high + _make_finding(id="f-010", check_id="iam-stale-key", title="Stale IAM key >180d", + severity=Severity.HIGH, status=ComplianceStatus.FAIL, + domain=CheckDomain.IAM, resource_id="arn:aws:iam::123:user/legacy-bot", + soc2=["CC6.3"]), + _make_finding(id="f-011", check_id="sg-open-22", title="Security group open on 22", + severity=Severity.HIGH, status=ComplianceStatus.FAIL, + domain=CheckDomain.NETWORKING, resource_id="sg-0e1f2a3b", + soc2=["CC6.6"]), + _make_finding(id="f-012", check_id="lambda-perm-role", title="Lambda overly permissive role", + severity=Severity.HIGH, status=ComplianceStatus.FAIL, + domain=CheckDomain.IAM, resource_id="arn:aws:lambda::123:function/proc", + soc2=["CC6.1"]), + # 3 medium, mixed status + _make_finding(id="f-020", check_id="s3-versioning", title="S3 versioning off", + severity=Severity.MEDIUM, status=ComplianceStatus.FAIL, + domain=CheckDomain.STORAGE, resource_id="arn:aws:s3:::dev-build"), + _make_finding(id="f-021", check_id="ebs-encryption-default", title="EBS default encryption", + severity=Severity.MEDIUM, status=ComplianceStatus.PASS, + domain=CheckDomain.ENCRYPTION, resource_id="ebs-default", + soc2=["CC6.7"]), + _make_finding(id="f-022", check_id="vpc-flow-logs", title="VPC flow logs off", + severity=Severity.MEDIUM, status=ComplianceStatus.PARTIAL, + domain=CheckDomain.MONITORING, resource_id="vpc-abc", + soc2=["CC7.2"]), + ] + + scan = ScanResult( + id="scan-test-001", + account_id="123456789012", + region="us-east-1", + cloud_provider=CloudProvider.AWS, + domains_scanned=[CheckDomain.IAM, CheckDomain.STORAGE, CheckDomain.NETWORKING, + CheckDomain.ENCRYPTION, CheckDomain.LOGGING, CheckDomain.MONITORING], + findings=findings, + started_at=_ago(2), + ) + scan.complete() + db.save_scan(scan) + + # Save an older scan too so trend queries have at least 2 datapoints + older_findings = [ + _make_finding(id=f"old-{f.id}", check_id=f.check_id, title=f.title, + severity=f.severity, status=f.status, domain=f.domain, + resource_id=f.resource_id, soc2=f.soc2_controls, + iso27001=f.iso27001_controls, hipaa=f.hipaa_controls) + for f in findings[:7] # fewer findings = different score + ] + older_scan = ScanResult( + id="scan-test-old", + account_id="123456789012", + region="us-east-1", + cloud_provider=CloudProvider.AWS, + domains_scanned=[CheckDomain.IAM, CheckDomain.STORAGE], + findings=older_findings, + started_at=_ago(72), + ) + older_scan.complete() + db.save_scan(older_scan) + + db.close() + return db_path + + +@pytest.fixture +def store(seeded_db_path: Path): + """Convenience: a Store instance pointed at the seeded DB.""" + from shasta.voice.store import Store + s = Store(db_path=seeded_db_path) + yield s + s.close() +``` + +- [ ] **Step 2: Run pytest collection to confirm no syntax errors** + +```bash +pytest tests/voice/conftest.py --collect-only +``` +Expected: pytest reports it as a fixture file, no errors. (Tests using it come in later tasks.) + +- [ ] **Step 3: Commit** + +```bash +git add tests/voice/conftest.py +git commit -m "test(voice): seeded SQLite fixture for store and tool tests" +``` + +--- + +## Task 4: store.py — read methods + +**Files:** +- Create: `src/shasta/voice/store.py` +- Create: `tests/voice/test_store_reads.py` + +The store is the only module that touches `ShastaDB` and the existing scoring/mapper functions. Tools call into the store; the store calls into Shasta core. + +- [ ] **Step 1: Write the failing test** + +`tests/voice/test_store_reads.py`: +```python +from datetime import datetime + +from shasta.voice.store import Store + + +def test_get_latest_scan_summary(store: Store): + s = store.get_latest_scan() + assert s is not None + assert s.scan_id == "scan-test-001" + assert s.total_findings == 10 + assert s.critical_count == 4 + + +def test_list_findings_unfiltered(store: Store): + findings = store.list_findings() + assert len(findings) == 10 + + +def test_list_findings_severity_critical(store: Store): + findings = store.list_findings(severity="critical") + assert len(findings) == 4 + assert all(f.severity == "critical" for f in findings) + + +def test_list_findings_status_fail(store: Store): + findings = store.list_findings(status="fail") + assert len(findings) == 8 + assert all(f.status == "fail" for f in findings) + + +def test_list_findings_cloud_azure(store: Store): + findings = store.list_findings(cloud="azure") + assert len(findings) == 1 + assert findings[0].id == "f-003" + + +def test_list_findings_framework_soc2(store: Store): + findings = store.list_findings(framework="soc2") + assert all(f.soc2_controls for f in findings) + + +def test_list_findings_control_id(store: Store): + findings = store.list_findings(framework="soc2", control_id="CC6.1") + assert all("CC6.1" in f.soc2_controls for f in findings) + assert len(findings) == 3 + + +def test_list_findings_limit(store: Store): + findings = store.list_findings(limit=3) + assert len(findings) == 3 + + +def test_get_finding_known(store: Store): + f = store.get_finding("f-001") + assert f is not None + assert f.title == "MFA missing on root" + assert f.description == "desc" + + +def test_get_finding_unknown(store: Store): + assert store.get_finding("does-not-exist") is None + + +def test_list_top_blockers_default(store: Store): + blockers = store.list_top_blockers() + assert len(blockers) == 5 + # Sorted by severity (critical > high > medium), then status=fail first + assert blockers[0].severity == "critical" + + +def test_get_resource_findings(store: Store): + findings = store.get_resource_findings("arn:aws:s3:::prod-data") + assert len(findings) == 1 + assert findings[0].id == "f-002" + + +def test_get_compliance_score_soc2(store: Store): + score = store.get_compliance_score("soc2") + assert score.framework == "soc2" + assert 0 <= score.score_percentage <= 100 + assert score.grade in ("A", "B", "C", "D", "F") + + +def test_get_multi_framework_score(store: Store): + multi = store.get_multi_framework_score() + frameworks_present = {s.framework for s in multi.frameworks} + assert "soc2" in frameworks_present + + +def test_get_score_trend_soc2(store: Store): + trend = store.get_score_trend("soc2", limit=10) + assert trend.framework == "soc2" + assert len(trend.points) >= 2 + + +def test_get_control_summary_soc2_specific(store: Store): + summaries = store.get_control_summary("soc2", control_id="CC6.1") + assert len(summaries) == 1 + assert summaries[0].control_id == "CC6.1" + assert summaries[0].fail_count >= 1 + + +def test_get_control_summary_soc2_all(store: Store): + summaries = store.get_control_summary("soc2") + assert len(summaries) >= 1 + + +def test_list_scans(store: Store): + scans = store.list_scans(limit=10) + assert len(scans) == 2 + # Most recent first + assert scans[0].scan_id == "scan-test-001" +``` + +- [ ] **Step 2: Run tests — expect failure** + +```bash +pytest tests/voice/test_store_reads.py -v +``` +Expected: FAIL — `ModuleNotFoundError: No module named 'shasta.voice.store'` + +- [ ] **Step 3: Implement `src/shasta/voice/store.py` (read methods only)** + +```python +"""Voice store — facade over ShastaDB + compliance scoring/mapper functions. + +The only place in the voice module that touches Shasta core. Replace the body +of any read method to swap data sources without touching tool code. +""" +from __future__ import annotations + +from datetime import UTC, datetime +from pathlib import Path + +from shasta.compliance.hipaa_mapper import ( + enrich_findings_with_hipaa, + get_hipaa_control_summary, +) +from shasta.compliance.hipaa_scorer import calculate_hipaa_score +from shasta.compliance.iso27001_mapper import ( + enrich_findings_with_iso27001, + get_iso27001_control_summary, +) +from shasta.compliance.iso27001_scorer import calculate_iso27001_score +from shasta.compliance.mapper import enrich_findings_with_controls, get_control_summary +from shasta.compliance.scorer import calculate_score +from shasta.db.schema import ShastaDB +from shasta.evidence.models import Finding, ScanResult +from shasta.voice.models import ( + ComplianceScoreView, + ControlSummaryView, + FindingDetailView, + FindingSummary, + Framework, + MultiFrameworkScoreView, + ScanSummaryView, + ScoreTrendView, +) + + +# Scoring + mapper dispatch tables +_SCORERS = { + "soc2": calculate_score, + "iso27001": calculate_iso27001_score, + "hipaa": calculate_hipaa_score, +} + +_CONTROL_SUMMARIES = { + "soc2": get_control_summary, + "iso27001": get_iso27001_control_summary, + "hipaa": get_hipaa_control_summary, +} + + +def _enrich_all(findings: list[Finding]) -> list[Finding]: + enrich_findings_with_controls(findings) + enrich_findings_with_iso27001(findings) + enrich_findings_with_hipaa(findings) + return findings + + +def _finding_to_summary(f: Finding) -> FindingSummary: + return FindingSummary( + id=f.id, + check_id=f.check_id, + title=f.title, + severity=f.severity.value, + status=f.status.value, + domain=f.domain.value, + resource_id=f.resource_id, + cloud_provider=f.cloud_provider.value, + soc2_controls=list(f.soc2_controls), + iso27001_controls=list(f.iso27001_controls), + hipaa_controls=list(f.hipaa_controls), + ) + + +def _finding_to_detail(f: Finding) -> FindingDetailView: + return FindingDetailView( + **_finding_to_summary(f).model_dump(), + description=f.description, + remediation=f.remediation, + region=f.region, + account_id=f.account_id, + details=dict(f.details), + timestamp=f.timestamp if isinstance(f.timestamp, datetime) else datetime.fromisoformat(f.timestamp), + ) + + +_SEVERITY_RANK = {"critical": 0, "high": 1, "medium": 2, "low": 3, "info": 4} + + +class Store: + """Read+write facade for the voice module. + + Production seam: replace the body of any method to swap the data source. + Function signatures and return types must not change. + """ + + def __init__(self, db_path: Path | str | None = None): + self._db = ShastaDB(db_path=db_path) if db_path else ShastaDB() + self._db.initialize() + # Cache the latest enriched scan to avoid re-enriching across calls + self._cached_scan: ScanResult | None = None + self._cached_scan_id: str | None = None + + def close(self) -> None: + self._db.close() + + def _latest_scan(self) -> ScanResult | None: + scan = self._db.get_latest_scan() + if scan is None: + return None + if self._cached_scan_id != scan.id: + _enrich_all(scan.findings) + self._cached_scan = scan + self._cached_scan_id = scan.id + return self._cached_scan + + def has_data(self) -> bool: + return self._latest_scan() is not None + + # ---- Scans ---- + + def get_latest_scan(self) -> ScanSummaryView | None: + scan = self._latest_scan() + if scan is None: + return None + summary = scan.summary + return ScanSummaryView( + scan_id=scan.id, + account_id=scan.account_id, + cloud_provider=scan.cloud_provider.value if hasattr(scan.cloud_provider, "value") else scan.cloud_provider, + completed_at=scan.completed_at if isinstance(scan.completed_at, datetime) else (datetime.fromisoformat(scan.completed_at) if scan.completed_at else None), + total_findings=summary.total_findings if summary else len(scan.findings), + critical_count=summary.critical_count if summary else 0, + high_count=summary.high_count if summary else 0, + medium_count=summary.medium_count if summary else 0, + low_count=summary.low_count if summary else 0, + passed=summary.passed if summary else 0, + failed=summary.failed if summary else 0, + ) + + def list_scans(self, limit: int = 10) -> list[ScanSummaryView]: + history = self._db.get_scan_history(limit=limit) + out: list[ScanSummaryView] = [] + for row in history: + import json as _json + summary_blob = row.get("summary") + if summary_blob: + s = _json.loads(summary_blob) + else: + s = {} + out.append(ScanSummaryView( + scan_id=row["id"], + account_id=row["account_id"], + cloud_provider="aws", # history rows don't carry cloud_provider in this query + completed_at=datetime.fromisoformat(row["completed_at"]) if row.get("completed_at") else None, + total_findings=s.get("total_findings", 0), + critical_count=s.get("critical_count", 0), + high_count=s.get("high_count", 0), + medium_count=s.get("medium_count", 0), + low_count=s.get("low_count", 0), + passed=s.get("passed", 0), + failed=s.get("failed", 0), + )) + return out + + # ---- Findings ---- + + def list_findings( + self, + severity: str | None = None, + status: str | None = None, + domain: str | None = None, + cloud: str | None = None, + framework: Framework | None = None, + control_id: str | None = None, + limit: int | None = None, + ) -> list[FindingSummary]: + scan = self._latest_scan() + if scan is None: + return [] + results = list(scan.findings) + if severity: + results = [f for f in results if f.severity.value == severity] + if status: + results = [f for f in results if f.status.value == status] + if domain: + results = [f for f in results if f.domain.value == domain] + if cloud: + results = [f for f in results if f.cloud_provider.value == cloud] + if framework: + attr = {"soc2": "soc2_controls", "iso27001": "iso27001_controls", "hipaa": "hipaa_controls"}.get(framework) + if attr: + results = [f for f in results if getattr(f, attr)] + if control_id and framework: + attr = {"soc2": "soc2_controls", "iso27001": "iso27001_controls", "hipaa": "hipaa_controls"}.get(framework) + if attr: + results = [f for f in results if control_id in getattr(f, attr)] + results.sort(key=lambda f: (_SEVERITY_RANK.get(f.severity.value, 99), 0 if f.status.value == "fail" else 1)) + if limit: + results = results[:limit] + return [_finding_to_summary(f) for f in results] + + def get_finding(self, finding_id: str) -> FindingDetailView | None: + scan = self._latest_scan() + if scan is None: + return None + for f in scan.findings: + if f.id == finding_id: + return _finding_to_detail(f) + return None + + def list_top_blockers(self, limit: int = 5) -> list[FindingSummary]: + return self.list_findings(status="fail", limit=limit) + + def get_resource_findings(self, resource_id: str) -> list[FindingSummary]: + scan = self._latest_scan() + if scan is None: + return [] + matches = [f for f in scan.findings if f.resource_id == resource_id] + matches.sort(key=lambda f: _SEVERITY_RANK.get(f.severity.value, 99)) + return [_finding_to_summary(f) for f in matches] + + # ---- Scores ---- + + def _score_view(self, framework: Framework, score) -> ComplianceScoreView: + return ComplianceScoreView( + framework=framework, + score_percentage=getattr(score, "score_percentage", 0.0), + grade=getattr(score, "grade", "F"), + total_controls=getattr(score, "total_controls", 0), + passing=getattr(score, "passing", 0), + failing=getattr(score, "failing", 0), + partial=getattr(score, "partial", 0), + not_assessed=getattr(score, "not_assessed", 0), + total_findings=getattr(score, "total_findings", 0), + findings_failed=getattr(score, "findings_failed", 0), + ) + + def get_compliance_score(self, framework: Framework) -> ComplianceScoreView | None: + scan = self._latest_scan() + if scan is None: + return None + scorer = _SCORERS.get(framework) + if scorer is None: + # iso42001 / eu_ai_act / ai_governance — treat as AI governance for now + try: + from shasta.compliance.ai.scorer import calculate_ai_governance_score + ai_findings = [f for f in scan.findings if f.domain.value == "ai_governance"] + if not ai_findings: + return None + score = calculate_ai_governance_score(ai_findings) + return self._score_view(framework, score) + except ImportError: + return None + score = scorer(scan.findings) + return self._score_view(framework, score) + + def get_multi_framework_score(self) -> MultiFrameworkScoreView: + scan = self._latest_scan() + if scan is None: + return MultiFrameworkScoreView() + frameworks: list[ComplianceScoreView] = [] + not_enabled: list[Framework] = [] + for fw in ("soc2", "iso27001", "hipaa"): + score = self.get_compliance_score(fw) # type: ignore[arg-type] + if score is not None and score.total_controls > 0: + frameworks.append(score) + else: + not_enabled.append(fw) # type: ignore[arg-type] + # AI frameworks + for fw in ("iso42001", "eu_ai_act", "ai_governance"): + score = self.get_compliance_score(fw) # type: ignore[arg-type] + if score is not None: + frameworks.append(score) + else: + not_enabled.append(fw) # type: ignore[arg-type] + return MultiFrameworkScoreView(frameworks=frameworks, not_enabled=not_enabled) + + def get_score_trend(self, framework: Framework, limit: int = 10) -> ScoreTrendView: + history = self._db.get_scan_history(limit=limit) + scorer = _SCORERS.get(framework) + points = [] + for row in history: + scan = self._db.get_latest_scan(account_id=row["account_id"]) if False else None # placeholder + # The history row doesn't carry findings; we re-load each scan + from shasta.db.schema import ShastaDB + tmp_scan_id = row["id"] + # Cheap read: only fetch findings for that scan_id + full = self._db._get_findings_for_scan(tmp_scan_id) # noqa: SLF001 — internal but stable + _enrich_all(full) + if scorer is not None: + s = scorer(full) + points.append({ + "scan_id": tmp_scan_id, + "completed_at": row.get("completed_at"), + "score_percentage": getattr(s, "score_percentage", 0.0), + }) + # Oldest first for delta math + points.sort(key=lambda p: p["completed_at"] or "") + delta = (points[-1]["score_percentage"] - points[0]["score_percentage"]) if len(points) >= 2 else 0.0 + return ScoreTrendView(framework=framework, points=points, delta=round(delta, 1)) + + # ---- Controls ---- + + def get_control_summary(self, framework: Framework, control_id: str | None = None) -> list[ControlSummaryView]: + scan = self._latest_scan() + if scan is None: + return [] + summary_fn = _CONTROL_SUMMARIES.get(framework) + if summary_fn is None: + return [] + summary = summary_fn(scan.findings) + out: list[ControlSummaryView] = [] + for cid, data in summary.items(): + if control_id and cid != control_id: + continue + out.append(ControlSummaryView( + framework=framework, + control_id=cid, + title=data.get("title", cid), + overall_status=data.get("overall_status", "not_assessed"), + pass_count=data.get("pass_count", 0), + fail_count=data.get("fail_count", 0), + partial_count=data.get("partial_count", 0), + finding_ids=[f.id for f in data.get("findings", [])], + )) + return out +``` + +- [ ] **Step 4: Run tests** + +```bash +pytest tests/voice/test_store_reads.py -v +``` +Expected: PASS for the read tests. If `get_score_trend`'s use of `_get_findings_for_scan` fails because that's marked private on `ShastaDB`, replace with the public `get_latest_scan` per-account loop or add a public method to ShastaDB in a follow-up. + +If any test fails because `calculate_hipaa_score` isn't importable, leave a `try/except ImportError` around it and skip the relevant test — Shasta core may have moved the symbol. Report the import path issue rather than silently skipping in product code. + +- [ ] **Step 5: Commit** + +```bash +git add src/shasta/voice/store.py tests/voice/test_store_reads.py +git commit -m "feat(voice): store facade with read methods over ShastaDB + compliance scoring" +``` + +--- + +## Task 5: store.py — write methods (risk register) + +**Files:** +- Modify: `src/shasta/voice/store.py` (add risk methods) +- Create: `tests/voice/test_store_writes.py` + +- [ ] **Step 1: Write the failing test** + +`tests/voice/test_store_writes.py`: +```python +from shasta.voice.store import Store + + +def test_list_risk_items_empty(store: Store): + items = store.list_risk_items(account_id="123456789012") + assert items == [] + + +def test_add_risk_item_then_list(store: Store): + res = store.add_risk_item( + account_id="123456789012", + title="Unblock CloudTrail", + description="CloudTrail must be on across all regions", + category="logging", + likelihood="medium", + impact="high", + treatment="mitigate", + treatment_plan="Enable CloudTrail in audit account", + related_finding="f-004", + ) + assert res.success is True + assert res.record_id is not None + + items = store.list_risk_items(account_id="123456789012") + assert len(items) == 1 + assert items[0].title == "Unblock CloudTrail" + assert items[0].risk_score >= 1 + + +def test_get_risk_item_by_id(store: Store): + res = store.add_risk_item( + account_id="123456789012", title="t", description="d", category="iam", + likelihood="low", impact="medium", treatment="accept", + ) + r = store.get_risk_item(res.record_id) + assert r is not None + assert r.risk_id == res.record_id + + +def test_update_risk_status(store: Store): + res = store.add_risk_item( + account_id="123456789012", title="t", description="d", category="iam", + likelihood="low", impact="medium", treatment="accept", + ) + upd = store.update_risk(risk_id=res.record_id, status="resolved", review_notes="closed") + assert upd.success is True + r = store.get_risk_item(res.record_id) + assert r.status == "resolved" + + +def test_update_risk_treatment(store: Store): + res = store.add_risk_item( + account_id="123456789012", title="t", description="d", category="iam", + likelihood="low", impact="medium", treatment="accept", + ) + upd = store.update_risk(risk_id=res.record_id, treatment="mitigate", treatment_plan="new plan") + assert upd.success is True + + +def test_update_risk_unknown_fails(store: Store): + res = store.update_risk(risk_id="R-NOT-EXIST", status="resolved") + assert res.success is False +``` + +- [ ] **Step 2: Run tests — expect failure** + +```bash +pytest tests/voice/test_store_writes.py -v +``` +Expected: FAIL — `AttributeError: 'Store' object has no attribute 'list_risk_items'` + +- [ ] **Step 3: Append risk methods to `src/shasta/voice/store.py`** + +Append at the end of the `Store` class (preserving existing methods): + +```python + # ---- Risks ---- + + def _risk_row_to_view(self, row: dict) -> RiskItemView: + import json as _json + return RiskItemView( + risk_id=row["risk_id"], + title=row["title"], + description=row["description"], + category=row["category"], + likelihood=row["likelihood"], + impact=row["impact"], + risk_score=row["risk_score"], + risk_level=row["risk_level"], + treatment=row["treatment"], + treatment_plan=row.get("treatment_plan"), + status=row["status"], + soc2_controls=_json.loads(row["soc2_controls"]) if row.get("soc2_controls") else [], + related_finding=row.get("related_finding"), + ) + + def list_risk_items(self, account_id: str, status: str | None = None, level: str | None = None) -> list[RiskItemView]: + rows = self._db.get_risk_items(account_id) + if status: + rows = [r for r in rows if r.get("status") == status] + if level: + rows = [r for r in rows if r.get("risk_level") == level] + return [self._risk_row_to_view(r) for r in rows] + + def get_risk_item(self, risk_id: str, account_id: str = "123456789012") -> RiskItemView | None: + rows = self._db.get_risk_items(account_id) + for r in rows: + if r["risk_id"] == risk_id: + return self._risk_row_to_view(r) + return None + + def add_risk_item( + self, + *, + account_id: str, + title: str, + description: str, + category: str, + likelihood: str, + impact: str, + treatment: str, + treatment_plan: str | None = None, + related_finding: str | None = None, + soc2_controls: list[str] | None = None, + ) -> ActionResult: + from datetime import datetime, UTC + from uuid import uuid4 + risk_id = f"R-{uuid4().hex[:8].upper()}" + # Score: simple LxI matrix on a 1-3 scale → 1-9 + scale = {"low": 1, "medium": 2, "high": 3} + l = scale.get(likelihood.lower(), 2) + i = scale.get(impact.lower(), 2) + score = l * i + level = "high" if score >= 6 else "medium" if score >= 3 else "low" + now = datetime.now(UTC).isoformat() + + # Build a record matching the columns expected by save_risk_items + # save_risk_items expects an object with attributes — wrap in a SimpleNamespace + from types import SimpleNamespace + item = SimpleNamespace( + risk_id=risk_id, + title=title, + description=description, + category=category, + likelihood=likelihood, + impact=impact, + risk_score=score, + risk_level=level, + owner=None, + treatment=treatment, + treatment_plan=treatment_plan, + status="open", + soc2_controls=soc2_controls or [], + related_finding=related_finding, + created_date=now, + last_reviewed=now, + review_notes=None, + ) + try: + self._db.save_risk_items([item], account_id=account_id) + except Exception as e: + return ActionResult(success=False, message=f"Failed to add risk: {e}") + return ActionResult(success=True, message=f"Added risk {risk_id}", record_id=risk_id) + + def update_risk( + self, + risk_id: str, + *, + treatment: str | None = None, + treatment_plan: str | None = None, + status: str | None = None, + review_notes: str | None = None, + account_id: str = "123456789012", + ) -> ActionResult: + existing = self.get_risk_item(risk_id, account_id=account_id) + if existing is None: + return ActionResult(success=False, message=f"Risk {risk_id} not found") + from datetime import datetime, UTC + from types import SimpleNamespace + # Recreate the row with overrides — save_risk_items uses INSERT OR REPLACE + scale = {"low": 1, "medium": 2, "high": 3} + l = scale.get(existing.likelihood.lower(), 2) + i = scale.get(existing.impact.lower(), 2) + score = l * i + level = "high" if score >= 6 else "medium" if score >= 3 else "low" + item = SimpleNamespace( + risk_id=existing.risk_id, + title=existing.title, + description=existing.description, + category=existing.category, + likelihood=existing.likelihood, + impact=existing.impact, + risk_score=score, + risk_level=level, + owner=None, + treatment=treatment if treatment is not None else existing.treatment, + treatment_plan=treatment_plan if treatment_plan is not None else existing.treatment_plan, + status=status if status is not None else existing.status, + soc2_controls=existing.soc2_controls, + related_finding=existing.related_finding, + created_date=datetime.now(UTC).isoformat(), # save_risk_items doesn't preserve this on REPLACE + last_reviewed=datetime.now(UTC).isoformat(), + review_notes=review_notes if review_notes is not None else None, + ) + try: + self._db.save_risk_items([item], account_id=account_id) + except Exception as e: + return ActionResult(success=False, message=f"Failed to update risk: {e}") + return ActionResult(success=True, message=f"Updated risk {risk_id}", record_id=risk_id) +``` + +Also update the imports at the top of `store.py`: +```python +from shasta.voice.models import ( + ActionResult, # add + ComplianceScoreView, + ControlSummaryView, + FindingDetailView, + FindingSummary, + Framework, + MultiFrameworkScoreView, + RiskItemView, # add + ScanSummaryView, + ScoreTrendView, +) +``` + +- [ ] **Step 4: Run tests** + +```bash +pytest tests/voice/test_store_writes.py -v +``` +Expected: 6 passing. + +- [ ] **Step 5: Commit** + +```bash +git add src/shasta/voice/store.py tests/voice/test_store_writes.py +git commit -m "feat(voice): risk-register write methods on store facade" +``` + +--- + +## Task 6: Tool functions — findings + +**Files:** +- Create: `src/shasta/voice/tools/findings.py` +- Create: `tests/voice/test_tools_findings.py` + +Tools are thin wrappers — they take dict args, call `Store` methods, return `model_dump(mode="json")` dicts. Errors return `{"error": "...", ...}` rather than raising. + +- [ ] **Step 1: Write the failing test** + +`tests/voice/test_tools_findings.py`: +```python +from shasta.voice.store import Store +from shasta.voice.tools import findings as findings_tool + + +def test_list_findings_returns_dicts(store: Store): + result = findings_tool.list_findings(store=store) + assert isinstance(result, list) + assert all(isinstance(item, dict) for item in result) + assert all("severity" in item for item in result) + + +def test_list_findings_with_filters(store: Store): + result = findings_tool.list_findings(store=store, severity="critical", status="fail") + assert len(result) == 4 + + +def test_get_finding_known(store: Store): + result = findings_tool.get_finding(store=store, finding_id="f-001") + assert result["id"] == "f-001" + assert "description" in result + + +def test_get_finding_unknown_returns_error(store: Store): + result = findings_tool.get_finding(store=store, finding_id="nope") + assert result == {"error": "finding_not_found", "finding_id": "nope"} + + +def test_list_top_blockers(store: Store): + result = findings_tool.list_top_blockers(store=store) + assert len(result) == 5 + + +def test_get_resource_findings_unknown_returns_empty_list(store: Store): + result = findings_tool.get_resource_findings(store=store, resource_id="not-here") + assert result == [] +``` + +- [ ] **Step 2: Run — expect failure** + +```bash +pytest tests/voice/test_tools_findings.py -v +``` +Expected: `ModuleNotFoundError: No module named 'shasta.voice.tools.findings'` + +- [ ] **Step 3: Implement `src/shasta/voice/tools/findings.py`** + +```python +"""Tool functions for finding queries.""" +from typing import Any + +from shasta.voice.store import Store + + +def list_findings( + *, + store: Store, + severity: str | None = None, + status: str | None = None, + domain: str | None = None, + cloud: str | None = None, + framework: str | None = None, + control_id: str | None = None, + limit: int | None = None, +) -> list[dict[str, Any]]: + items = store.list_findings( + severity=severity, status=status, domain=domain, cloud=cloud, + framework=framework, control_id=control_id, limit=limit, + ) + return [i.model_dump(mode="json") for i in items] + + +def get_finding(*, store: Store, finding_id: str) -> dict[str, Any]: + detail = store.get_finding(finding_id) + if detail is None: + return {"error": "finding_not_found", "finding_id": finding_id} + return detail.model_dump(mode="json") + + +def list_top_blockers(*, store: Store, limit: int = 5) -> list[dict[str, Any]]: + return [i.model_dump(mode="json") for i in store.list_top_blockers(limit=limit)] + + +def get_resource_findings(*, store: Store, resource_id: str) -> list[dict[str, Any]]: + return [i.model_dump(mode="json") for i in store.get_resource_findings(resource_id)] +``` + +- [ ] **Step 4: Run — expect pass** + +```bash +pytest tests/voice/test_tools_findings.py -v +``` +Expected: 6 passing. + +- [ ] **Step 5: Commit** + +```bash +git add src/shasta/voice/tools/findings.py tests/voice/test_tools_findings.py +git commit -m "feat(voice): findings tool functions" +``` + +--- + +## Task 7: Tool functions — scores, controls, risks, scans + +**Files:** +- Create: `src/shasta/voice/tools/{scores,controls,risks,scans}.py` +- Create: `tests/voice/test_tools_{scores,controls,risks,scans}.py` + +Same shape as Task 6. Bundled into one task since each module is small (~30 lines). + +- [ ] **Step 1: Write all four test files** + +`tests/voice/test_tools_scores.py`: +```python +from shasta.voice.store import Store +from shasta.voice.tools import scores as scores_tool + + +def test_get_compliance_score_soc2(store: Store): + res = scores_tool.get_compliance_score(store=store, framework="soc2") + assert res["framework"] == "soc2" + assert "score_percentage" in res + + +def test_get_compliance_score_unknown_framework_returns_error(store: Store): + res = scores_tool.get_compliance_score(store=store, framework="bogus") + assert "error" in res + + +def test_get_multi_framework_score(store: Store): + res = scores_tool.get_multi_framework_score(store=store) + assert "frameworks" in res + assert isinstance(res["frameworks"], list) + + +def test_get_score_trend(store: Store): + res = scores_tool.get_score_trend(store=store, framework="soc2", limit=10) + assert res["framework"] == "soc2" + assert "delta" in res + assert "points" in res +``` + +`tests/voice/test_tools_controls.py`: +```python +from shasta.voice.store import Store +from shasta.voice.tools import controls as controls_tool + + +def test_get_control_summary_specific(store: Store): + res = controls_tool.get_control_summary(store=store, framework="soc2", control_id="CC6.1") + assert isinstance(res, list) + assert len(res) == 1 + assert res[0]["control_id"] == "CC6.1" + + +def test_get_control_summary_all(store: Store): + res = controls_tool.get_control_summary(store=store, framework="soc2") + assert isinstance(res, list) + assert len(res) >= 1 +``` + +`tests/voice/test_tools_risks.py`: +```python +from shasta.voice.store import Store +from shasta.voice.tools import risks as risks_tool + + +def test_list_risk_items_empty(store: Store): + assert risks_tool.list_risk_items(store=store, account_id="123456789012") == [] + + +def test_add_risk_item_success(store: Store): + res = risks_tool.add_risk_item( + store=store, account_id="123456789012", + title="t", description="d", category="iam", + likelihood="medium", impact="high", treatment="mitigate", + ) + assert res["success"] is True + assert res["record_id"] + + +def test_get_risk_item_known(store: Store): + add = risks_tool.add_risk_item( + store=store, account_id="123456789012", + title="t", description="d", category="iam", + likelihood="low", impact="low", treatment="accept", + ) + res = risks_tool.get_risk_item(store=store, risk_id=add["record_id"]) + assert res["risk_id"] == add["record_id"] + + +def test_get_risk_item_unknown_returns_error(store: Store): + res = risks_tool.get_risk_item(store=store, risk_id="R-NOPE") + assert res == {"error": "risk_not_found", "risk_id": "R-NOPE"} + + +def test_update_risk(store: Store): + add = risks_tool.add_risk_item( + store=store, account_id="123456789012", + title="t", description="d", category="iam", + likelihood="low", impact="low", treatment="accept", + ) + upd = risks_tool.update_risk(store=store, risk_id=add["record_id"], status="resolved") + assert upd["success"] is True +``` + +`tests/voice/test_tools_scans.py`: +```python +from shasta.voice.store import Store +from shasta.voice.tools import scans as scans_tool + + +def test_list_scans(store: Store): + res = scans_tool.list_scans(store=store, limit=10) + assert isinstance(res, list) + assert len(res) == 2 + + +def test_get_latest_scan(store: Store): + res = scans_tool.get_latest_scan(store=store) + assert res["scan_id"] == "scan-test-001" +``` + +- [ ] **Step 2: Run — expect failure** + +```bash +pytest tests/voice/test_tools_scores.py tests/voice/test_tools_controls.py tests/voice/test_tools_risks.py tests/voice/test_tools_scans.py -v +``` + +- [ ] **Step 3: Implement all four modules** + +`src/shasta/voice/tools/scores.py`: +```python +"""Tool functions for compliance scores.""" +from typing import Any + +from shasta.voice.store import Store + +_VALID_FRAMEWORKS = {"soc2", "iso27001", "hipaa", "iso42001", "eu_ai_act", "ai_governance"} + + +def get_compliance_score(*, store: Store, framework: str) -> dict[str, Any]: + if framework not in _VALID_FRAMEWORKS: + return {"error": "invalid_framework", "framework": framework, "valid": sorted(_VALID_FRAMEWORKS)} + score = store.get_compliance_score(framework) # type: ignore[arg-type] + if score is None: + return {"error": "framework_not_applicable", "framework": framework, "reason": "no_findings_or_scorer_unavailable"} + return score.model_dump(mode="json") + + +def get_multi_framework_score(*, store: Store) -> dict[str, Any]: + return store.get_multi_framework_score().model_dump(mode="json") + + +def get_score_trend(*, store: Store, framework: str, limit: int = 10) -> dict[str, Any]: + if framework not in _VALID_FRAMEWORKS: + return {"error": "invalid_framework", "framework": framework} + return store.get_score_trend(framework, limit=limit).model_dump(mode="json") # type: ignore[arg-type] +``` + +`src/shasta/voice/tools/controls.py`: +```python +"""Tool functions for control summaries.""" +from typing import Any + +from shasta.voice.store import Store + + +def get_control_summary(*, store: Store, framework: str, control_id: str | None = None) -> list[dict[str, Any]]: + return [c.model_dump(mode="json") for c in store.get_control_summary(framework, control_id=control_id)] # type: ignore[arg-type] +``` + +`src/shasta/voice/tools/risks.py`: +```python +"""Tool functions for risk-register operations.""" +from typing import Any + +from shasta.voice.store import Store + + +def list_risk_items(*, store: Store, account_id: str, status: str | None = None, level: str | None = None) -> list[dict[str, Any]]: + return [r.model_dump(mode="json") for r in store.list_risk_items(account_id, status=status, level=level)] + + +def get_risk_item(*, store: Store, risk_id: str, account_id: str = "123456789012") -> dict[str, Any]: + r = store.get_risk_item(risk_id, account_id=account_id) + if r is None: + return {"error": "risk_not_found", "risk_id": risk_id} + return r.model_dump(mode="json") + + +def add_risk_item( + *, + store: Store, + account_id: str, + title: str, + description: str, + category: str, + likelihood: str, + impact: str, + treatment: str, + treatment_plan: str | None = None, + related_finding: str | None = None, +) -> dict[str, Any]: + return store.add_risk_item( + account_id=account_id, title=title, description=description, category=category, + likelihood=likelihood, impact=impact, treatment=treatment, + treatment_plan=treatment_plan, related_finding=related_finding, + ).model_dump(mode="json") + + +def update_risk( + *, + store: Store, + risk_id: str, + treatment: str | None = None, + treatment_plan: str | None = None, + status: str | None = None, + review_notes: str | None = None, + account_id: str = "123456789012", +) -> dict[str, Any]: + return store.update_risk( + risk_id=risk_id, treatment=treatment, treatment_plan=treatment_plan, + status=status, review_notes=review_notes, account_id=account_id, + ).model_dump(mode="json") +``` + +`src/shasta/voice/tools/scans.py`: +```python +"""Tool functions for scan queries.""" +from typing import Any + +from shasta.voice.store import Store + + +def list_scans(*, store: Store, limit: int = 10) -> list[dict[str, Any]]: + return [s.model_dump(mode="json") for s in store.list_scans(limit=limit)] + + +def get_latest_scan(*, store: Store) -> dict[str, Any]: + s = store.get_latest_scan() + if s is None: + return {"error": "no_scan_data"} + return s.model_dump(mode="json") +``` + +- [ ] **Step 4: Run — expect pass** + +```bash +pytest tests/voice/test_tools_scores.py tests/voice/test_tools_controls.py tests/voice/test_tools_risks.py tests/voice/test_tools_scans.py -v +``` + +- [ ] **Step 5: Commit** + +```bash +git add src/shasta/voice/tools/ tests/voice/test_tools_*.py +git commit -m "feat(voice): scores/controls/risks/scans tool functions" +``` + +--- + +## Task 8: Realtime config (Distiller + 14 tool schemas) + +**Files:** +- Create: `src/shasta/voice/realtime_config.py` +- Create: `tests/voice/test_realtime_config.py` + +- [ ] **Step 1: Write the failing test** + +`tests/voice/test_realtime_config.py`: +```python +import json + +from shasta.voice.realtime_config import SYSTEM_PROMPT, TOOL_SCHEMAS, VAD_CONFIG, build_session_payload + + +def test_system_prompt_mentions_shasta_and_25_words(): + assert "Shasta" in SYSTEM_PROMPT + assert "25 words" in SYSTEM_PROMPT + + +def test_system_prompt_mentions_redirects(): + assert "/scan" in SYSTEM_PROMPT + assert "/report" in SYSTEM_PROMPT + + +def test_tool_schemas_cover_all_14_tools(): + names = {t["name"] for t in TOOL_SCHEMAS} + assert names == { + "list_findings", "get_finding", "list_top_blockers", "get_resource_findings", + "get_compliance_score", "get_multi_framework_score", "get_score_trend", + "get_control_summary", + "list_scans", "get_latest_scan", + "list_risk_items", "get_risk_item", "add_risk_item", "update_risk", + } + + +def test_tool_schemas_have_required_fields(): + for s in TOOL_SCHEMAS: + assert s["type"] == "function" + assert "name" in s and "description" in s and "parameters" in s + assert s["parameters"]["type"] == "object" + + +def test_vad_config_uses_server_vad(): + assert VAD_CONFIG["type"] == "server_vad" + + +def test_build_session_payload_shape(): + p = build_session_payload() + assert p["model"] + assert p["voice"] + assert p["instructions"] == SYSTEM_PROMPT + assert p["tools"] == TOOL_SCHEMAS + assert p["input_audio_transcription"]["model"] == "whisper-1" + json.dumps(p) # serializable +``` + +- [ ] **Step 2: Run — expect failure** + +```bash +pytest tests/voice/test_realtime_config.py -v +``` + +- [ ] **Step 3: Implement `src/shasta/voice/realtime_config.py`** + +```python +"""OpenAI Realtime session configuration: Distiller prompt, 14 tool schemas, VAD.""" +import os +from typing import Any + +SYSTEM_PROMPT = """You are Shasta's voice compliance assistant. You are talking to a security engineer or founder over voice. Their data is real — you have read access to their actual scan findings, compliance scores across SOC 2 / ISO 27001 / HIPAA / ISO 42001 / EU AI Act, and risk register. + +VOICE OUTPUT RULES (non-negotiable): +- Maximum 25 words per response unless the user explicitly asks for detail. +- Lead with the most important fact. Numbers before context. Severity before description. Failed counts before passing counts. +- Never read JSON, ARNs, IP addresses, or long control IDs out loud unless the user asks. +- If listing items, name at most 3. Offer to continue ("...and 5 more — want the full list?"). +- Use plain words, not compliance jargon, unless the user uses jargon first. + +TOOL USE: +- For any question about findings, scores, controls, scans, or risks, call a tool. Never invent data. +- For ambiguous questions, make the most reasonable assumption (default: status=fail, scope=latest scan) and proceed; mention your assumption briefly. +- After an action tool succeeds (add_risk_item, update_risk), confirm in one short sentence. +- If a tool returns "no_data" or empty, say so honestly. Do not invent. + +REDIRECTS (out of scope for voice — Shasta runs these via Claude Code skills): +- RUN A SCAN → "Run /scan in Claude Code. Want me to summarize what it'll do first?" +- GENERATE A REPORT/PDF → "Run /report — voice can't deliver PDFs. I can summarize the latest scan." +- GENERATE TERRAFORM → "Run /remediate for the Terraform. I can describe what the fix does." +- GENERATE POLICY DOCS → "Run /policy-gen for the policy docs." + +PERSONA: +- Calm, precise, slightly understated. Experienced compliance engineer on a Tuesday afternoon. +- Adjust register to the audience — technical for engineers, plainer for founders. +- Never apologize for tool latency. Never say "let me check that for you" — just do it. +""" + +TOOL_SCHEMAS: list[dict[str, Any]] = [ + { + "type": "function", "name": "list_findings", + "description": "List compliance findings from the latest scan. Filter by severity, status, domain, cloud, framework, control.", + "parameters": { + "type": "object", + "properties": { + "severity": {"type": "string", "enum": ["critical", "high", "medium", "low", "info"]}, + "status": {"type": "string", "enum": ["pass", "fail", "partial", "not_assessed", "not_applicable"]}, + "domain": {"type": "string", "enum": ["iam", "networking", "encryption", "logging", "compute", "storage", "monitoring", "ai_governance"]}, + "cloud": {"type": "string", "enum": ["aws", "azure"]}, + "framework": {"type": "string", "enum": ["soc2", "iso27001", "hipaa"]}, + "control_id": {"type": "string", "description": "e.g., CC6.1 — only meaningful with framework set"}, + "limit": {"type": "integer", "minimum": 1, "maximum": 100}, + }, + "additionalProperties": False, + }, + }, + { + "type": "function", "name": "get_finding", + "description": "Get full detail of a single finding by ID — description, remediation, affected resource, control mappings.", + "parameters": { + "type": "object", + "properties": {"finding_id": {"type": "string"}}, + "required": ["finding_id"], + "additionalProperties": False, + }, + }, + { + "type": "function", "name": "list_top_blockers", + "description": "List the highest-severity unresolved findings. Use for 'what should I fix first?' questions.", + "parameters": { + "type": "object", + "properties": {"limit": {"type": "integer", "minimum": 1, "maximum": 20}}, + "additionalProperties": False, + }, + }, + { + "type": "function", "name": "get_resource_findings", + "description": "List all findings for a specific cloud resource (by ARN or Azure resource ID).", + "parameters": { + "type": "object", + "properties": {"resource_id": {"type": "string"}}, + "required": ["resource_id"], + "additionalProperties": False, + }, + }, + { + "type": "function", "name": "get_compliance_score", + "description": "Get the compliance score for one framework. Use when the user asks about a specific standard.", + "parameters": { + "type": "object", + "properties": {"framework": {"type": "string", "enum": ["soc2", "iso27001", "hipaa", "iso42001", "eu_ai_act", "ai_governance"]}}, + "required": ["framework"], + "additionalProperties": False, + }, + }, + { + "type": "function", "name": "get_multi_framework_score", + "description": "Get scores for ALL frameworks at once. Use for 'how am I doing across the board?' or 'overall posture' questions.", + "parameters": {"type": "object", "properties": {}, "additionalProperties": False}, + }, + { + "type": "function", "name": "get_score_trend", + "description": "Get score history for a framework across recent scans. Use for 'how does that compare to last week?' questions.", + "parameters": { + "type": "object", + "properties": { + "framework": {"type": "string", "enum": ["soc2", "iso27001", "hipaa"]}, + "limit": {"type": "integer", "minimum": 2, "maximum": 50}, + }, + "required": ["framework"], + "additionalProperties": False, + }, + }, + { + "type": "function", "name": "get_control_summary", + "description": "Get summary of a specific control (e.g., CC6.1) or all controls in a framework. Returns pass/fail counts + finding IDs.", + "parameters": { + "type": "object", + "properties": { + "framework": {"type": "string", "enum": ["soc2", "iso27001", "hipaa"]}, + "control_id": {"type": "string"}, + }, + "required": ["framework"], + "additionalProperties": False, + }, + }, + { + "type": "function", "name": "list_scans", + "description": "List recent scans with summary stats (date, total findings, pass/fail counts).", + "parameters": { + "type": "object", + "properties": {"limit": {"type": "integer", "minimum": 1, "maximum": 50}}, + "additionalProperties": False, + }, + }, + { + "type": "function", "name": "get_latest_scan", + "description": "Get summary of the most recent scan: when it ran, total findings, severity counts.", + "parameters": {"type": "object", "properties": {}, "additionalProperties": False}, + }, + { + "type": "function", "name": "list_risk_items", + "description": "List risk register items. Filter by status (open/in_progress/accepted/resolved) or level (high/medium/low).", + "parameters": { + "type": "object", + "properties": { + "account_id": {"type": "string", "description": "Cloud account ID — pass the user's account from latest scan if unknown"}, + "status": {"type": "string", "enum": ["open", "in_progress", "accepted", "resolved"]}, + "level": {"type": "string", "enum": ["high", "medium", "low"]}, + }, + "required": ["account_id"], + "additionalProperties": False, + }, + }, + { + "type": "function", "name": "get_risk_item", + "description": "Get a single risk register item by ID.", + "parameters": { + "type": "object", + "properties": {"risk_id": {"type": "string"}, "account_id": {"type": "string"}}, + "required": ["risk_id"], + "additionalProperties": False, + }, + }, + { + "type": "function", "name": "add_risk_item", + "description": "Add a new risk to the risk register. Use when the user explicitly asks to record a risk.", + "parameters": { + "type": "object", + "properties": { + "account_id": {"type": "string"}, + "title": {"type": "string"}, + "description": {"type": "string"}, + "category": {"type": "string", "description": "e.g., iam, logging, encryption"}, + "likelihood": {"type": "string", "enum": ["low", "medium", "high"]}, + "impact": {"type": "string", "enum": ["low", "medium", "high"]}, + "treatment": {"type": "string", "enum": ["mitigate", "accept", "transfer", "avoid"]}, + "treatment_plan": {"type": "string"}, + "related_finding": {"type": "string", "description": "Optional finding ID this risk relates to"}, + }, + "required": ["account_id", "title", "description", "category", "likelihood", "impact", "treatment"], + "additionalProperties": False, + }, + }, + { + "type": "function", "name": "update_risk", + "description": "Update an existing risk register item. Pass risk_id plus any fields to change.", + "parameters": { + "type": "object", + "properties": { + "risk_id": {"type": "string"}, + "account_id": {"type": "string"}, + "treatment": {"type": "string", "enum": ["mitigate", "accept", "transfer", "avoid"]}, + "treatment_plan": {"type": "string"}, + "status": {"type": "string", "enum": ["open", "in_progress", "accepted", "resolved"]}, + "review_notes": {"type": "string"}, + }, + "required": ["risk_id"], + "additionalProperties": False, + }, + }, +] + + +VAD_CONFIG: dict[str, Any] = { + "type": "server_vad", + "threshold": 0.5, + "prefix_padding_ms": 300, + "silence_duration_ms": 500, +} + + +def build_session_payload() -> dict[str, Any]: + return { + "model": os.environ.get("OPENAI_REALTIME_MODEL", "gpt-realtime"), + "voice": os.environ.get("OPENAI_REALTIME_VOICE", "cedar"), + "instructions": SYSTEM_PROMPT, + "tools": TOOL_SCHEMAS, + "turn_detection": VAD_CONFIG, + "input_audio_format": "pcm16", + "output_audio_format": "pcm16", + "input_audio_transcription": {"model": "whisper-1"}, + } +``` + +- [ ] **Step 4: Run — expect pass** + +```bash +pytest tests/voice/test_realtime_config.py -v +``` +Expected: 6 passing. + +- [ ] **Step 5: Commit** + +```bash +git add src/shasta/voice/realtime_config.py tests/voice/test_realtime_config.py +git commit -m "feat(voice): realtime session config — Distiller prompt + 14 tool schemas" +``` + +--- + +## Task 9: Observability + session token + +**Files:** +- Create: `src/shasta/voice/observability.py` +- Create: `src/shasta/voice/session.py` +- Create: `tests/voice/test_observability.py` +- Create: `tests/voice/test_session.py` (deferred run — needs app.py from Task 10) + +This task bundles observability and session because they're tiny and adjacent. + +- [ ] **Step 1: Implement observability and its test** + +`src/shasta/voice/observability.py`: +```python +"""Structured logging for tool calls.""" +import json +import logging +import os +from typing import Any + +_LOGGER = logging.getLogger("shasta.voice") + + +def configure_logging() -> None: + level = os.environ.get("LOG_LEVEL", "INFO").upper() + logging.basicConfig(level=level, format="%(message)s") + _LOGGER.setLevel(level) + + +def log_tool_call( + tool_name: str, + args: dict[str, Any], + latency_ms: float, + result_size: int, + error: str | None = None, +) -> None: + payload: dict[str, Any] = { + "event": "tool_call", + "tool_name": tool_name, + "args": args, + "latency_ms": round(latency_ms, 2), + "result_size": result_size, + } + if error: + payload["error"] = error + _LOGGER.info(json.dumps(payload)) +``` + +`tests/voice/test_observability.py`: +```python +import json +import logging + +from shasta.voice.observability import configure_logging, log_tool_call + + +def test_log_tool_call_emits_json(caplog): + configure_logging() + with caplog.at_level(logging.INFO, logger="shasta.voice"): + log_tool_call(tool_name="list_findings", args={"severity": "critical"}, latency_ms=1.234, result_size=4) + payloads = [json.loads(r.message) for r in caplog.records if r.message.startswith("{")] + assert any(p.get("tool_name") == "list_findings" for p in payloads) + matching = [p for p in payloads if p.get("tool_name") == "list_findings"][0] + assert matching["latency_ms"] == 1.23 + assert matching["result_size"] == 4 +``` + +- [ ] **Step 2: Run observability test** + +```bash +pytest tests/voice/test_observability.py -v +``` +Expected: 1 passing. + +- [ ] **Step 3: Implement session.py** + +`src/shasta/voice/session.py`: +```python +"""Ephemeral OpenAI Realtime token endpoint.""" +import os + +import httpx +from fastapi import APIRouter, HTTPException + +from shasta.voice.realtime_config import build_session_payload + +router = APIRouter() + + +@router.post("/session/token") +def mint_session_token() -> dict: + api_key = os.environ.get("OPENAI_API_KEY") + if not api_key or api_key == "sk-replace-me": + raise HTTPException(status_code=500, detail="OPENAI_API_KEY not configured") + + payload = build_session_payload() + try: + response = httpx.post( + "https://api.openai.com/v1/realtime/sessions", + headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}, + json=payload, + timeout=10.0, + ) + except httpx.HTTPError as e: + raise HTTPException(status_code=502, detail=f"OpenAI request failed: {e}") from e + + if response.status_code != 200: + raise HTTPException(status_code=502, detail=f"OpenAI session creation failed ({response.status_code}): {response.text[:200]}") + + body = response.json() + return { + "client_secret": body["client_secret"]["value"], + "expires_at": body["client_secret"]["expires_at"], + "model": payload["model"], + } +``` + +- [ ] **Step 4: Write deferred session test (will run in Task 11)** + +`tests/voice/test_session.py`: +```python +from unittest.mock import MagicMock, patch + + +def test_session_token_endpoint_calls_openai(client): + fake = MagicMock() + fake.status_code = 200 + fake.json.return_value = {"client_secret": {"value": "ek_x", "expires_at": 1735000000}} + + with patch("shasta.voice.session.httpx.post", return_value=fake) as mock_post: + resp = client.post("/session/token") + assert resp.status_code == 200 + body = resp.json() + assert body["client_secret"] == "ek_x" + sent = mock_post.call_args.kwargs["json"] + assert sent["model"] + assert "Shasta" in sent["instructions"] + assert len(sent["tools"]) == 14 + + +def test_session_token_endpoint_handles_openai_error(client): + fake = MagicMock() + fake.status_code = 401 + fake.text = "Invalid key" + + with patch("shasta.voice.session.httpx.post", return_value=fake): + resp = client.post("/session/token") + assert resp.status_code == 502 +``` + +Skip the test run — it depends on a `client` fixture that comes from Task 10's app. + +- [ ] **Step 5: Commit** + +```bash +git add src/shasta/voice/observability.py src/shasta/voice/session.py tests/voice/test_observability.py tests/voice/test_session.py +git commit -m "feat(voice): structured logging + ephemeral OpenAI token endpoint" +``` + +--- + +## Task 10: Tool router + app.py + CLI + +**Files:** +- Create: `src/shasta/voice/tools/router.py` +- Create: `src/shasta/voice/app.py` +- Create: `src/shasta/voice/cli.py` +- Create: `tests/voice/test_tool_endpoints.py` +- Create: `tests/voice/test_cli.py` +- Modify: `tests/voice/conftest.py` (add `client` fixture) + +This is the integration task. After it, the deferred test from Task 9 should pass too. + +- [ ] **Step 1: Implement `src/shasta/voice/tools/router.py`** + +```python +"""HTTP endpoints for tool calls. Browser relays OpenAI tool calls here.""" +import time +from typing import Literal + +from fastapi import APIRouter, Request +from pydantic import BaseModel + +from shasta.voice.observability import log_tool_call +from shasta.voice.tools import controls as controls_tool +from shasta.voice.tools import findings as findings_tool +from shasta.voice.tools import risks as risks_tool +from shasta.voice.tools import scans as scans_tool +from shasta.voice.tools import scores as scores_tool + +router = APIRouter(prefix="/tools") + + +def _store(request: Request): + return request.app.state.store + + +def _timed(tool_name: str, args: dict, fn): + start = time.perf_counter() + result = fn() + latency_ms = (time.perf_counter() - start) * 1000 + size = len(result) if isinstance(result, list) else 1 + log_tool_call(tool_name=tool_name, args=args, latency_ms=latency_ms, result_size=size) + return result + + +# ---------- request models ---------- + +Severity = Literal["critical", "high", "medium", "low", "info"] +Status = Literal["pass", "fail", "partial", "not_assessed", "not_applicable"] +Cloud = Literal["aws", "azure"] +Framework = Literal["soc2", "iso27001", "hipaa", "iso42001", "eu_ai_act", "ai_governance"] +ScoringFramework = Literal["soc2", "iso27001", "hipaa"] +RiskStatus = Literal["open", "in_progress", "accepted", "resolved"] +RiskLevel = Literal["high", "medium", "low"] +Treatment = Literal["mitigate", "accept", "transfer", "avoid"] + + +class ListFindingsReq(BaseModel): + severity: Severity | None = None + status: Status | None = None + domain: str | None = None + cloud: Cloud | None = None + framework: Framework | None = None + control_id: str | None = None + limit: int | None = None + + +class IdReq(BaseModel): + finding_id: str | None = None + risk_id: str | None = None + resource_id: str | None = None + + +class GetComplianceScoreReq(BaseModel): + framework: Framework + + +class GetScoreTrendReq(BaseModel): + framework: ScoringFramework + limit: int = 10 + + +class GetControlSummaryReq(BaseModel): + framework: ScoringFramework + control_id: str | None = None + + +class LimitReq(BaseModel): + limit: int = 5 + + +class ListRisksReq(BaseModel): + account_id: str + status: RiskStatus | None = None + level: RiskLevel | None = None + + +class GetRiskReq(BaseModel): + risk_id: str + account_id: str = "123456789012" + + +class AddRiskReq(BaseModel): + account_id: str + title: str + description: str + category: str + likelihood: RiskLevel + impact: RiskLevel + treatment: Treatment + treatment_plan: str | None = None + related_finding: str | None = None + + +class UpdateRiskReq(BaseModel): + risk_id: str + account_id: str = "123456789012" + treatment: Treatment | None = None + treatment_plan: str | None = None + status: RiskStatus | None = None + review_notes: str | None = None + + +# ---------- endpoints ---------- + +@router.post("/list_findings") +def list_findings(req: ListFindingsReq, request: Request): + return _timed("list_findings", req.model_dump(exclude_none=True), + lambda: findings_tool.list_findings(store=_store(request), **req.model_dump(exclude_none=True))) + + +@router.post("/get_finding") +def get_finding(req: IdReq, request: Request): + return _timed("get_finding", {"finding_id": req.finding_id}, + lambda: findings_tool.get_finding(store=_store(request), finding_id=req.finding_id or "")) + + +@router.post("/list_top_blockers") +def list_top_blockers(req: LimitReq, request: Request): + return _timed("list_top_blockers", {"limit": req.limit}, + lambda: findings_tool.list_top_blockers(store=_store(request), limit=req.limit)) + + +@router.post("/get_resource_findings") +def get_resource_findings(req: IdReq, request: Request): + return _timed("get_resource_findings", {"resource_id": req.resource_id}, + lambda: findings_tool.get_resource_findings(store=_store(request), resource_id=req.resource_id or "")) + + +@router.post("/get_compliance_score") +def get_compliance_score(req: GetComplianceScoreReq, request: Request): + return _timed("get_compliance_score", req.model_dump(), + lambda: scores_tool.get_compliance_score(store=_store(request), framework=req.framework)) + + +@router.post("/get_multi_framework_score") +def get_multi_framework_score(request: Request): + return _timed("get_multi_framework_score", {}, + lambda: scores_tool.get_multi_framework_score(store=_store(request))) + + +@router.post("/get_score_trend") +def get_score_trend(req: GetScoreTrendReq, request: Request): + return _timed("get_score_trend", req.model_dump(), + lambda: scores_tool.get_score_trend(store=_store(request), framework=req.framework, limit=req.limit)) + + +@router.post("/get_control_summary") +def get_control_summary(req: GetControlSummaryReq, request: Request): + return _timed("get_control_summary", req.model_dump(exclude_none=True), + lambda: controls_tool.get_control_summary(store=_store(request), framework=req.framework, control_id=req.control_id)) + + +@router.post("/list_scans") +def list_scans(req: LimitReq, request: Request): + return _timed("list_scans", {"limit": req.limit}, + lambda: scans_tool.list_scans(store=_store(request), limit=req.limit)) + + +@router.post("/get_latest_scan") +def get_latest_scan(request: Request): + return _timed("get_latest_scan", {}, + lambda: scans_tool.get_latest_scan(store=_store(request))) + + +@router.post("/list_risk_items") +def list_risk_items(req: ListRisksReq, request: Request): + return _timed("list_risk_items", req.model_dump(exclude_none=True), + lambda: risks_tool.list_risk_items(store=_store(request), account_id=req.account_id, status=req.status, level=req.level)) + + +@router.post("/get_risk_item") +def get_risk_item(req: GetRiskReq, request: Request): + return _timed("get_risk_item", req.model_dump(), + lambda: risks_tool.get_risk_item(store=_store(request), risk_id=req.risk_id, account_id=req.account_id)) + + +@router.post("/add_risk_item") +def add_risk_item(req: AddRiskReq, request: Request): + return _timed("add_risk_item", req.model_dump(exclude_none=True), + lambda: risks_tool.add_risk_item(store=_store(request), **req.model_dump(exclude_none=True))) + + +@router.post("/update_risk") +def update_risk(req: UpdateRiskReq, request: Request): + return _timed("update_risk", req.model_dump(exclude_none=True), + lambda: risks_tool.update_risk(store=_store(request), **req.model_dump(exclude_none=True))) +``` + +- [ ] **Step 2: Implement `src/shasta/voice/app.py`** + +```python +"""FastAPI application for the voice console.""" +import os +from pathlib import Path + +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from fastapi.staticfiles import StaticFiles + +from shasta.voice.observability import configure_logging +from shasta.voice.session import router as session_router +from shasta.voice.store import Store +from shasta.voice.tools.router import router as tools_router + + +def create_app(*, db_path: str | Path | None = None, serve_static: bool = True) -> FastAPI: + configure_logging() + app = FastAPI(title="Shasta Voice Console", version="0.1.0") + + allowed = os.environ.get("ALLOWED_ORIGINS", "http://localhost:8090").split(",") + app.add_middleware( + CORSMiddleware, + allow_origins=allowed, + allow_credentials=False, + allow_methods=["GET", "POST"], + allow_headers=["Content-Type"], + ) + + # One Store per process — reuses the same SQLite connection + app.state.store = Store(db_path=db_path) + + @app.get("/health") + def health() -> dict: + return {"status": "ok"} + + app.include_router(session_router) + app.include_router(tools_router) + + if serve_static: + dist = Path(__file__).parent / "web" / "dist" + if dist.exists(): + app.mount("/", StaticFiles(directory=str(dist), html=True), name="static") + return app + + +# Module-level app for `uvicorn shasta.voice.app:app` +app = create_app() +``` + +- [ ] **Step 3: Implement `src/shasta/voice/cli.py`** + +```python +"""`python -m shasta.voice` entrypoint.""" +import argparse +import os +import sys +import webbrowser +from pathlib import Path + +from shasta.voice.app import create_app + + +def main() -> int: + parser = argparse.ArgumentParser(prog="shasta.voice", description="Voice console for Shasta") + parser.add_argument("--port", type=int, default=8090) + parser.add_argument("--host", default="127.0.0.1") + parser.add_argument("--no-open", action="store_true", help="don't auto-launch browser") + parser.add_argument("--db", type=Path, default=None, help="path to shasta.db (default: data/shasta.db)") + args = parser.parse_args() + + api_key = os.environ.get("OPENAI_API_KEY") + if not api_key or api_key == "sk-replace-me": + print("✗ OPENAI_API_KEY not set in environment", file=sys.stderr) + print(" Add to your shell: export OPENAI_API_KEY=sk-...", file=sys.stderr) + return 2 + + db_path = args.db or Path("data/shasta.db") + if not db_path.exists(): + print(f"✗ No scan data at {db_path}", file=sys.stderr) + print(" Run a scan first: open Claude Code and use /scan", file=sys.stderr) + return 2 + + # Verify the DB has at least one scan + from shasta.voice.store import Store + s = Store(db_path=db_path) + if not s.has_data(): + print(f"✗ {db_path} exists but contains no scan data", file=sys.stderr) + print(" Run a scan first: open Claude Code and use /scan", file=sys.stderr) + s.close() + return 2 + latest = s.get_latest_scan() + s.close() + + print("✓ OPENAI_API_KEY found") + print(f"✓ {db_path} (latest scan: {latest.completed_at}, {latest.total_findings} findings)") + url = f"http://{args.host}:{args.port}" + print(f"→ Starting voice console at {url}") + + if not args.no_open: + try: + webbrowser.open(url) + except Exception: + pass + + import uvicorn + app = create_app(db_path=db_path) + uvicorn.run(app, host=args.host, port=args.port, log_level="info") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) +``` + +- [ ] **Step 4: Add `client` fixture to `tests/voice/conftest.py`** + +Append to the existing `conftest.py`: + +```python +@pytest.fixture +def client(seeded_db_path: Path): + """FastAPI TestClient bound to the seeded DB.""" + import os + os.environ.setdefault("OPENAI_API_KEY", "test-key") + os.environ.setdefault("ALLOWED_ORIGINS", "http://localhost:8090") + from fastapi.testclient import TestClient + from shasta.voice.app import create_app + app = create_app(db_path=seeded_db_path, serve_static=False) + return TestClient(app) +``` + +- [ ] **Step 5: Write `tests/voice/test_tool_endpoints.py`** + +```python +def test_health(client): + r = client.get("/health") + assert r.status_code == 200 + assert r.json() == {"status": "ok"} + + +def test_list_findings_endpoint(client): + r = client.post("/tools/list_findings", json={}) + assert r.status_code == 200 + assert isinstance(r.json(), list) + assert len(r.json()) == 10 + + +def test_list_findings_filtered(client): + r = client.post("/tools/list_findings", json={"severity": "critical", "status": "fail"}) + assert r.status_code == 200 + assert len(r.json()) == 4 + + +def test_get_finding_endpoint(client): + r = client.post("/tools/get_finding", json={"finding_id": "f-001"}) + assert r.status_code == 200 + assert r.json()["id"] == "f-001" + + +def test_get_finding_unknown_returns_200_with_error(client): + r = client.post("/tools/get_finding", json={"finding_id": "nope"}) + assert r.status_code == 200 + assert r.json() == {"error": "finding_not_found", "finding_id": "nope"} + + +def test_get_compliance_score_endpoint(client): + r = client.post("/tools/get_compliance_score", json={"framework": "soc2"}) + assert r.status_code == 200 + assert r.json()["framework"] == "soc2" + + +def test_get_multi_framework_score_endpoint(client): + r = client.post("/tools/get_multi_framework_score", json={}) + assert r.status_code == 200 + assert "frameworks" in r.json() + + +def test_add_and_get_risk_endpoint(client): + add = client.post("/tools/add_risk_item", json={ + "account_id": "123456789012", + "title": "x", "description": "y", "category": "iam", + "likelihood": "low", "impact": "low", "treatment": "accept", + }) + assert add.status_code == 200 + rid = add.json()["record_id"] + + get = client.post("/tools/get_risk_item", json={"risk_id": rid}) + assert get.status_code == 200 + assert get.json()["risk_id"] == rid + + +def test_unknown_endpoint_returns_404(client): + r = client.post("/tools/does_not_exist", json={}) + assert r.status_code == 404 + + +def test_validates_severity_enum(client): + r = client.post("/tools/list_findings", json={"severity": "extremely-critical"}) + assert r.status_code == 422 +``` + +- [ ] **Step 6: Write `tests/voice/test_cli.py`** + +```python +import os +import subprocess +import sys + + +def test_cli_help_runs(): + result = subprocess.run( + [sys.executable, "-m", "shasta.voice", "--help"], + capture_output=True, text=True, timeout=10, + ) + assert result.returncode == 0 + assert "voice console" in result.stdout.lower() + + +def test_cli_missing_api_key(monkeypatch, tmp_path): + monkeypatch.delenv("OPENAI_API_KEY", raising=False) + db = tmp_path / "shasta.db" + db.touch() + result = subprocess.run( + [sys.executable, "-m", "shasta.voice", "--db", str(db), "--no-open"], + capture_output=True, text=True, timeout=10, env={**os.environ, "OPENAI_API_KEY": ""}, + ) + assert result.returncode == 2 + assert "OPENAI_API_KEY" in result.stderr + + +def test_cli_missing_db(monkeypatch, tmp_path): + monkeypatch.setenv("OPENAI_API_KEY", "sk-test") + missing = tmp_path / "absent.db" + result = subprocess.run( + [sys.executable, "-m", "shasta.voice", "--db", str(missing), "--no-open"], + capture_output=True, text=True, timeout=10, env={**os.environ, "OPENAI_API_KEY": "sk-test"}, + ) + assert result.returncode == 2 + assert "No scan data" in result.stderr +``` + +- [ ] **Step 7: Run the full backend suite** + +```bash +cd E:/Projects/Vanta +pytest tests/voice/ -v +``` +Expected: all tests pass, including the deferred `test_session.py` from Task 9. + +- [ ] **Step 8: Commit** + +```bash +git add src/shasta/voice/tools/router.py src/shasta/voice/app.py src/shasta/voice/cli.py tests/voice/test_tool_endpoints.py tests/voice/test_cli.py tests/voice/conftest.py +git commit -m "feat(voice): tool endpoints, FastAPI app, and CLI entry point" +``` + +--- + +## Task 11: Frontend scaffold + +**Files:** +- Create: `src/shasta/voice/web/package.json` +- Create: `src/shasta/voice/web/vite.config.ts` +- Create: `src/shasta/voice/web/tsconfig.json` +- Create: `src/shasta/voice/web/tsconfig.node.json` +- Create: `src/shasta/voice/web/index.html` +- Create: `src/shasta/voice/web/src/main.tsx` +- Create: `src/shasta/voice/web/src/App.tsx` (placeholder) + +Same scaffold as VoiceApp's `web/` (Task 13 of `E:\Projects\Misc\VoiceApp\docs\superpowers\plans\2026-05-05-voiceapp.md`) with two changes: the page title is "Shasta — Voice Console" and Vite proxies hit port 8090 (not 8000). + +- [ ] **Step 1: Create `package.json`** (verbatim copy from VoiceApp's `web/package.json`) + +```json +{ + "name": "shasta-voice-web", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1", + "zustand": "^5.0.0", + "framer-motion": "^11.11.0" + }, + "devDependencies": { + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.3", + "typescript": "^5.6.3", + "vite": "^5.4.10" + } +} +``` + +- [ ] **Step 2: Create `vite.config.ts`** — proxies point at the FastAPI on port 8090 + +```typescript +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +export default defineConfig({ + plugins: [react()], + server: { + port: 5174, // dev server port (different from prod 8090 to avoid collision) + proxy: { + "/session": "http://localhost:8090", + "/tools": "http://localhost:8090", + "/health": "http://localhost:8090", + }, + }, + build: { + outDir: "dist", + emptyOutDir: true, + }, +}); +``` + +- [ ] **Step 3: Create `tsconfig.json` and `tsconfig.node.json`** — copy verbatim from VoiceApp: + +`tsconfig.json`: +```json +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} +``` + +`tsconfig.node.json`: +```json +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["vite.config.ts"] +} +``` + +- [ ] **Step 4: Create `index.html`** — title is the only change from VoiceApp's + +```html + + + + + + Shasta — Voice Console + + + + + +
+ + + +``` + +- [ ] **Step 5: Create `src/main.tsx`** — verbatim from VoiceApp + +```typescript +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App"; +import "./styles/tokens.css"; +import "./styles/global.css"; + +ReactDOM.createRoot(document.getElementById("root")!).render( + + + +); +``` + +- [ ] **Step 6: Create placeholder `src/App.tsx`** + +```typescript +export default function App() { + return ( +
+

Shasta — Voice Console

+

Scaffold OK. Components mount in later tasks.

+
+ ); +} +``` + +- [ ] **Step 7: Install and verify** + +```bash +cd src/shasta/voice/web +npm install +npm run dev +``` +Expected: Vite reports `Local: http://localhost:5174/`. Stop with Ctrl+C. + +- [ ] **Step 8: Commit** + +```bash +cd E:/Projects/Vanta +git add src/shasta/voice/web/package.json src/shasta/voice/web/vite.config.ts src/shasta/voice/web/tsconfig*.json src/shasta/voice/web/index.html src/shasta/voice/web/src/main.tsx src/shasta/voice/web/src/App.tsx +git commit -m "feat(voice): React + Vite scaffold for voice console" +``` + +`node_modules/` and `dist/` are gitignored at the Shasta repo level — verify with `git status` that they're not staged. + +--- + +## Task 12: Brand tokens and global styles + +**Files:** +- Create: `src/shasta/voice/web/src/styles/tokens.css` +- Create: `src/shasta/voice/web/src/styles/global.css` + +**Copy verbatim from VoiceApp:** `E:\Projects\Misc\VoiceApp\web\src\styles\tokens.css` and `global.css`. The Transilience dark theme + brand gradient + severity-to-gradient mapping all carry over unchanged. No modifications. + +- [ ] **Step 1: Copy both files** + +```bash +copy "E:\Projects\Misc\VoiceApp\web\src\styles\tokens.css" "E:\Projects\Vanta\src\shasta\voice\web\src\styles\tokens.css" +copy "E:\Projects\Misc\VoiceApp\web\src\styles\global.css" "E:\Projects\Vanta\src\shasta\voice\web\src\styles\global.css" +``` + +(PowerShell: `Copy-Item`. Bash via the tool: `cp`.) + +- [ ] **Step 2: Verify in browser** + +```bash +cd src/shasta/voice/web +npm run dev +``` +Expected: page shows the placeholder text on Rich Black background in Roboto. Stop server. + +- [ ] **Step 3: Commit** + +```bash +cd E:/Projects/Vanta +git add src/shasta/voice/web/src/styles/tokens.css src/shasta/voice/web/src/styles/global.css +git commit -m "feat(voice): brand tokens and global styles (dark Transilience theme)" +``` + +--- + +## Task 13: TS types + Zustand store + +**Files:** +- Create: `src/shasta/voice/web/src/tools/types.ts` +- Create: `src/shasta/voice/web/src/state/session.ts` + +Types are Shasta-specific (not copied from VoiceApp). Store is structurally identical to VoiceApp's but the `ActiveCard` union changes. + +- [ ] **Step 1: Create `tools/types.ts`** + +```typescript +// Mirrors src/shasta/voice/models.py — keep field names in sync. + +export type Severity = "critical" | "high" | "medium" | "low" | "info"; +export type Status = "pass" | "fail" | "partial" | "not_assessed" | "not_applicable"; +export type Cloud = "aws" | "azure"; +export type Framework = "soc2" | "iso27001" | "hipaa" | "iso42001" | "eu_ai_act" | "ai_governance"; + +export interface FindingSummary { + id: string; + check_id: string; + title: string; + severity: Severity; + status: Status; + domain: string; + resource_id: string; + cloud_provider: Cloud; + soc2_controls: string[]; + iso27001_controls: string[]; + hipaa_controls: string[]; +} + +export interface FindingDetailView extends FindingSummary { + description: string; + remediation: string; + region: string; + account_id: string; + details: Record; + timestamp: string; +} + +export interface ComplianceScoreView { + framework: Framework; + score_percentage: number; + grade: string; + total_controls: number; + passing: number; + failing: number; + partial: number; + not_assessed: number; + total_findings: number; + findings_failed: number; +} + +export interface MultiFrameworkScoreView { + frameworks: ComplianceScoreView[]; + not_enabled: Framework[]; +} + +export interface ScoreTrendView { + framework: Framework; + points: Array<{ scan_id: string; completed_at: string | null; score_percentage: number }>; + delta: number; +} + +export interface ControlSummaryView { + framework: Framework; + control_id: string; + title: string; + overall_status: string; + pass_count: number; + fail_count: number; + partial_count: number; + finding_ids: string[]; +} + +export interface RiskItemView { + risk_id: string; + title: string; + description: string; + category: string; + likelihood: string; + impact: string; + risk_score: number; + risk_level: string; + treatment: string; + treatment_plan: string | null; + status: string; + soc2_controls: string[]; + related_finding: string | null; +} + +export interface ScanSummaryView { + scan_id: string; + account_id: string; + cloud_provider: Cloud; + completed_at: string | null; + total_findings: number; + critical_count: number; + high_count: number; + medium_count: number; + low_count: number; + passed: number; + failed: number; +} + +export interface ActionResult { + success: boolean; + message: string; + record_id: string | null; +} + +export type ActiveCard = + | { kind: "none" } + | { kind: "findings_list"; data: FindingSummary[] } + | { kind: "finding_detail"; data: FindingDetailView } + | { kind: "compliance_score"; data: ComplianceScoreView } + | { kind: "multi_framework"; data: MultiFrameworkScoreView } + | { kind: "control_summary"; data: ControlSummaryView[] } + | { kind: "risk_list"; data: RiskItemView[] } + | { kind: "risk_detail"; data: RiskItemView } + | { kind: "action"; data: ActionResult }; + +export type ConnectionState = + | "idle" | "connecting" | "connected" + | "listening" | "thinking" | "speaking" + | "error"; + +export interface TranscriptLine { + id: string; + who: "user" | "assistant"; + text: string; + timestamp: number; + partial?: boolean; +} +``` + +- [ ] **Step 2: Create `state/session.ts`** — copy verbatim from `E:\Projects\Misc\VoiceApp\web\src\state\session.ts`. The store shape doesn't change — `ActiveCard` is imported from types so the new union is picked up automatically. + +- [ ] **Step 3: Verify TypeScript compiles** + +```bash +cd src/shasta/voice/web +npx tsc --noEmit +``` +Expected: no errors. + +- [ ] **Step 4: Commit** + +```bash +cd E:/Projects/Vanta +git add src/shasta/voice/web/src/tools/types.ts src/shasta/voice/web/src/state/session.ts +git commit -m "feat(voice): TypeScript types mirroring backend models + Zustand store" +``` + +--- + +## Task 14: Voice plumbing — connection, events, relay + +**Files:** +- Create: `src/shasta/voice/web/src/voice/connection.ts` +- Create: `src/shasta/voice/web/src/voice/events.ts` +- Create: `src/shasta/voice/web/src/tools/relay.ts` + +`connection.ts` and `events.ts` are **verbatim copies from VoiceApp**, including the audio-element-to-DOM fix (so we don't repeat the bug from VoiceApp Task 28→29 cycle). `relay.ts` is structurally identical but with the Shasta tool list. + +- [ ] **Step 1: Copy `connection.ts` from VoiceApp** — but use the FIXED version (with `document.body.appendChild(audioElement)` and `audioElement.remove()` in close) + +Source: `E:\Projects\Misc\VoiceApp\web\src\voice\connection.ts` (already includes Task 29 fix). + +```bash +copy "E:\Projects\Misc\VoiceApp\web\src\voice\connection.ts" "E:\Projects\Vanta\src\shasta\voice\web\src\voice\connection.ts" +``` + +- [ ] **Step 2: Copy `events.ts` from VoiceApp verbatim** + +```bash +copy "E:\Projects\Misc\VoiceApp\web\src\voice\events.ts" "E:\Projects\Vanta\src\shasta\voice\web\src\voice\events.ts" +``` + +- [ ] **Step 3: Create `tools/relay.ts`** — same shape as VoiceApp's, with the Shasta tool list + +```typescript +const KNOWN_TOOLS = new Set([ + "list_findings", + "get_finding", + "list_top_blockers", + "get_resource_findings", + "get_compliance_score", + "get_multi_framework_score", + "get_score_trend", + "get_control_summary", + "list_scans", + "get_latest_scan", + "list_risk_items", + "get_risk_item", + "add_risk_item", + "update_risk", +]); + +export interface ToolCallResult { + output: string; + parsed: unknown; + toolName: string; + latencyMs: number; +} + +export async function executeToolCall(toolName: string, argsJson: string): Promise { + const start = performance.now(); + if (!KNOWN_TOOLS.has(toolName)) { + const errorPayload = { error: "unknown_tool", tool: toolName }; + return { output: JSON.stringify(errorPayload), parsed: errorPayload, toolName, latencyMs: 0 }; + } + let args: unknown; + try { + args = argsJson ? JSON.parse(argsJson) : {}; + } catch { + const errorPayload = { error: "invalid_arguments_json", raw: argsJson }; + return { output: JSON.stringify(errorPayload), parsed: errorPayload, toolName, latencyMs: 0 }; + } + try { + const resp = await fetch(`/tools/${toolName}`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(args), + }); + if (!resp.ok) { + const errorPayload = { + error: "tool_unavailable", + status: resp.status, + detail: (await resp.text()).slice(0, 200), + }; + return { output: JSON.stringify(errorPayload), parsed: errorPayload, toolName, latencyMs: performance.now() - start }; + } + const parsed = await resp.json(); + return { output: JSON.stringify(parsed), parsed, toolName, latencyMs: performance.now() - start }; + } catch (err) { + const errorPayload = { error: "tool_unavailable", detail: err instanceof Error ? err.message : String(err) }; + return { output: JSON.stringify(errorPayload), parsed: errorPayload, toolName, latencyMs: performance.now() - start }; + } +} +``` + +- [ ] **Step 4: Verify TypeScript compiles** + +```bash +cd src/shasta/voice/web +npx tsc --noEmit +``` + +- [ ] **Step 5: Commit** + +```bash +cd E:/Projects/Vanta +git add src/shasta/voice/web/src/voice/connection.ts src/shasta/voice/web/src/voice/events.ts src/shasta/voice/web/src/tools/relay.ts +git commit -m "feat(voice): WebRTC connection + event parser + tool relay" +``` + +--- + +## Task 15: Reused UI chrome — Header, MicChrome, Transcript, SeverityBadge, ActionToast + +**Files:** +- Create: `src/shasta/voice/web/src/components/Header.tsx` +- Create: `src/shasta/voice/web/src/components/MicChrome.tsx` +- Create: `src/shasta/voice/web/src/components/Transcript.tsx` +- Create: `src/shasta/voice/web/src/components/cards/SeverityBadge.tsx` +- Create: `src/shasta/voice/web/src/components/cards/ActionToast.tsx` + +`Header.tsx` is the only one that needs editing — text-only (no logo) per design. Others are verbatim copies from VoiceApp. + +- [ ] **Step 1: Create `Header.tsx`** — text-only, no `` tag + +```typescript +import { useSession } from "../state/session"; + +export function Header() { + const connection = useSession((s) => s.connection); + + const indicatorColor = + connection === "connected" || connection === "listening" || + connection === "thinking" || connection === "speaking" + ? "var(--brand-yellow)" + : connection === "error" + ? "var(--severity-critical)" + : "var(--text-subtle)"; + + return ( +
+
+ + Shasta + + + Voice Console + +
+
+
+ + {connection} + +
+
+ ); +} +``` + +- [ ] **Step 2: Copy MicChrome, Transcript, SeverityBadge, ActionToast from VoiceApp verbatim** + +```bash +copy "E:\Projects\Misc\VoiceApp\web\src\components\MicChrome.tsx" "E:\Projects\Vanta\src\shasta\voice\web\src\components\MicChrome.tsx" +copy "E:\Projects\Misc\VoiceApp\web\src\components\Transcript.tsx" "E:\Projects\Vanta\src\shasta\voice\web\src\components\Transcript.tsx" +copy "E:\Projects\Misc\VoiceApp\web\src\components\cards\SeverityBadge.tsx" "E:\Projects\Vanta\src\shasta\voice\web\src\components\cards\SeverityBadge.tsx" +copy "E:\Projects\Misc\VoiceApp\web\src\components\cards\ActionToast.tsx" "E:\Projects\Vanta\src\shasta\voice\web\src\components\cards\ActionToast.tsx" +``` + +- [ ] **Step 3: Verify TypeScript compiles** + +```bash +cd src/shasta/voice/web +npx tsc --noEmit +``` + +- [ ] **Step 4: Commit** + +```bash +cd E:/Projects/Vanta +git add src/shasta/voice/web/src/components/Header.tsx src/shasta/voice/web/src/components/MicChrome.tsx src/shasta/voice/web/src/components/Transcript.tsx src/shasta/voice/web/src/components/cards/SeverityBadge.tsx src/shasta/voice/web/src/components/cards/ActionToast.tsx +git commit -m "feat(voice): reused UI chrome — Header (text-only), MicChrome, Transcript, badges" +``` + +--- + +## Task 16: New cards — FindingsList, FindingDetail + +**Files:** +- Create: `src/shasta/voice/web/src/components/cards/FindingsList.tsx` +- Create: `src/shasta/voice/web/src/components/cards/FindingDetail.tsx` + +- [ ] **Step 1: Create `FindingsList.tsx`** + +```typescript +import { motion } from "framer-motion"; +import type { FindingSummary } from "../../tools/types"; +import { SeverityBadge } from "./SeverityBadge"; + +function FrameworkChips({ f }: { f: FindingSummary }) { + const chips: Array<{ label: string; color: string }> = []; + for (const c of f.soc2_controls.slice(0, 2)) chips.push({ label: `SOC 2 · ${c}`, color: "var(--brand-purple)" }); + for (const c of f.iso27001_controls.slice(0, 1)) chips.push({ label: `ISO · ${c}`, color: "var(--severity-medium)" }); + for (const c of f.hipaa_controls.slice(0, 1)) chips.push({ label: `HIPAA · ${c}`, color: "var(--severity-high)" }); + return ( +
+ {chips.map((c) => ( + {c.label} + ))} +
+ ); +} + +export function FindingsList({ findings }: { findings: FindingSummary[] }) { + return ( + +

+ {findings.length} finding{findings.length === 1 ? "" : "s"} +

+
+ {findings.map((f) => ( +
+ +
+
{f.title}
+
+ + {f.cloud_provider.toUpperCase()} · {f.domain} + + +
+
+
{f.status}
+
+ ))} +
+
+ ); +} +``` + +- [ ] **Step 2: Create `FindingDetail.tsx`** + +```typescript +import { motion } from "framer-motion"; +import type { FindingDetailView } from "../../tools/types"; +import { SeverityBadge } from "./SeverityBadge"; + +export function FindingDetail({ finding }: { finding: FindingDetailView }) { + return ( + +
+ + + {finding.id} · {finding.cloud_provider.toUpperCase()} · {finding.domain} + +
+

{finding.title}

+

{finding.description}

+ +
+ {finding.soc2_controls.length > 0 && ( +
+
+ SOC 2 +
+
{finding.soc2_controls.join(", ")}
+
+ )} + {finding.iso27001_controls.length > 0 && ( +
+
+ ISO 27001 +
+
{finding.iso27001_controls.join(", ")}
+
+ )} + {finding.hipaa_controls.length > 0 && ( +
+
+ HIPAA +
+
{finding.hipaa_controls.join(", ")}
+
+ )} +
+ +
+ Resource:{" "} + {finding.resource_id} +
+ {finding.remediation && ( +
+ Fix: {finding.remediation} +
+ )} +
+ ); +} +``` + +- [ ] **Step 3: Verify and commit** + +```bash +cd src/shasta/voice/web && npx tsc --noEmit && cd E:/Projects/Vanta +git add src/shasta/voice/web/src/components/cards/FindingsList.tsx src/shasta/voice/web/src/components/cards/FindingDetail.tsx +git commit -m "feat(voice): FindingsList and FindingDetail cards with framework chips" +``` + +--- + +## Task 17: New cards — ComplianceScore, MultiFrameworkScore + +**Files:** +- Create: `src/shasta/voice/web/src/components/cards/ComplianceScore.tsx` +- Create: `src/shasta/voice/web/src/components/cards/MultiFrameworkScore.tsx` + +- [ ] **Step 1: Create `ComplianceScore.tsx`** + +```typescript +import { motion } from "framer-motion"; +import type { ComplianceScoreView } from "../../tools/types"; + +const FRAMEWORK_LABELS: Record = { + soc2: "SOC 2", + iso27001: "ISO 27001", + hipaa: "HIPAA", + iso42001: "ISO 42001", + eu_ai_act: "EU AI Act", + ai_governance: "AI Governance", +}; + +export function ComplianceScore({ score }: { score: ComplianceScoreView }) { + const gradeColor = score.grade === "A" ? "var(--brand-yellow)" + : score.grade === "B" ? "var(--severity-low)" + : score.grade === "C" ? "var(--severity-medium)" + : "var(--severity-critical)"; + return ( + +

+ {FRAMEWORK_LABELS[score.framework] ?? score.framework} +

+
+ + {score.score_percentage.toFixed(0)}% + + {score.grade} +
+
+
Passing
{score.passing}
+
Failing
{score.failing}
+
Partial
{score.partial}
+
Findings
{score.total_findings}
+
+
+ ); +} +``` + +- [ ] **Step 2: Create `MultiFrameworkScore.tsx`** + +```typescript +import { motion } from "framer-motion"; +import type { ComplianceScoreView, MultiFrameworkScoreView } from "../../tools/types"; + +const LABELS: Record = { + soc2: "SOC 2", iso27001: "ISO 27001", hipaa: "HIPAA", + iso42001: "ISO 42001", eu_ai_act: "EU AI Act", ai_governance: "AI Gov", +}; + +function ScoreColumn({ s }: { s: ComplianceScoreView }) { + const fillPct = Math.max(0, Math.min(100, s.score_percentage)); + return ( +
+
+
+ {LABELS[s.framework] ?? s.framework} +
+
+ {s.score_percentage.toFixed(0)}% +
+
+ Grade {s.grade} +
+
+ ); +} + +function NotEnabledColumn({ framework }: { framework: string }) { + return ( +
+
+ {LABELS[framework] ?? framework} +
+
+ not enabled +
+
+ ); +} + +export function MultiFrameworkScore({ data }: { data: MultiFrameworkScoreView }) { + const ALL: string[] = ["soc2", "iso27001", "hipaa", "iso42001", "eu_ai_act"]; + const enabled = new Map(data.frameworks.map((s) => [s.framework, s])); + return ( + +

Compliance posture

+
+ {ALL.map((fw) => { + const score = enabled.get(fw as ComplianceScoreView["framework"]); + return score ? : ; + })} +
+
+ ); +} +``` + +- [ ] **Step 3: Verify + commit** + +```bash +cd src/shasta/voice/web && npx tsc --noEmit && cd E:/Projects/Vanta +git add src/shasta/voice/web/src/components/cards/ComplianceScore.tsx src/shasta/voice/web/src/components/cards/MultiFrameworkScore.tsx +git commit -m "feat(voice): ComplianceScore and MultiFrameworkScore cards" +``` + +--- + +## Task 18: New cards — ControlSummary, RiskList, RiskDetail + +**Files:** +- Create: `src/shasta/voice/web/src/components/cards/ControlSummary.tsx` +- Create: `src/shasta/voice/web/src/components/cards/RiskList.tsx` +- Create: `src/shasta/voice/web/src/components/cards/RiskDetail.tsx` + +- [ ] **Step 1: Create `ControlSummary.tsx`** + +```typescript +import { motion } from "framer-motion"; +import type { ControlSummaryView } from "../../tools/types"; + +const STATUS_COLOR: Record = { + pass: "var(--brand-yellow)", fail: "var(--severity-critical)", + partial: "var(--severity-medium)", not_assessed: "var(--text-subtle)", + requires_policy: "var(--severity-low)", +}; + +export function ControlSummary({ controls }: { controls: ControlSummaryView[] }) { + return ( + +

+ {controls.length} control{controls.length === 1 ? "" : "s"} +

+
+ {controls.map((c) => ( +
+ {c.control_id} + {c.title} + {c.pass_count} pass + {c.fail_count} fail + + {c.overall_status} + +
+ ))} +
+
+ ); +} +``` + +- [ ] **Step 2: Create `RiskList.tsx`** + +```typescript +import { motion } from "framer-motion"; +import type { RiskItemView } from "../../tools/types"; + +const LEVEL_COLOR: Record = { + high: "var(--severity-critical)", medium: "var(--severity-medium)", low: "var(--severity-low)", +}; + +export function RiskList({ risks }: { risks: RiskItemView[] }) { + return ( + +

+ {risks.length} risk{risks.length === 1 ? "" : "s"} +

+
+ {risks.map((r) => ( +
+ {r.risk_score} + {r.title} + {r.treatment} + {r.status} +
+ ))} +
+
+ ); +} +``` + +- [ ] **Step 3: Create `RiskDetail.tsx`** + +```typescript +import { motion } from "framer-motion"; +import type { RiskItemView } from "../../tools/types"; + +export function RiskDetail({ risk }: { risk: RiskItemView }) { + return ( + +
+ {risk.risk_id} + + {risk.risk_level.toUpperCase()} · score {risk.risk_score} · {risk.status} + +
+

{risk.title}

+

{risk.description}

+
+
Likelihood
{risk.likelihood}
+
Impact
{risk.impact}
+
Treatment
{risk.treatment}
+
+ {risk.treatment_plan && ( +
+ Plan: {risk.treatment_plan} +
+ )} +
+ ); +} +``` + +- [ ] **Step 4: Verify + commit** + +```bash +cd src/shasta/voice/web && npx tsc --noEmit && cd E:/Projects/Vanta +git add src/shasta/voice/web/src/components/cards/ControlSummary.tsx src/shasta/voice/web/src/components/cards/RiskList.tsx src/shasta/voice/web/src/components/cards/RiskDetail.tsx +git commit -m "feat(voice): ControlSummary, RiskList, RiskDetail cards" +``` + +--- + +## Task 19: CardSlot + cardDispatcher + App.tsx wiring + +**Files:** +- Create: `src/shasta/voice/web/src/components/CardSlot.tsx` +- Create: `src/shasta/voice/web/src/voice/cardDispatcher.ts` +- Replace: `src/shasta/voice/web/src/App.tsx` + +This is the integration step — after this the frontend is end-to-end wired. + +- [ ] **Step 1: Create `CardSlot.tsx`** + +```typescript +import { AnimatePresence } from "framer-motion"; +import { useSession } from "../state/session"; +import { ActionToast } from "./cards/ActionToast"; +import { ComplianceScore } from "./cards/ComplianceScore"; +import { ControlSummary } from "./cards/ControlSummary"; +import { FindingDetail } from "./cards/FindingDetail"; +import { FindingsList } from "./cards/FindingsList"; +import { MultiFrameworkScore } from "./cards/MultiFrameworkScore"; +import { RiskDetail } from "./cards/RiskDetail"; +import { RiskList } from "./cards/RiskList"; + +export function CardSlot() { + const card = useSession((s) => s.activeCard); + return ( +
+
+ + {card.kind === "findings_list" && } + {card.kind === "finding_detail" && } + {card.kind === "compliance_score" && } + {card.kind === "multi_framework" && } + {card.kind === "control_summary" && } + {card.kind === "risk_list" && } + {card.kind === "risk_detail" && } + {card.kind === "action" && } + {card.kind === "none" && ( +
+ Ask about findings, scores, controls, or risks to populate this panel. +
+ )} +
+
+
+ ); +} +``` + +(`ActionToast` from VoiceApp expects an `ActionResult` with `success`, `message`, `new_status`, `timestamp`. Our Shasta `ActionResult` doesn't have `new_status` or `timestamp` — adjust by creating a small shim or skipping `new_status`/`timestamp` fields in the toast. Easiest fix: in the ActionToast we copied from VoiceApp, change the references to those fields to use `record_id` and the current time. Make that one-line edit if TypeScript complains.) + +- [ ] **Step 2: Create `voice/cardDispatcher.ts`** + +```typescript +import type { + ActionResult, ActiveCard, ComplianceScoreView, ControlSummaryView, + FindingDetailView, FindingSummary, MultiFrameworkScoreView, RiskItemView, +} from "../tools/types"; + +export function dispatchCard(toolName: string, parsed: unknown): ActiveCard | null { + if (parsed && typeof parsed === "object" && "error" in parsed) return null; + + switch (toolName) { + case "list_findings": + case "list_top_blockers": + case "get_resource_findings": + if (Array.isArray(parsed)) return { kind: "findings_list", data: parsed as FindingSummary[] }; + return null; + case "get_finding": + if (parsed && typeof parsed === "object" && "id" in parsed) return { kind: "finding_detail", data: parsed as FindingDetailView }; + return null; + case "get_compliance_score": + if (parsed && typeof parsed === "object" && "framework" in parsed) return { kind: "compliance_score", data: parsed as ComplianceScoreView }; + return null; + case "get_multi_framework_score": + if (parsed && typeof parsed === "object" && "frameworks" in parsed) return { kind: "multi_framework", data: parsed as MultiFrameworkScoreView }; + return null; + case "get_control_summary": + if (Array.isArray(parsed)) return { kind: "control_summary", data: parsed as ControlSummaryView[] }; + return null; + case "list_risk_items": + if (Array.isArray(parsed)) return { kind: "risk_list", data: parsed as RiskItemView[] }; + return null; + case "get_risk_item": + if (parsed && typeof parsed === "object" && "risk_id" in parsed) return { kind: "risk_detail", data: parsed as RiskItemView }; + return null; + case "add_risk_item": + case "update_risk": + if (parsed && typeof parsed === "object" && "success" in parsed) return { kind: "action", data: parsed as ActionResult }; + return null; + // Tools without cards: get_score_trend, list_scans, get_latest_scan + default: + return null; + } +} +``` + +- [ ] **Step 3: Replace `App.tsx`** — copy from VoiceApp's `App.tsx` and adjust imports to point at the Shasta dispatcher and components + +The structure is identical to `E:\Projects\Misc\VoiceApp\web\src\App.tsx` (the post-Task-28 fully-wired version). Copy that file and: +1. Change `import { dispatchCard } from "./voice/cardDispatcher"` — same path, new content (already done in step 2 above). +2. Change the imports of `Header`, `MicChrome`, `Transcript`, `CardSlot` — same paths, already exist in our project. +3. Nothing else changes — `App.tsx` doesn't reference any specific card component directly; everything goes through `useSession` + `setActiveCard`. + +```bash +copy "E:\Projects\Misc\VoiceApp\web\src\App.tsx" "E:\Projects\Vanta\src\shasta\voice\web\src\App.tsx" +``` + +- [ ] **Step 4: Verify the full frontend compiles** + +```bash +cd src/shasta/voice/web +npx tsc --noEmit +``` + +If errors mention `ActionToast` referencing `new_status` or `timestamp` (because Shasta's `ActionResult` doesn't have those), open `src/components/cards/ActionToast.tsx` and replace `action.new_status` with `action.record_id ?? "ok"` and replace `action.timestamp` with `new Date().toLocaleTimeString()`. One-line fix per reference. + +- [ ] **Step 5: Commit** + +```bash +cd E:/Projects/Vanta +git add src/shasta/voice/web/src/components/CardSlot.tsx src/shasta/voice/web/src/voice/cardDispatcher.ts src/shasta/voice/web/src/App.tsx src/shasta/voice/web/src/components/cards/ActionToast.tsx +git commit -m "feat(voice): wire end-to-end — App, CardSlot, cardDispatcher, ActionToast tweaks" +``` + +--- + +## Task 20: Build the React bundle and commit `dist/` + +**Files:** +- Create: `src/shasta/voice/web/dist/**` (build output) + +This is the only place we deliberately commit a build artifact. It's required so `pip install shasta[voice]` works without Node. + +- [ ] **Step 1: Build the production bundle** + +```bash +cd src/shasta/voice/web +npm run build +``` +Expected: `dist/` directory created with `index.html` and `assets/`. No TypeScript errors. Bundle ~250-300 KB (~85-100 KB gzipped). + +- [ ] **Step 2: Verify the FastAPI server can serve it end-to-end** + +```bash +cd E:/Projects/Vanta +# Make sure the seeded test DB is in place; otherwise create a tiny one or point at a real scan +# For verification, set OPENAI_API_KEY to a stub so the CLI accepts it +$env:OPENAI_API_KEY = "sk-test-stub" # PowerShell +# or: export OPENAI_API_KEY=sk-test-stub (Bash) +python -m shasta.voice --no-open --port 8090 --db data/shasta.db +``` +If `data/shasta.db` doesn't exist on this machine, the CLI will refuse — that's expected and fine. The build verification is just `npm run build` succeeding. + +- [ ] **Step 3: Commit the bundle** + +```bash +git add -f src/shasta/voice/web/dist/ +git commit -m "build(voice): pre-built React bundle for zero-Node distribution" +``` + +The `-f` is needed if `dist/` is in `.gitignore` (likely). If you'd rather not pollute the global `.gitignore`, add a `src/shasta/voice/web/.gitignore` that explicitly negates `!dist/`. + +--- + +## Task 21: CI workflow + README updates + +**Files:** +- Create: `.github/workflows/voice-bundle.yml` +- Modify: `README.md` + +- [ ] **Step 1: Create the bundle-check CI workflow** + +`.github/workflows/voice-bundle.yml`: +```yaml +name: voice-bundle + +on: + pull_request: + paths: + - 'src/shasta/voice/web/src/**' + - 'src/shasta/voice/web/package.json' + - 'src/shasta/voice/web/package-lock.json' + - 'src/shasta/voice/web/vite.config.ts' + +jobs: + rebuild-and-diff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: 'src/shasta/voice/web/package-lock.json' + - name: Install + working-directory: src/shasta/voice/web + run: npm ci + - name: Build + working-directory: src/shasta/voice/web + run: npm run build + - name: Verify committed bundle is up to date + run: | + if ! git diff --quiet src/shasta/voice/web/dist/; then + echo "::error::The committed React bundle is out of date. Run 'npm run build' in src/shasta/voice/web/ and commit the result." + git status src/shasta/voice/web/dist/ + git diff src/shasta/voice/web/dist/ | head -100 + exit 1 + fi +``` + +- [ ] **Step 2: Add a Voice Console section to the root README** + +Find the Quick Start section (or a sensible location). Add this block: + +```markdown +### Voice Console (optional) + +Talk to your compliance posture instead of clicking through dashboards. + +```bash +pip install shasta[voice] # adds FastAPI + uvicorn + httpx +export OPENAI_API_KEY=sk-... # required for OpenAI Realtime API +python -m shasta.voice # opens browser at http://localhost:8090 +``` + +Requires a recent scan in `data/shasta.db` (run `/scan` in Claude Code first). The voice assistant has read access to all your findings, compliance scores (SOC 2, ISO 27001, HIPAA, ISO 42001, EU AI Act), and risk register, plus light writes for adding/updating risk-register items. Heavy operations (scans, reports, Terraform generation) remain in the Claude Code skills — voice will redirect you to them. +``` + +- [ ] **Step 3: Commit** + +```bash +git add .github/workflows/voice-bundle.yml README.md +git commit -m "docs(voice): CI workflow + README section for voice console" +``` + +--- + +## Task 22: Final verification + PR + +- [ ] **Step 1: Run the full test suite** + +```bash +cd E:/Projects/Vanta +pytest tests/voice/ -v --cov=src/shasta/voice --cov-report=term-missing +``` +Expected: all tests pass. Coverage target ≥80% on `src/shasta/voice/store.py` and `src/shasta/voice/tools/`. + +- [ ] **Step 2: Run Shasta's existing test suite to confirm no regressions** + +```bash +pytest tests/ -v --ignore=tests/voice +``` +Expected: same pass/fail ratio as `main`. The voice module is purely additive — should not affect any existing test. + +- [ ] **Step 3: Lint check** + +```bash +ruff check src/shasta/voice/ tests/voice/ +``` +Fix any reported issues — they'll mostly be unused imports or import sorting. + +- [ ] **Step 4: Manual end-to-end (USER required)** + +The following requires a real scan database and a real `OPENAI_API_KEY`. The implementer should hand this off to the user: + +1. Run `/scan` in Claude Code against a real (or test) AWS/Azure environment to populate `data/shasta.db`. +2. Set `OPENAI_API_KEY` in the environment. +3. Run `python -m shasta.voice`. +4. In the browser at `http://localhost:8090`: tap mic, allow permission, ask the following and verify the corresponding cards mount: + - *"What's my SOC 2 score?"* → ComplianceScore card + - *"Show me my critical findings."* → FindingsList card + - *"Tell me about that one."* (after assistant names a finding) → FindingDetail card + - *"How am I doing across the board?"* → MultiFrameworkScore card + - *"What's CC6.1 looking like?"* → ControlSummary card + - *"Show me my open risks."* → RiskList card (likely empty initially — that's fine) + - *"Add a risk for the IAM thing."* → ActionToast confirming the new risk item +5. Verify barge-in: interrupt the assistant mid-response, confirm it stops within ~200ms. +6. Verify a redirect: ask *"Run a scan."* — assistant should suggest `/scan` rather than attempting it. + +If anything fails, file an issue with the precise failure mode (which tool returned what, what card mounted instead of expected, what the browser console shows). Don't try to fix during the manual pass — capture and report. + +- [ ] **Step 5: Open a PR** + +```bash +git push -u origin feat/voice-console +gh pr create --title "feat: voice-driven compliance console (opt-in)" --body "$(cat <<'EOF' +## Summary +Adds an optional voice-driven dashboard at `python -m shasta.voice`. Reads real scan data, talks to compliance posture across SOC 2 / ISO 27001 / HIPAA / ISO 42001 / EU AI Act, supports light writes to the risk register. + +- New sibling module `src/shasta/voice/` (FastAPI sub-app, OpenAI Realtime, React frontend) +- Pre-built React bundle ships in the wheel — no Node required at runtime +- Read-only over `ShastaDB` + risk-register writes via existing `save_risk_items` +- `pip install shasta[voice]` extra; default install unchanged + +Spec: `docs/superpowers/specs/2026-05-05-shastavoice-design.md` +Plan: `docs/superpowers/plans/2026-05-05-shastavoice.md` + +## Test plan +- [x] Voice tool layer unit tests passing (≥80% coverage on `src/shasta/voice/`) +- [x] Tool endpoint integration tests passing +- [x] Existing Shasta test suite still passing (no regressions) +- [ ] Manual rehearsal against real scan data + real OpenAI key (handed off to user) + +🤖 Generated with [Claude Code](https://claude.com/claude-code) +EOF +)" +``` + +--- + +## Self-Review Checklist (run after writing the plan) + +✅ **Spec coverage:** +- Sibling module `src/shasta/voice/` with all listed Python files: Tasks 0, 2, 4, 5, 6, 7, 8, 9, 10 +- All 14 tools: Tasks 6, 7 +- Distiller system prompt with redirects: Task 8 +- Voice-driven dashboard with all listed card types: Tasks 15, 16, 17, 18 +- Brand integration (dark Transilience theme, Roboto): Task 12 +- Text-only header (no logo): Task 15 step 1 +- Real ShastaDB integration (no mock layer): Task 4 +- Light writes (add/update risk): Task 5, Task 7 +- Pre-built React bundle in wheel: Tasks 11, 20, 21 +- CLI invocation `python -m shasta.voice`: Tasks 0, 10 +- Empty-state and redirect handling: Task 8 (system prompt), Task 10 (CLI), Task 7 (tool errors) +- Test fixture seeded SQLite: Task 3 +- pyproject `[voice]` extra + package-data: Task 1 + +No spec gaps. + +✅ **Placeholder scan:** No "TBD", "TODO", "implement later", or "similar to Task N" without code. Reused VoiceApp files are referenced by exact path with explicit copy commands. The only intentional cross-file dependency calls out the precise edit (ActionToast field rename) when the type interface differs. + +✅ **Type consistency:** All Python field names (`scan_id`, `account_id`, `score_percentage`, `risk_id`, etc.) match between `models.py` (Task 2), `store.py` (Tasks 4-5), tool functions (Tasks 6-7), and TypeScript `types.ts` (Task 13). Tool names match across `realtime_config.py` (Task 8), `tools/router.py` (Task 10), and `tools/relay.ts` (Task 14): list_findings, get_finding, list_top_blockers, get_resource_findings, get_compliance_score, get_multi_framework_score, get_score_trend, get_control_summary, list_scans, get_latest_scan, list_risk_items, get_risk_item, add_risk_item, update_risk. + +✅ **Cross-repo references:** All `copy` commands reference the existing VoiceApp files by their actual paths on disk (`E:\Projects\Misc\VoiceApp\...`). The implementer can verify these exist at runtime before copying. + + diff --git a/docs/superpowers/specs/2026-05-05-shastavoice-design.md b/docs/superpowers/specs/2026-05-05-shastavoice-design.md new file mode 100644 index 0000000..b7bb420 --- /dev/null +++ b/docs/superpowers/specs/2026-05-05-shastavoice-design.md @@ -0,0 +1,386 @@ +# ShastaVoice — Voice-Driven Compliance Console + +**Status:** Approved design, pre-implementation +**Date:** 2026-05-05 +**Owner:** kkmookhey +**Repo:** kkmookhey/shasta (sibling module, not a separate repo) + +## Purpose + +Add a voice-driven dynamic dashboard to Shasta as an opt-in capability. Users run `python -m shasta.voice` after a scan and have a conversational, hands-free interface to their compliance posture — query findings across SOC 2 / ISO 27001 / HIPAA / ISO 42001 / EU AI Act, drill into specific controls and resources, and perform light writes (manage the risk register). + +This is positioned as an **open-source credibility / UX showcase** for Shasta — proof that AI-native compliance can include a voice modality, not just a CLI and an HTML dashboard. It reuses ~70% of the [VoiceApp prototype](../../../../../VoiceApp/docs/superpowers/specs/2026-05-05-voiceapp-design.md)'s architecture, adapted to Shasta's real data layer. + +## Non-goals + +- Demo / seeded fake data. Voice runs on the user's actual `data/shasta.db`. No `--demo` mode. +- Long-running operations over voice — `/scan`, `/report`, `/remediate`, `/policy-gen` stay in slash-commands. The voice assistant points users to those when asked. +- Production multi-user auth. Localhost-only, matches Shasta's existing dashboard. +- Replacing the existing HTML dashboard. Voice runs alongside it on a separate port. +- Mobile / responsive layout. Desktop-first. + +## Locked decisions (from brainstorming) + +| Decision | Choice | Why | +|---|---|---| +| Where the code lives | Sibling module `src/shasta/voice/` inside the Shasta repo | First-class Shasta feature without coupling to the existing HTML dashboard | +| Data source | Real `ShastaDB` (no mocks, no demo mode) | "Full functionality on actual scan results" — no curated demo layer | +| Scope | Read everything + light writes (risk register operations) | Snappy demo without async/job-tracking complexity for long-running ops | +| Voice architecture | OpenAI Realtime API (browser-direct WebRTC) | Same as VoiceApp — sub-second latency, natural barge-in | +| Visual language | Distinct dark Transilience theme (not Shasta's existing light/indigo dashboard) | Voice = "command center" experience, deserves its own signature; reuses VoiceApp components | +| Distribution | `pip install shasta[voice]` extra; pre-built React bundle ships in the wheel | No Node required at runtime; voice is opt-in | +| Auth | None (localhost only) | Matches Shasta's existing dashboard pattern; production seam documented | + +## Architecture + +``` +Shasta repo (one git tree) + +src/shasta/ + ├── aws/, azure/, compliance/, db/, evidence/, … ← existing + ├── dashboard/ ← existing HTML dashboard (Jinja, port 8080) + │ + └── voice/ ← NEW SIBLING MODULE + ├── __init__.py + ├── __main__.py ← `python -m shasta.voice` entrypoint + ├── cli.py ← registers `shasta voice` subcommand + ├── app.py ← FastAPI sub-app (separate from dashboard's) + ├── session.py ← /session/token endpoint + ├── realtime_config.py ← system prompt + tool schemas + VAD + ├── store.py ← thin facade over ShastaDB + scoring + ├── observability.py ← structured tool-call logging + ├── tools/ + │ ├── __init__.py + │ ├── findings.py ← list_findings, get_finding, … + │ ├── scores.py ← get_compliance_score, get_trend, … + │ ├── controls.py ← get_control_summary + │ ├── risks.py ← list/get/add/update risk items + │ ├── scans.py ← list_scans, get_latest_scan + │ └── router.py ← FastAPI router exposing /tools/* + └── web/ + ├── dist/ ← pre-built React bundle (committed) + └── src/ ← React source (built via `npm run build`) + +At runtime: + +┌────────────────────────┐ ┌──────────────────────────┐ +│ Browser (localhost) │ ◄── WebRTC ──► │ OpenAI Realtime API │ +│ React app served by │ └──────────────────────────┘ +│ the FastAPI sub-app │ ▲ │ +└──────────┬─────────────┘ │ │ tool_call events + │ POST /tools/{name} │ ephemeral ▼ + ▼ │ token +┌────────────────────────────────────┴──────────┐ +│ shasta voice → FastAPI on :8090 │ +│ • serves / (React bundle from web/dist/) │ +│ • POST /session/token → mints OpenAI key │ +│ • POST /tools/* → executes tool │ +└──────────┬────────────────────────────────────┘ + │ (calls) + ▼ +┌──────────────────────────────────────────────┐ +│ shasta.voice.store (thin facade) │ +│ ├── ShastaDB (existing, unchanged) │ +│ ├── compliance.scorer (existing) │ +│ ├── compliance.iso27001_scorer (existing) │ +│ ├── compliance.hipaa_scorer (existing) │ +│ └── compliance.mapper / iso27001_mapper / │ +│ hipaa_mapper (existing) │ +└──────────────────────────────────────────────┘ +``` + +**Key architectural principles:** + +1. **No mock layer.** Voice reads what the dashboard reads. The store facade composes existing Shasta modules; it does not introduce a second source of truth. +2. **Pre-built React bundle in the pip wheel.** Users do not need Node.js installed to run `shasta voice`. Source lives in `web/src/`, build artifact in `web/dist/`, both committed. CI rebuilds on PRs that touch `web/src/`. (Documented exception to "don't commit build artifacts" — justified because zero-Node distribution is a non-negotiable for the credibility-demo audience.) +3. **Separate FastAPI app + separate port.** Voice console at `:8090`, existing dashboard stays at `:8080`. They can run side by side. Sharing one app would couple lifecycles unnecessarily. +4. **Voice is read-mostly + risk-register writes only.** Heavy operations (scans, reports, Terraform, policy gen) explicitly remain in slash-commands. The system prompt teaches the model to redirect. +5. **No auth in MVP.** Localhost-only. The seam where production-grade auth would plug in is `app.py` middleware + `session.py` token-mint identity binding. + +**Production seams (documented in code):** +- `app.py` CORS origin and `session.py` user identity would derive from JWT in a hosted scenario. +- `store.py` is the only place that touches `ShastaDB` directly — single point of substitution if Shasta ever moves to a different storage backend. + +## Components + +### Python (`src/shasta/voice/`) + +| Module | Responsibility | +|---|---| +| `cli.py` | The `main()` function called by `__main__.py` (so `python -m shasta.voice` works). Argparse-based — validates `OPENAI_API_KEY`, checks `data/shasta.db` has at least one scan, starts uvicorn on `:8090`, opens browser unless `--no-open`. Mirrors the existing `shasta.dashboard.__main__` pattern. | +| `app.py` | FastAPI app, CORS for `http://localhost:8090` only, mounts `web/dist/` as static files at `/`, includes session and tools routers. | +| `session.py` | `POST /session/token` — calls OpenAI's `/v1/realtime/sessions` with the payload from `realtime_config.build_session_payload()`, returns ephemeral client secret. Returns 502 on OpenAI failure, 500 if `OPENAI_API_KEY` missing. | +| `realtime_config.py` | The Distiller system prompt (compliance-flavored persona), JSON schemas for the 14 tools, server VAD config, voice selection, `input_audio_transcription` enabled. Single source of truth. | +| `store.py` | Thin facade over `ShastaDB` and scoring/mapper functions. Methods: `list_findings(...)`, `get_finding(id)`, `get_score(framework)`, `get_multi_framework_score()`, `get_score_trend(framework, limit)`, `get_control_summary(framework, control_id?)`, `list_risk_items(...)`, `get_risk_item(id)`, `add_risk_item(...)`, `update_risk(...)`, `list_scans(limit)`, `get_latest_scan()`. Each returns Pydantic-validated objects. | +| `observability.py` | Structured JSON logging via the `shasta.voice` logger. Logs every tool call with `tool_name`, `args`, `latency_ms`, `result_size`. | +| `tools/findings.py` | `list_findings`, `get_finding`, `list_top_blockers`, `get_resource_findings` — thin wrappers returning JSON dicts. | +| `tools/scores.py` | `get_compliance_score`, `get_multi_framework_score`, `get_score_trend`, `get_ai_governance_score`. | +| `tools/controls.py` | `get_control_summary`, `get_control_findings`. | +| `tools/risks.py` | `list_risk_items`, `get_risk_item`, `add_risk_item`, `update_risk`. The two write paths. | +| `tools/scans.py` | `list_scans`, `get_latest_scan`. | +| `tools/router.py` | FastAPI router under `/tools` prefix; one endpoint per tool with Pydantic request models for input validation; wraps each call with `_timed()` for observability. | + +### React (`src/shasta/voice/web/src/`) + +Reused unchanged from VoiceApp: +- `voice/connection.ts`, `voice/events.ts`, `state/session.ts` +- `components/Header.tsx`, `MicChrome.tsx`, `Transcript.tsx`, `cards/SeverityBadge.tsx`, `cards/ActionToast.tsx` +- `styles/tokens.css`, `global.css` + +Updated for Shasta: +- `tools/types.ts` — replaced with Shasta types (`Finding`, `FindingDetail`, `ComplianceScore`, `MultiFrameworkScore`, `ScoreTrend`, `ControlSummary`, `RiskItem`, `Scan`, `ActionResult`). +- `tools/relay.ts` — `KNOWN_TOOLS` set updated to the 14 Shasta tools. +- `voice/cardDispatcher.ts` — maps each Shasta tool name to its corresponding card kind (or `null` for tools that don't mount a card). +- `components/CardSlot.tsx` — renders the new Shasta card kinds. + +New Shasta-specific cards (`components/cards/`): +- `FindingsList.tsx` — list of findings with severity badge, title, framework chips (multi-framework — e.g., `SOC 2 · CC6.1`, `ISO 27001 · A.9.2`). +- `FindingDetail.tsx` — full finding with description, remediation snippet (truncated), affected resource, control mappings across frameworks, evidence count. +- `ComplianceScore.tsx` — single-framework score with trend arrow, severity breakdown ring, top affected services. +- `MultiFrameworkScore.tsx` — five vertical score columns (one per framework) with brand-gradient fill bars; greyed columns show "not enabled" for frameworks with no findings. +- `ControlSummary.tsx` — findings under a specific control with pass/fail counts and a list of failing resources. +- `RiskList.tsx` — open risks ranked by score, treatment column. +- `RiskDetail.tsx` — single risk with treatment plan, related finding, review history. + +**File-count budget:** ~13 Python files + ~25 React files (mostly reused from VoiceApp). + +### OpenAI Realtime session configuration + +- **Model:** `gpt-realtime` (pinned in `realtime_config.py`) +- **Voice:** `cedar` or `marin` (A/B during build; pick before release) +- **Input audio format:** `pcm16` @ 24kHz +- **Turn detection:** `server_vad`, `threshold: 0.5`, `silence_duration_ms: 500` +- **Tools:** 14 tool schemas (see Tool Inventory below) +- **`input_audio_transcription`:** `{model: "whisper-1"}` so the user's spoken words appear in the transcript panel +- **System prompt:** ~250 words, includes Distiller rules + compliance-flavored persona + tool-use guidance + redirect rules for out-of-scope operations + +## Tool Inventory + +| # | Tool | Args | Returns | Card mounted | +|---|---|---|---|---| +| 1 | `list_findings` | `severity?`, `status?`, `domain?`, `cloud?`, `framework?`, `control_id?`, `limit?` | `Finding[]` | FindingsList | +| 2 | `get_finding` | `finding_id` | `FindingDetail` | FindingDetail | +| 3 | `list_top_blockers` | `limit?` (default 5) | `Finding[]` (highest-severity unresolved) | FindingsList | +| 4 | `get_resource_findings` | `resource_id` | `Finding[]` | FindingsList | +| 5 | `get_compliance_score` | `framework` ∈ {`soc2`, `iso27001`, `hipaa`, `iso42001`, `eu_ai_act`, `ai_governance`} | `ComplianceScore` | ComplianceScore | +| 6 | `get_multi_framework_score` | (no args) | `MultiFrameworkScore` | MultiFrameworkScore | +| 7 | `get_score_trend` | `framework`, `limit?` (default 10) | `ScoreTrend` | (no card — assistant speaks the delta) | +| 8 | `get_control_summary` | `framework`, `control_id?` | `ControlSummary[]` | ControlSummary | +| 9 | `list_scans` | `limit?` (default 10) | `Scan[]` | (no card) | +| 10 | `get_latest_scan` | (no args) | `ScanSummary` | (no card) | +| 11 | `list_risk_items` | `status?`, `level?` | `RiskItem[]` | RiskList | +| 12 | `get_risk_item` | `risk_id` | `RiskItem` | RiskDetail | +| 13 | `add_risk_item` | `title`, `description`, `category`, `likelihood`, `impact`, `treatment`, `treatment_plan?`, `related_finding?` | `ActionResult` | ActionToast | +| 14 | `update_risk` | `risk_id`, plus any of: `treatment`, `treatment_plan`, `status`, `review_notes` | `ActionResult` | ActionToast | + +Tools 13 and 14 are the only writes. They map to single SQLite UPDATEs / INSERTs, complete in <50ms. + +## The Distiller (system prompt) + +``` +You are Shasta's voice compliance assistant for the user's cloud security +posture. You are talking to a security engineer or founder over voice. Their +data is real — you have read access to their actual scan findings, compliance +scores across SOC 2 / ISO 27001 / HIPAA / ISO 42001 / EU AI Act, and risk +register. + +VOICE OUTPUT RULES (non-negotiable): +- Maximum 25 words per response unless the user explicitly asks for detail. +- Lead with the most important fact. Numbers before context. Severity before + description. Failed counts before passing counts. +- Never read JSON, ARNs, IP addresses, or long control IDs out loud unless + the user asks. +- If listing items, name at most 3. Offer to continue ("...and 5 more — want + the full list?"). +- Use plain words, not compliance jargon, unless the user uses jargon first. + (e.g., say "your encryption findings" not "your CC6.7 controls" unless + the user asked about CC6.7.) + +TOOL USE: +- For any question about findings, scores, controls, scans, or risks, call + a tool. Never invent data. +- For ambiguous questions ("show me the critical ones"), make the most + reasonable assumption (default: status=fail, scope=latest scan) and proceed; + mention your assumption briefly. +- After an action tool succeeds (add_risk_item, update_risk), confirm in one + short sentence. +- If a tool returns "no_data" or an empty list, say so honestly. Do not + invent findings. + +REDIRECTS (out of scope for voice — Shasta runs these via Claude Code skills): +- If the user asks to RUN A SCAN: "That's a heavier operation — run `/scan` + in Claude Code. Want me to summarize what it'll do first?" +- If the user asks to GENERATE A REPORT/PDF: "Reports go to disk — run + `/report` to generate one. I can summarize the latest scan first if that + helps." +- If the user asks to GENERATE TERRAFORM / REMEDIATION CODE: "Run `/remediate` + for the Terraform — voice can't deliver code cleanly. I can describe what + the fix does." +- If the user asks to GENERATE POLICY DOCUMENTS: "Run `/policy-gen` for the + policy docs." + +PERSONA: +- Calm, precise, slightly understated. Think experienced compliance engineer + on a Tuesday afternoon, not breaking-news anchor. +- Comfortable with both technical and founder audiences — adjust register to + the question. +- Never apologize for tool latency. Never say "let me check that for you" — + just do it. +``` + +## Data Flow (one turn, end-to-end) + +User says *"What's failing in my latest scan?"* + +1. Browser mic captures audio → WebRTC track to OpenAI +2. Server VAD detects `speech_started` → app shows "listening" state +3. Server VAD detects `speech_stopped` → model begins reasoning +4. Model decides to call `list_findings(status="fail", limit=20)` +5. `tool_call` event arrives at browser via data channel +6. Browser POSTs to `/tools/list_findings` with args +7. FastAPI calls `shasta.voice.store.list_findings(...)` → which calls `ShastaDB.get_latest_scan()`, filters findings, enriches with framework mappings via existing `mapper`/`iso27001_mapper`/`hipaa_mapper`, returns +8. Browser sends `function_call_output` back to OpenAI +9. In parallel: browser dispatches event → `` animates in +10. OpenAI generates audio response (~"You have 12 failing checks. The most urgent is the public S3 bucket policy on prod-customer-data...") +11. Browser plays audio incrementally; transcript text appears in sync +12. Total time: target <1s p50 from `speech_stopped` to first audio byte + +Barge-in works the same way as VoiceApp: VAD detects user speech mid-response, OpenAI cancels, browser stops playback, restart from step 2. + +## CLI Integration + +Invocation matches the existing `shasta.dashboard` pattern (`python -m shasta.dashboard`). No new entry-point in `pyproject.toml` — keeps Shasta's invocation conventions consistent. + +```bash +$ python -m shasta.voice +✓ OPENAI_API_KEY found +✓ data/shasta.db exists (last scan: 2026-05-04 03:14, 34 findings) +→ Starting voice console at http://localhost:8090 +→ Opening browser… +[uvicorn output] +``` + +Failure modes: + +```bash +$ python -m shasta.voice +✗ OPENAI_API_KEY not set in environment + Add to your shell: export OPENAI_API_KEY=sk-... + Or to .env: echo 'OPENAI_API_KEY=sk-...' >> .env + +$ python -m shasta.voice +✗ No scan data in data/shasta.db + Run a scan first: open Claude Code and use /scan +``` + +Flags: +- `--port 8090` (default) +- `--no-open` (don't auto-launch browser) +- `--db data/shasta.db` (override DB path) +- `--host 127.0.0.1` (default; do NOT bind to 0.0.0.0 by default — voice has no auth) + +## UI + +Visual language is **identical to VoiceApp** (dark Transilience theme, Roboto, brand purple→magenta gradient, severity colors mapped to gradient stops). Layout is the same: header at top with logo + connection state; main area split into card slot (left, 70%) and live transcript (right, 30%); mic chrome at the bottom. + +Header is **text-only** (per design decision): no logo asset. Just **"Shasta — Voice Console"** rendered in Roboto Bold against the dark background, with the connection-state indicator dot to the right. Removes one asset to ship and keeps the header lightweight. + +Two notable visual additions vs VoiceApp: + +- **Framework chips on FindingsList items.** Each finding gets small colored chips for the frameworks it maps to (`SOC 2 · CC6.1`, `ISO 27001 · A.9.2`, `HIPAA · §164.312(a)`). Helps the user see compliance overlap at a glance. +- **MultiFrameworkScore card.** Five vertical "score columns" (one per framework), each showing the headline percentage with the brand gradient as a vertical fill bar. Greyed columns for frameworks with no findings (e.g., user hasn't enabled HIPAA). Most visually striking card — anchors the *"how am I doing across the board?"* question. + +## Error & Empty-State Handling + +| State | Behavior | +|---|---| +| No scan data in DB | CLI refuses to start. Server never runs. | +| User asks about something with no matches | Tool returns `{"error": "no_data", "message": "..."}`. Model says *"I don't see any of those — want me to broaden the search?"* | +| User asks about an enabled framework with zero findings | Tool returns the score (likely 100%) with empty findings list. Model says *"You're clean on HIPAA — no findings."* | +| User asks about AI governance but no AI services in last scan | Tool returns `{"score": null, "reason": "no_ai_services_detected"}`. Model says *"I don't see any AI services in your last scan — that framework isn't applicable."* | +| Tool endpoint 500 | Returns `{"error": "tool_unavailable"}` to the model; model apologizes briefly. Logged with full traceback server-side. | +| User asks for an out-of-scope action (run scan, gen report) | System prompt instructs model to redirect with the appropriate slash-command. | +| OpenAI session drops mid-conversation | Reconnect once, surface failure on second attempt. | + +All errors logged via `observability.log_tool_call(error=...)`. No silent failures. + +## Testing + +Different from VoiceApp because there's no mock layer to stub: + +1. **Test SQLite fixture** (`tests/voice/conftest.py`) — pytest fixture creates a fresh SQLite at `tmp_path`, seeds it with a curated `Scan + Findings + RiskItems` payload that exercises every code path. ~150 lines of seed data crafted to support every tool's behavior. The seed data includes findings across multiple frameworks, multiple severities, multiple cloud providers, multiple statuses, plus risk items in various states. +2. **Tool function tests** (`tests/voice/test_tools_*.py`) — one test file per tool module (~5 files), targeting ≥85% line coverage of `src/shasta/voice/tools/`. +3. **Tool endpoint integration tests** (`tests/voice/test_tool_endpoints.py`) — FastAPI TestClient against the test SQLite, hitting every `/tools/*` endpoint with realistic args. +4. **Store facade tests** (`tests/voice/test_store.py`) — verify the facade composes `ShastaDB` + scoring/mapper functions correctly. Catches breakage if Shasta's internal APIs change. +5. **Session token endpoint test** (`tests/voice/test_session.py`) — mocks `httpx.post` to OpenAI, asserts payload shape and error handling. +6. **CLI test** (`tests/voice/test_cli.py`) — verifies the failure-mode messages (missing key, missing DB) and that `python -m shasta.voice --help` runs. +7. **No frontend unit tests** (per VoiceApp precedent — manual rehearsal covers UI). +8. **Manual end-to-end verification** — user runs a real `shasta scan` then `shasta voice`, exercises each major query, confirms cards mount correctly. Documented in the voice section of the Shasta README. + +We do NOT add tests to Shasta's existing test suite for behaviors that already have coverage there (scoring logic, ShastaDB queries). We only test voice-specific code. + +## Distribution / Install + +Add to Shasta's `pyproject.toml`: + +```toml +[project.optional-dependencies] +voice = [ + "fastapi>=0.115", + "uvicorn[standard]>=0.32", + "httpx>=0.27", +] +``` + +Pre-built React bundle committed at `src/shasta/voice/web/dist/`. Add to `.gitattributes`: + +``` +src/shasta/voice/web/dist/** linguist-generated=true +``` + +So GitHub doesn't pollute language stats with the bundle. + +CI workflow (`.github/workflows/voice-bundle.yml`) rebuilds `web/dist/` on PRs that touch `web/src/` and fails if the committed bundle is out of date. + +Package data declaration so `web/dist/**` ships with the wheel: + +```toml +[tool.setuptools.package-data] +"shasta.voice" = ["web/dist/**/*"] +``` + +No `[project.scripts]` entry needed — invocation is `python -m shasta.voice` via the module's `__main__.py` (matches the existing `shasta.dashboard` pattern). + +Update Shasta's root README (`README.md`) to mention voice as an optional capability: + +> **Voice console (optional).** `pip install shasta[voice]` adds a voice-driven dashboard. After running a scan via Claude Code's `/scan` skill: `python -m shasta.voice` opens a browser at localhost:8090 — talk to your compliance posture. Requires `OPENAI_API_KEY`. Demo: a 60-second screen recording will be added once the prototype is rehearsed and recorded. + +## Success Criteria + +- `python -m shasta.voice` starts cleanly given a valid scan DB and `OPENAI_API_KEY`. +- All 14 tools work end-to-end against a real scan: read tools return correct data, write tools persist correctly to SQLite. +- Time from `speech_stopped` → first audio byte: <1s p50, <1.5s p95. +- Zero hallucinated data — every finding/score/risk the model mentions is traceable to the user's actual SQLite. +- Voice responses ≤25 words target, ≤30 words at p95. +- Barge-in works: user interrupts mid-sentence, assistant stops within 200ms. +- Tool layer test suite: ≥85% line coverage of `src/shasta/voice/tools/` and `src/shasta/voice/store.py`. +- The line *"to make this multi-tenant: add JWT middleware to `app.py` and bind user identity in `session.py`"* must be defensibly true on inspection. + +## Cost envelope + +- OpenAI Realtime: ~$0.06/min input, ~$0.24/min output. Build + ~5 rehearsals + light user testing: ~90 minutes of voice. Realistic budget: **$20–40** in OpenAI charges across the project. Hard cap: kill if it exceeds $75. +- No other paid services. +- Users running ShastaVoice pay for their own OpenAI usage. Document the per-minute cost in the voice section of the README so users can budget. + +## Out of scope (deliberately) + +- Demo / seeded fake data +- Multi-user sessions, accounts, JWT auth +- Long-running operation triggering over voice (scans, reports, remediation, policy gen) +- Mobile / responsive layout +- Hosted version of voice (e.g., voice.shasta.dev) — out of scope for this MVP +- Custom voice cloning +- Cost monitoring / per-tenant rate limiting (defer to hosted scenario) +- Internationalization +- Replay / save conversation history +- Voice-driven Whitney scans diff --git a/pyproject.toml b/pyproject.toml index 9b2aca8..b2458fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "shasta" -version = "1.8.0" +version = "1.9.0" description = "Multi-cloud compliance and AI governance platform — SOC 2, ISO 27001, HIPAA, ISO 42001, and EU AI Act" readme = "README.md" requires-python = ">=3.11" @@ -69,6 +69,11 @@ dashboard = [ semgrep = [ "semgrep>=1.150.0", ] +voice = [ + "fastapi>=0.115.0", + "uvicorn[standard]>=0.32.0", + "httpx>=0.27.0", +] dev = [ "pytest>=8.0", "pytest-asyncio>=0.24", @@ -84,6 +89,7 @@ build-backend = "hatchling.build" packages = ["src/shasta"] [tool.hatch.build.targets.wheel.force-include] +"src/shasta/voice/web/dist" = "shasta/voice/web/dist" [tool.pytest.ini_options] testpaths = ["tests"] diff --git a/src/shasta/db/schema.py b/src/shasta/db/schema.py index 4f8b18c..9b8a2a2 100644 --- a/src/shasta/db/schema.py +++ b/src/shasta/db/schema.py @@ -104,7 +104,7 @@ def __init__(self, db_path: Path | str = DEFAULT_DB_PATH): @property def conn(self) -> sqlite3.Connection: if self._conn is None: - self._conn = sqlite3.connect(str(self._db_path)) + self._conn = sqlite3.connect(str(self._db_path), check_same_thread=False) self._conn.row_factory = sqlite3.Row self._conn.execute("PRAGMA journal_mode=WAL") self._conn.execute("PRAGMA foreign_keys=ON") diff --git a/src/shasta/voice/__init__.py b/src/shasta/voice/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/shasta/voice/__main__.py b/src/shasta/voice/__main__.py new file mode 100644 index 0000000..36676b1 --- /dev/null +++ b/src/shasta/voice/__main__.py @@ -0,0 +1,5 @@ +"""Entry point for `python -m shasta.voice`.""" +from shasta.voice.cli import main + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/shasta/voice/app.py b/src/shasta/voice/app.py new file mode 100644 index 0000000..6c393bd --- /dev/null +++ b/src/shasta/voice/app.py @@ -0,0 +1,46 @@ +"""FastAPI application for the voice console.""" +import os +from pathlib import Path + +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from fastapi.staticfiles import StaticFiles + +from shasta.voice.observability import configure_logging +from shasta.voice.session import router as session_router +from shasta.voice.store import Store +from shasta.voice.tools.router import router as tools_router + + +def create_app(*, db_path: str | Path | None = None, serve_static: bool = True) -> FastAPI: + configure_logging() + app = FastAPI(title="Shasta Voice Console", version="0.1.0") + + allowed = os.environ.get("ALLOWED_ORIGINS", "http://localhost:8090").split(",") + app.add_middleware( + CORSMiddleware, + allow_origins=allowed, + allow_credentials=False, + allow_methods=["GET", "POST"], + allow_headers=["Content-Type"], + ) + + # One Store per process — reuses the same SQLite connection + app.state.store = Store(db_path=db_path) + + @app.get("/health") + def health() -> dict: + return {"status": "ok"} + + app.include_router(session_router) + app.include_router(tools_router) + + if serve_static: + dist = Path(__file__).parent / "web" / "dist" + if dist.exists(): + app.mount("/", StaticFiles(directory=str(dist), html=True), name="static") + return app + + +# Module-level app for `uvicorn shasta.voice.app:app` +app = create_app() diff --git a/src/shasta/voice/cli.py b/src/shasta/voice/cli.py new file mode 100644 index 0000000..90e7612 --- /dev/null +++ b/src/shasta/voice/cli.py @@ -0,0 +1,60 @@ +"""`python -m shasta.voice` entrypoint.""" +import argparse +import os +import sys +import webbrowser +from pathlib import Path + +from shasta.voice.app import create_app + + +def main() -> int: + parser = argparse.ArgumentParser(prog="shasta.voice", description="Voice console for Shasta") + parser.add_argument("--port", type=int, default=8090) + parser.add_argument("--host", default="127.0.0.1") + parser.add_argument("--no-open", action="store_true", help="don't auto-launch browser") + parser.add_argument("--db", type=Path, default=None, help="path to shasta.db (default: data/shasta.db)") + args = parser.parse_args() + + api_key = os.environ.get("OPENAI_API_KEY") + if not api_key or api_key == "sk-replace-me": + print("✗ OPENAI_API_KEY not set in environment", file=sys.stderr) + print(" Add to your shell: export OPENAI_API_KEY=sk-...", file=sys.stderr) + return 2 + + db_path = args.db or Path("data/shasta.db") + if not db_path.exists(): + print(f"✗ No scan data at {db_path}", file=sys.stderr) + print(" Run a scan first: open Claude Code and use /scan", file=sys.stderr) + return 2 + + # Verify the DB has at least one scan + from shasta.voice.store import Store + s = Store(db_path=db_path) + if not s.has_data(): + print(f"✗ {db_path} exists but contains no scan data", file=sys.stderr) + print(" Run a scan first: open Claude Code and use /scan", file=sys.stderr) + s.close() + return 2 + latest = s.get_latest_scan() + s.close() + + print("✓ OPENAI_API_KEY found") + print(f"✓ {db_path} (latest scan: {latest.completed_at}, {latest.total_findings} findings)") + url = f"http://{args.host}:{args.port}" + print(f"→ Starting voice console at {url}") + + if not args.no_open: + try: + webbrowser.open(url) + except Exception: + pass + + import uvicorn + app = create_app(db_path=db_path) + uvicorn.run(app, host=args.host, port=args.port, log_level="info") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/shasta/voice/models.py b/src/shasta/voice/models.py new file mode 100644 index 0000000..d26a494 --- /dev/null +++ b/src/shasta/voice/models.py @@ -0,0 +1,106 @@ +"""Pydantic I/O models for voice tools — JSON-serializable views over Shasta core models.""" +from __future__ import annotations + +from datetime import datetime +from typing import Any, Literal + +from pydantic import BaseModel, Field + +Severity = Literal["critical", "high", "medium", "low", "info"] +Status = Literal["pass", "fail", "partial", "not_assessed", "not_applicable"] +Cloud = Literal["aws", "azure"] +Framework = Literal["soc2", "iso27001", "hipaa", "iso42001", "eu_ai_act", "ai_governance"] + + +class FindingSummary(BaseModel): + id: str + check_id: str + title: str + severity: Severity + status: Status + domain: str + resource_id: str + cloud_provider: Cloud + soc2_controls: list[str] = Field(default_factory=list) + iso27001_controls: list[str] = Field(default_factory=list) + hipaa_controls: list[str] = Field(default_factory=list) + + +class FindingDetailView(FindingSummary): + description: str + remediation: str = "" + region: str + account_id: str + details: dict[str, Any] = Field(default_factory=dict) + timestamp: datetime + + +class ComplianceScoreView(BaseModel): + framework: Framework + score_percentage: float + grade: str + total_controls: int + passing: int + failing: int + partial: int + not_assessed: int + total_findings: int + findings_failed: int + + +class MultiFrameworkScoreView(BaseModel): + frameworks: list[ComplianceScoreView] = Field(default_factory=list) + not_enabled: list[Framework] = Field(default_factory=list) + + +class ScoreTrendView(BaseModel): + framework: Framework + points: list[dict[str, Any]] = Field(default_factory=list) + delta: float # latest - earliest + + +class ControlSummaryView(BaseModel): + framework: Framework + control_id: str + title: str + overall_status: str + pass_count: int + fail_count: int + partial_count: int + finding_ids: list[str] = Field(default_factory=list) + + +class RiskItemView(BaseModel): + risk_id: str + title: str + description: str + category: str + likelihood: str + impact: str + risk_score: int + risk_level: str + treatment: str + treatment_plan: str | None = None + status: str + soc2_controls: list[str] = Field(default_factory=list) + related_finding: str | None = None + + +class ScanSummaryView(BaseModel): + scan_id: str + account_id: str + cloud_provider: Cloud + completed_at: datetime | None + total_findings: int + critical_count: int + high_count: int + medium_count: int + low_count: int + passed: int + failed: int + + +class ActionResult(BaseModel): + success: bool + message: str + record_id: str | None = None diff --git a/src/shasta/voice/observability.py b/src/shasta/voice/observability.py new file mode 100644 index 0000000..d74e7cf --- /dev/null +++ b/src/shasta/voice/observability.py @@ -0,0 +1,32 @@ +"""Structured logging for tool calls.""" +import json +import logging +import os +from typing import Any + +_LOGGER = logging.getLogger("shasta.voice") + + +def configure_logging() -> None: + level = os.environ.get("LOG_LEVEL", "INFO").upper() + logging.basicConfig(level=level, format="%(message)s") + _LOGGER.setLevel(level) + + +def log_tool_call( + tool_name: str, + args: dict[str, Any], + latency_ms: float, + result_size: int, + error: str | None = None, +) -> None: + payload: dict[str, Any] = { + "event": "tool_call", + "tool_name": tool_name, + "args": args, + "latency_ms": round(latency_ms, 2), + "result_size": result_size, + } + if error: + payload["error"] = error + _LOGGER.info(json.dumps(payload)) diff --git a/src/shasta/voice/realtime_config.py b/src/shasta/voice/realtime_config.py new file mode 100644 index 0000000..52f68f5 --- /dev/null +++ b/src/shasta/voice/realtime_config.py @@ -0,0 +1,216 @@ +"""OpenAI Realtime session configuration: Distiller prompt, 14 tool schemas, VAD.""" +import os +from typing import Any + +SYSTEM_PROMPT = """You are Shasta's voice compliance assistant. You are talking to a security engineer or founder over voice. Their data is real — you have read access to their actual scan findings, compliance scores across SOC 2 / ISO 27001 / HIPAA / ISO 42001 / EU AI Act, and risk register. + +VOICE OUTPUT RULES (non-negotiable): +- Maximum 25 words per response unless the user explicitly asks for detail. +- Lead with the most important fact. Numbers before context. Severity before description. Failed counts before passing counts. +- Never read JSON, ARNs, IP addresses, or long control IDs out loud unless the user asks. +- If listing items, name at most 3. Offer to continue ("...and 5 more — want the full list?"). +- Use plain words, not compliance jargon, unless the user uses jargon first. + +TOOL USE: +- For any question about findings, scores, controls, scans, or risks, call a tool. Never invent data. +- For ambiguous questions, make the most reasonable assumption (default: status=fail, scope=latest scan) and proceed; mention your assumption briefly. +- After an action tool succeeds (add_risk_item, update_risk), confirm in one short sentence. +- If a tool returns "no_data" or empty, say so honestly. Do not invent. + +REDIRECTS (out of scope for voice — Shasta runs these via Claude Code skills): +- RUN A SCAN → "Run /scan in Claude Code. Want me to summarize what it'll do first?" +- GENERATE A REPORT/PDF → "Run /report — voice can't deliver PDFs. I can summarize the latest scan." +- GENERATE TERRAFORM → "Run /remediate for the Terraform. I can describe what the fix does." +- GENERATE POLICY DOCS → "Run /policy-gen for the policy docs." + +PERSONA: +- Calm, precise, slightly understated. Experienced compliance engineer on a Tuesday afternoon. +- Adjust register to the audience — technical for engineers, plainer for founders. +- Never apologize for tool latency. Never say "let me check that for you" — just do it. +""" + +TOOL_SCHEMAS: list[dict[str, Any]] = [ + { + "type": "function", "name": "list_findings", + "description": "List compliance findings from the latest scan. Filter by severity, status, domain, cloud, framework, control.", + "parameters": { + "type": "object", + "properties": { + "severity": {"type": "string", "enum": ["critical", "high", "medium", "low", "info"]}, + "status": {"type": "string", "enum": ["pass", "fail", "partial", "not_assessed", "not_applicable"]}, + "domain": {"type": "string", "enum": ["iam", "networking", "encryption", "logging", "compute", "storage", "monitoring", "ai_governance"]}, + "cloud": {"type": "string", "enum": ["aws", "azure"]}, + "framework": {"type": "string", "enum": ["soc2", "iso27001", "hipaa"]}, + "control_id": {"type": "string", "description": "e.g., CC6.1 — only meaningful with framework set"}, + "limit": {"type": "integer", "minimum": 1, "maximum": 100}, + }, + "additionalProperties": False, + }, + }, + { + "type": "function", "name": "get_finding", + "description": "Get full detail of a single finding by ID — description, remediation, affected resource, control mappings.", + "parameters": { + "type": "object", + "properties": {"finding_id": {"type": "string"}}, + "required": ["finding_id"], + "additionalProperties": False, + }, + }, + { + "type": "function", "name": "list_top_blockers", + "description": "List the highest-severity unresolved findings. Use for 'what should I fix first?' questions.", + "parameters": { + "type": "object", + "properties": {"limit": {"type": "integer", "minimum": 1, "maximum": 20}}, + "additionalProperties": False, + }, + }, + { + "type": "function", "name": "get_resource_findings", + "description": "List all findings for a specific cloud resource (by ARN or Azure resource ID).", + "parameters": { + "type": "object", + "properties": {"resource_id": {"type": "string"}}, + "required": ["resource_id"], + "additionalProperties": False, + }, + }, + { + "type": "function", "name": "get_compliance_score", + "description": "Get the compliance score for one framework. Use when the user asks about a specific standard.", + "parameters": { + "type": "object", + "properties": {"framework": {"type": "string", "enum": ["soc2", "iso27001", "hipaa", "iso42001", "eu_ai_act", "ai_governance"]}}, + "required": ["framework"], + "additionalProperties": False, + }, + }, + { + "type": "function", "name": "get_multi_framework_score", + "description": "Get scores for ALL frameworks at once. Use for 'how am I doing across the board?' or 'overall posture' questions.", + "parameters": {"type": "object", "properties": {}, "additionalProperties": False}, + }, + { + "type": "function", "name": "get_score_trend", + "description": "Get score history for a framework across recent scans. Use for 'how does that compare to last week?' questions.", + "parameters": { + "type": "object", + "properties": { + "framework": {"type": "string", "enum": ["soc2", "iso27001", "hipaa"]}, + "limit": {"type": "integer", "minimum": 2, "maximum": 50}, + }, + "required": ["framework"], + "additionalProperties": False, + }, + }, + { + "type": "function", "name": "get_control_summary", + "description": "Get summary of a specific control (e.g., CC6.1) or all controls in a framework. Returns pass/fail counts + finding IDs.", + "parameters": { + "type": "object", + "properties": { + "framework": {"type": "string", "enum": ["soc2", "iso27001", "hipaa"]}, + "control_id": {"type": "string"}, + }, + "required": ["framework"], + "additionalProperties": False, + }, + }, + { + "type": "function", "name": "list_scans", + "description": "List recent scans with summary stats (date, total findings, pass/fail counts).", + "parameters": { + "type": "object", + "properties": {"limit": {"type": "integer", "minimum": 1, "maximum": 50}}, + "additionalProperties": False, + }, + }, + { + "type": "function", "name": "get_latest_scan", + "description": "Get summary of the most recent scan: when it ran, total findings, severity counts.", + "parameters": {"type": "object", "properties": {}, "additionalProperties": False}, + }, + { + "type": "function", "name": "list_risk_items", + "description": "List risk register items. Filter by status (open/in_progress/accepted/resolved) or level (high/medium/low).", + "parameters": { + "type": "object", + "properties": { + "account_id": {"type": "string", "description": "Cloud account ID — pass the user's account from latest scan if unknown"}, + "status": {"type": "string", "enum": ["open", "in_progress", "accepted", "resolved"]}, + "level": {"type": "string", "enum": ["high", "medium", "low"]}, + }, + "required": ["account_id"], + "additionalProperties": False, + }, + }, + { + "type": "function", "name": "get_risk_item", + "description": "Get a single risk register item by ID.", + "parameters": { + "type": "object", + "properties": {"risk_id": {"type": "string"}, "account_id": {"type": "string"}}, + "required": ["risk_id"], + "additionalProperties": False, + }, + }, + { + "type": "function", "name": "add_risk_item", + "description": "Add a new risk to the risk register. Use when the user explicitly asks to record a risk.", + "parameters": { + "type": "object", + "properties": { + "account_id": {"type": "string"}, + "title": {"type": "string"}, + "description": {"type": "string"}, + "category": {"type": "string", "description": "e.g., iam, logging, encryption"}, + "likelihood": {"type": "string", "enum": ["low", "medium", "high"]}, + "impact": {"type": "string", "enum": ["low", "medium", "high"]}, + "treatment": {"type": "string", "enum": ["mitigate", "accept", "transfer", "avoid"]}, + "treatment_plan": {"type": "string"}, + "related_finding": {"type": "string", "description": "Optional finding ID this risk relates to"}, + }, + "required": ["account_id", "title", "description", "category", "likelihood", "impact", "treatment"], + "additionalProperties": False, + }, + }, + { + "type": "function", "name": "update_risk", + "description": "Update an existing risk register item. Pass risk_id plus any fields to change.", + "parameters": { + "type": "object", + "properties": { + "risk_id": {"type": "string"}, + "account_id": {"type": "string"}, + "treatment": {"type": "string", "enum": ["mitigate", "accept", "transfer", "avoid"]}, + "treatment_plan": {"type": "string"}, + "status": {"type": "string", "enum": ["open", "in_progress", "accepted", "resolved"]}, + "review_notes": {"type": "string"}, + }, + "required": ["risk_id"], + "additionalProperties": False, + }, + }, +] + + +VAD_CONFIG: dict[str, Any] = { + "type": "server_vad", + "threshold": 0.5, + "prefix_padding_ms": 300, + "silence_duration_ms": 500, +} + + +def build_session_payload() -> dict[str, Any]: + return { + "model": os.environ.get("OPENAI_REALTIME_MODEL", "gpt-realtime"), + "voice": os.environ.get("OPENAI_REALTIME_VOICE", "cedar"), + "instructions": SYSTEM_PROMPT, + "tools": TOOL_SCHEMAS, + "turn_detection": VAD_CONFIG, + "input_audio_format": "pcm16", + "output_audio_format": "pcm16", + "input_audio_transcription": {"model": "whisper-1"}, + } diff --git a/src/shasta/voice/session.py b/src/shasta/voice/session.py new file mode 100644 index 0000000..3ca019f --- /dev/null +++ b/src/shasta/voice/session.py @@ -0,0 +1,37 @@ +"""Ephemeral OpenAI Realtime token endpoint.""" +import os + +import httpx +from fastapi import APIRouter, HTTPException + +from shasta.voice.realtime_config import build_session_payload + +router = APIRouter() + + +@router.post("/session/token") +def mint_session_token() -> dict: + api_key = os.environ.get("OPENAI_API_KEY") + if not api_key or api_key == "sk-replace-me": + raise HTTPException(status_code=500, detail="OPENAI_API_KEY not configured") + + payload = build_session_payload() + try: + response = httpx.post( + "https://api.openai.com/v1/realtime/sessions", + headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}, + json=payload, + timeout=10.0, + ) + except httpx.HTTPError as e: + raise HTTPException(status_code=502, detail=f"OpenAI request failed: {e}") from e + + if response.status_code != 200: + raise HTTPException(status_code=502, detail=f"OpenAI session creation failed ({response.status_code}): {response.text[:200]}") + + body = response.json() + return { + "client_secret": body["client_secret"]["value"], + "expires_at": body["client_secret"]["expires_at"], + "model": payload["model"], + } diff --git a/src/shasta/voice/store.py b/src/shasta/voice/store.py new file mode 100644 index 0000000..42cb9a4 --- /dev/null +++ b/src/shasta/voice/store.py @@ -0,0 +1,458 @@ +"""Voice store — facade over ShastaDB + compliance scoring/mapper functions. + +The only place in the voice module that touches Shasta core. Replace the body +of any read method to swap data sources without touching tool code. +""" +from __future__ import annotations + +from datetime import UTC, datetime +from pathlib import Path + +from shasta.compliance.hipaa_mapper import ( + enrich_findings_with_hipaa, + get_hipaa_control_summary, +) +from shasta.compliance.hipaa_scorer import calculate_hipaa_score +from shasta.compliance.iso27001_mapper import ( + enrich_findings_with_iso27001, + get_iso27001_control_summary, +) +from shasta.compliance.iso27001_scorer import calculate_iso27001_score +from shasta.compliance.mapper import enrich_findings_with_controls, get_control_summary +from shasta.compliance.scorer import calculate_score +from shasta.db.schema import ShastaDB +from shasta.evidence.models import Finding, ScanResult +from shasta.voice.models import ( + ActionResult, + ComplianceScoreView, + ControlSummaryView, + FindingDetailView, + FindingSummary, + Framework, + MultiFrameworkScoreView, + RiskItemView, + ScanSummaryView, + ScoreTrendView, +) + +# Scoring + mapper dispatch tables +_SCORERS = { + "soc2": calculate_score, + "iso27001": calculate_iso27001_score, + "hipaa": calculate_hipaa_score, +} + +_CONTROL_SUMMARIES = { + "soc2": get_control_summary, + "iso27001": get_iso27001_control_summary, + "hipaa": get_hipaa_control_summary, +} + + +def _enrich_all(findings: list[Finding]) -> list[Finding]: + enrich_findings_with_controls(findings) + enrich_findings_with_iso27001(findings) + enrich_findings_with_hipaa(findings) + return findings + + +def _finding_to_summary(f: Finding) -> FindingSummary: + return FindingSummary( + id=f.id, + check_id=f.check_id, + title=f.title, + severity=f.severity.value, + status=f.status.value, + domain=f.domain.value, + resource_id=f.resource_id, + cloud_provider=f.cloud_provider.value, + soc2_controls=list(f.soc2_controls), + iso27001_controls=list(f.iso27001_controls), + hipaa_controls=list(f.hipaa_controls), + ) + + +def _finding_to_detail(f: Finding) -> FindingDetailView: + return FindingDetailView( + **_finding_to_summary(f).model_dump(), + description=f.description, + remediation=f.remediation, + region=f.region, + account_id=f.account_id, + details=dict(f.details), + timestamp=f.timestamp if isinstance(f.timestamp, datetime) else datetime.fromisoformat(f.timestamp), + ) + + +_SEVERITY_RANK = {"critical": 0, "high": 1, "medium": 2, "low": 3, "info": 4} + + +class Store: + """Read+write facade for the voice module. + + Production seam: replace the body of any method to swap the data source. + Function signatures and return types must not change. + """ + + def __init__(self, db_path: Path | str | None = None): + self._db = ShastaDB(db_path=db_path) if db_path else ShastaDB() + self._db.initialize() + # Cache the latest enriched scan to avoid re-enriching across calls + self._cached_scan: ScanResult | None = None + self._cached_scan_id: str | None = None + + def close(self) -> None: + self._db.close() + + def _latest_scan(self) -> ScanResult | None: + scan = self._db.get_latest_scan() + if scan is None: + return None + if self._cached_scan_id != scan.id: + _enrich_all(scan.findings) + self._cached_scan = scan + self._cached_scan_id = scan.id + return self._cached_scan + + def has_data(self) -> bool: + return self._latest_scan() is not None + + # ---- Scans ---- + + def get_latest_scan(self) -> ScanSummaryView | None: + scan = self._latest_scan() + if scan is None: + return None + summary = scan.summary + return ScanSummaryView( + scan_id=scan.id, + account_id=scan.account_id, + cloud_provider=scan.cloud_provider.value if hasattr(scan.cloud_provider, "value") else scan.cloud_provider, + completed_at=scan.completed_at if isinstance(scan.completed_at, datetime) else (datetime.fromisoformat(scan.completed_at) if scan.completed_at else None), + total_findings=summary.total_findings if summary else len(scan.findings), + critical_count=summary.critical_count if summary else 0, + high_count=summary.high_count if summary else 0, + medium_count=summary.medium_count if summary else 0, + low_count=summary.low_count if summary else 0, + passed=summary.passed if summary else 0, + failed=summary.failed if summary else 0, + ) + + def list_scans(self, limit: int = 10) -> list[ScanSummaryView]: + history = self._db.get_scan_history(limit=limit) + out: list[ScanSummaryView] = [] + for row in history: + import json as _json + summary_blob = row.get("summary") + if summary_blob: + s = _json.loads(summary_blob) + else: + s = {} + out.append(ScanSummaryView( + scan_id=row["id"], + account_id=row["account_id"], + cloud_provider="aws", # history rows don't carry cloud_provider in this query + completed_at=datetime.fromisoformat(row["completed_at"]) if row.get("completed_at") else None, + total_findings=s.get("total_findings", 0), + critical_count=s.get("critical_count", 0), + high_count=s.get("high_count", 0), + medium_count=s.get("medium_count", 0), + low_count=s.get("low_count", 0), + passed=s.get("passed", 0), + failed=s.get("failed", 0), + )) + return out + + # ---- Findings ---- + + def list_findings( + self, + severity: str | None = None, + status: str | None = None, + domain: str | None = None, + cloud: str | None = None, + framework: Framework | None = None, + control_id: str | None = None, + limit: int | None = None, + ) -> list[FindingSummary]: + scan = self._latest_scan() + if scan is None: + return [] + results = list(scan.findings) + if severity: + results = [f for f in results if f.severity.value == severity] + if status: + results = [f for f in results if f.status.value == status] + if domain: + results = [f for f in results if f.domain.value == domain] + if cloud: + results = [f for f in results if f.cloud_provider.value == cloud] + if framework: + attr = {"soc2": "soc2_controls", "iso27001": "iso27001_controls", "hipaa": "hipaa_controls"}.get(framework) + if attr: + results = [f for f in results if getattr(f, attr)] + if control_id and framework: + attr = {"soc2": "soc2_controls", "iso27001": "iso27001_controls", "hipaa": "hipaa_controls"}.get(framework) + if attr: + results = [f for f in results if control_id in getattr(f, attr)] + results.sort(key=lambda f: (_SEVERITY_RANK.get(f.severity.value, 99), 0 if f.status.value == "fail" else 1)) + if limit: + results = results[:limit] + return [_finding_to_summary(f) for f in results] + + def get_finding(self, finding_id: str) -> FindingDetailView | None: + scan = self._latest_scan() + if scan is None: + return None + for f in scan.findings: + if f.id == finding_id: + return _finding_to_detail(f) + return None + + def list_top_blockers(self, limit: int = 5) -> list[FindingSummary]: + return self.list_findings(status="fail", limit=limit) + + def get_resource_findings(self, resource_id: str) -> list[FindingSummary]: + scan = self._latest_scan() + if scan is None: + return [] + matches = [f for f in scan.findings if f.resource_id == resource_id] + matches.sort(key=lambda f: _SEVERITY_RANK.get(f.severity.value, 99)) + return [_finding_to_summary(f) for f in matches] + + # ---- Scores ---- + + def _score_view(self, framework: Framework, score) -> ComplianceScoreView: + return ComplianceScoreView( + framework=framework, + score_percentage=getattr(score, "score_percentage", 0.0), + grade=getattr(score, "grade", "F"), + total_controls=getattr(score, "total_controls", 0), + passing=getattr(score, "passing", 0), + failing=getattr(score, "failing", 0), + partial=getattr(score, "partial", 0), + not_assessed=getattr(score, "not_assessed", 0), + total_findings=getattr(score, "total_findings", 0), + findings_failed=getattr(score, "findings_failed", 0), + ) + + def get_compliance_score(self, framework: Framework) -> ComplianceScoreView | None: + scan = self._latest_scan() + if scan is None: + return None + scorer = _SCORERS.get(framework) + if scorer is None: + # iso42001 / eu_ai_act / ai_governance — treat as AI governance for now + try: + from shasta.compliance.ai.scorer import calculate_ai_governance_score + ai_findings = [f for f in scan.findings if f.domain.value == "ai_governance"] + if not ai_findings: + return None + score = calculate_ai_governance_score(ai_findings) + return self._score_view(framework, score) + except ImportError: + return None + score = scorer(scan.findings) + return self._score_view(framework, score) + + def get_multi_framework_score(self) -> MultiFrameworkScoreView: + scan = self._latest_scan() + if scan is None: + return MultiFrameworkScoreView() + frameworks: list[ComplianceScoreView] = [] + not_enabled: list[Framework] = [] + for fw in ("soc2", "iso27001", "hipaa"): + score = self.get_compliance_score(fw) # type: ignore[arg-type] + if score is not None and score.total_controls > 0: + frameworks.append(score) + else: + not_enabled.append(fw) # type: ignore[arg-type] + # AI frameworks + for fw in ("iso42001", "eu_ai_act", "ai_governance"): + score = self.get_compliance_score(fw) # type: ignore[arg-type] + if score is not None: + frameworks.append(score) + else: + not_enabled.append(fw) # type: ignore[arg-type] + return MultiFrameworkScoreView(frameworks=frameworks, not_enabled=not_enabled) + + def get_score_trend(self, framework: Framework, limit: int = 10) -> ScoreTrendView: + history = self._db.get_scan_history(limit=limit) + scorer = _SCORERS.get(framework) + points = [] + for row in history: + tmp_scan_id = row["id"] + # Cheap read: only fetch findings for that scan_id + full = self._db._get_findings_for_scan(tmp_scan_id) # noqa: SLF001 — internal but stable + _enrich_all(full) + if scorer is not None: + s = scorer(full) + points.append({ + "scan_id": tmp_scan_id, + "completed_at": row.get("completed_at"), + "score_percentage": getattr(s, "score_percentage", 0.0), + }) + # Oldest first for delta math + points.sort(key=lambda p: p["completed_at"] or "") + delta = (points[-1]["score_percentage"] - points[0]["score_percentage"]) if len(points) >= 2 else 0.0 + return ScoreTrendView(framework=framework, points=points, delta=round(delta, 1)) + + # ---- Controls ---- + + def get_control_summary(self, framework: Framework, control_id: str | None = None) -> list[ControlSummaryView]: + scan = self._latest_scan() + if scan is None: + return [] + summary_fn = _CONTROL_SUMMARIES.get(framework) + if summary_fn is None: + return [] + summary = summary_fn(scan.findings) + out: list[ControlSummaryView] = [] + for cid, data in summary.items(): + if control_id and cid != control_id: + continue + out.append(ControlSummaryView( + framework=framework, + control_id=cid, + title=data.get("title", cid), + overall_status=data.get("overall_status", "not_assessed"), + pass_count=data.get("pass_count", 0), + fail_count=data.get("fail_count", 0), + partial_count=data.get("partial_count", 0), + finding_ids=[f.id for f in data.get("findings", [])], + )) + return out + + # ---- Risks ---- + + def _risk_row_to_view(self, row: dict) -> RiskItemView: + import json as _json + return RiskItemView( + risk_id=row["risk_id"], + title=row["title"], + description=row["description"], + category=row["category"], + likelihood=row["likelihood"], + impact=row["impact"], + risk_score=row["risk_score"], + risk_level=row["risk_level"], + treatment=row["treatment"], + treatment_plan=row.get("treatment_plan"), + status=row["status"], + soc2_controls=_json.loads(row["soc2_controls"]) if row.get("soc2_controls") else [], + related_finding=row.get("related_finding"), + ) + + def list_risk_items(self, account_id: str, status: str | None = None, level: str | None = None) -> list[RiskItemView]: + rows = self._db.get_risk_items(account_id) + if status: + rows = [r for r in rows if r.get("status") == status] + if level: + rows = [r for r in rows if r.get("risk_level") == level] + return [self._risk_row_to_view(r) for r in rows] + + def get_risk_item(self, risk_id: str, account_id: str = "123456789012") -> RiskItemView | None: + rows = self._db.get_risk_items(account_id) + for r in rows: + if r["risk_id"] == risk_id: + return self._risk_row_to_view(r) + return None + + def add_risk_item( + self, + *, + account_id: str, + title: str, + description: str, + category: str, + likelihood: str, + impact: str, + treatment: str, + treatment_plan: str | None = None, + related_finding: str | None = None, + soc2_controls: list[str] | None = None, + ) -> ActionResult: + from datetime import datetime + from uuid import uuid4 + risk_id = f"R-{uuid4().hex[:8].upper()}" + # Score: simple LxI matrix on a 1-3 scale → 1-9 + scale = {"low": 1, "medium": 2, "high": 3} + likelihood_score = scale.get(likelihood.lower(), 2) + impact_score = scale.get(impact.lower(), 2) + score = likelihood_score * impact_score + level = "high" if score >= 6 else "medium" if score >= 3 else "low" + now = datetime.now(UTC).isoformat() + + # Build a record matching the columns expected by save_risk_items + # save_risk_items expects an object with attributes — wrap in a SimpleNamespace + from types import SimpleNamespace + item = SimpleNamespace( + risk_id=risk_id, + title=title, + description=description, + category=category, + likelihood=likelihood, + impact=impact, + risk_score=score, + risk_level=level, + owner=None, + treatment=treatment, + treatment_plan=treatment_plan, + status="open", + soc2_controls=soc2_controls or [], + related_finding=related_finding, + created_date=now, + last_reviewed=now, + review_notes=None, + ) + try: + self._db.save_risk_items([item], account_id=account_id) + except Exception as e: + return ActionResult(success=False, message=f"Failed to add risk: {e}") + return ActionResult(success=True, message=f"Added risk {risk_id}", record_id=risk_id) + + def update_risk( + self, + risk_id: str, + *, + treatment: str | None = None, + treatment_plan: str | None = None, + status: str | None = None, + review_notes: str | None = None, + account_id: str = "123456789012", + ) -> ActionResult: + existing = self.get_risk_item(risk_id, account_id=account_id) + if existing is None: + return ActionResult(success=False, message=f"Risk {risk_id} not found") + from datetime import datetime + from types import SimpleNamespace + # Recreate the row with overrides — save_risk_items uses INSERT OR REPLACE + scale = {"low": 1, "medium": 2, "high": 3} + likelihood_score = scale.get(existing.likelihood.lower(), 2) + impact_score = scale.get(existing.impact.lower(), 2) + score = likelihood_score * impact_score + level = "high" if score >= 6 else "medium" if score >= 3 else "low" + item = SimpleNamespace( + risk_id=existing.risk_id, + title=existing.title, + description=existing.description, + category=existing.category, + likelihood=existing.likelihood, + impact=existing.impact, + risk_score=score, + risk_level=level, + owner=None, + treatment=treatment if treatment is not None else existing.treatment, + treatment_plan=treatment_plan if treatment_plan is not None else existing.treatment_plan, + status=status if status is not None else existing.status, + soc2_controls=existing.soc2_controls, + related_finding=existing.related_finding, + created_date=datetime.now(UTC).isoformat(), # save_risk_items doesn't preserve this on REPLACE + last_reviewed=datetime.now(UTC).isoformat(), + review_notes=review_notes if review_notes is not None else None, + ) + try: + self._db.save_risk_items([item], account_id=account_id) + except Exception as e: + return ActionResult(success=False, message=f"Failed to update risk: {e}") + return ActionResult(success=True, message=f"Updated risk {risk_id}", record_id=risk_id) diff --git a/src/shasta/voice/tools/__init__.py b/src/shasta/voice/tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/shasta/voice/tools/controls.py b/src/shasta/voice/tools/controls.py new file mode 100644 index 0000000..e470949 --- /dev/null +++ b/src/shasta/voice/tools/controls.py @@ -0,0 +1,8 @@ +"""Tool functions for control summaries.""" +from typing import Any + +from shasta.voice.store import Store + + +def get_control_summary(*, store: Store, framework: str, control_id: str | None = None) -> list[dict[str, Any]]: + return [c.model_dump(mode="json") for c in store.get_control_summary(framework, control_id=control_id)] # type: ignore[arg-type] diff --git a/src/shasta/voice/tools/findings.py b/src/shasta/voice/tools/findings.py new file mode 100644 index 0000000..279cf4c --- /dev/null +++ b/src/shasta/voice/tools/findings.py @@ -0,0 +1,37 @@ +"""Tool functions for finding queries.""" +from typing import Any + +from shasta.voice.store import Store + + +def list_findings( + *, + store: Store, + severity: str | None = None, + status: str | None = None, + domain: str | None = None, + cloud: str | None = None, + framework: str | None = None, + control_id: str | None = None, + limit: int | None = None, +) -> list[dict[str, Any]]: + items = store.list_findings( + severity=severity, status=status, domain=domain, cloud=cloud, + framework=framework, control_id=control_id, limit=limit, + ) + return [i.model_dump(mode="json") for i in items] + + +def get_finding(*, store: Store, finding_id: str) -> dict[str, Any]: + detail = store.get_finding(finding_id) + if detail is None: + return {"error": "finding_not_found", "finding_id": finding_id} + return detail.model_dump(mode="json") + + +def list_top_blockers(*, store: Store, limit: int = 5) -> list[dict[str, Any]]: + return [i.model_dump(mode="json") for i in store.list_top_blockers(limit=limit)] + + +def get_resource_findings(*, store: Store, resource_id: str) -> list[dict[str, Any]]: + return [i.model_dump(mode="json") for i in store.get_resource_findings(resource_id)] diff --git a/src/shasta/voice/tools/risks.py b/src/shasta/voice/tools/risks.py new file mode 100644 index 0000000..6e06879 --- /dev/null +++ b/src/shasta/voice/tools/risks.py @@ -0,0 +1,51 @@ +"""Tool functions for risk-register operations.""" +from typing import Any + +from shasta.voice.store import Store + + +def list_risk_items(*, store: Store, account_id: str, status: str | None = None, level: str | None = None) -> list[dict[str, Any]]: + return [r.model_dump(mode="json") for r in store.list_risk_items(account_id, status=status, level=level)] + + +def get_risk_item(*, store: Store, risk_id: str, account_id: str = "123456789012") -> dict[str, Any]: + r = store.get_risk_item(risk_id, account_id=account_id) + if r is None: + return {"error": "risk_not_found", "risk_id": risk_id} + return r.model_dump(mode="json") + + +def add_risk_item( + *, + store: Store, + account_id: str, + title: str, + description: str, + category: str, + likelihood: str, + impact: str, + treatment: str, + treatment_plan: str | None = None, + related_finding: str | None = None, +) -> dict[str, Any]: + return store.add_risk_item( + account_id=account_id, title=title, description=description, category=category, + likelihood=likelihood, impact=impact, treatment=treatment, + treatment_plan=treatment_plan, related_finding=related_finding, + ).model_dump(mode="json") + + +def update_risk( + *, + store: Store, + risk_id: str, + treatment: str | None = None, + treatment_plan: str | None = None, + status: str | None = None, + review_notes: str | None = None, + account_id: str = "123456789012", +) -> dict[str, Any]: + return store.update_risk( + risk_id=risk_id, treatment=treatment, treatment_plan=treatment_plan, + status=status, review_notes=review_notes, account_id=account_id, + ).model_dump(mode="json") diff --git a/src/shasta/voice/tools/router.py b/src/shasta/voice/tools/router.py new file mode 100644 index 0000000..6dd6762 --- /dev/null +++ b/src/shasta/voice/tools/router.py @@ -0,0 +1,192 @@ +"""HTTP endpoints for tool calls. Browser relays OpenAI tool calls here.""" +import time +from typing import Literal + +from fastapi import APIRouter, Request +from pydantic import BaseModel + +from shasta.voice.observability import log_tool_call +from shasta.voice.tools import controls as controls_tool +from shasta.voice.tools import findings as findings_tool +from shasta.voice.tools import risks as risks_tool +from shasta.voice.tools import scans as scans_tool +from shasta.voice.tools import scores as scores_tool + +router = APIRouter(prefix="/tools") + + +def _store(request: Request): + return request.app.state.store + + +def _timed(tool_name: str, args: dict, fn): + start = time.perf_counter() + result = fn() + latency_ms = (time.perf_counter() - start) * 1000 + size = len(result) if isinstance(result, list) else 1 + log_tool_call(tool_name=tool_name, args=args, latency_ms=latency_ms, result_size=size) + return result + + +# ---------- request models ---------- + +Severity = Literal["critical", "high", "medium", "low", "info"] +Status = Literal["pass", "fail", "partial", "not_assessed", "not_applicable"] +Cloud = Literal["aws", "azure"] +Framework = Literal["soc2", "iso27001", "hipaa", "iso42001", "eu_ai_act", "ai_governance"] +ScoringFramework = Literal["soc2", "iso27001", "hipaa"] +RiskStatus = Literal["open", "in_progress", "accepted", "resolved"] +RiskLevel = Literal["high", "medium", "low"] +Treatment = Literal["mitigate", "accept", "transfer", "avoid"] + + +class ListFindingsReq(BaseModel): + severity: Severity | None = None + status: Status | None = None + domain: str | None = None + cloud: Cloud | None = None + framework: Framework | None = None + control_id: str | None = None + limit: int | None = None + + +class IdReq(BaseModel): + finding_id: str | None = None + risk_id: str | None = None + resource_id: str | None = None + + +class GetComplianceScoreReq(BaseModel): + framework: Framework + + +class GetScoreTrendReq(BaseModel): + framework: ScoringFramework + limit: int = 10 + + +class GetControlSummaryReq(BaseModel): + framework: ScoringFramework + control_id: str | None = None + + +class LimitReq(BaseModel): + limit: int = 5 + + +class ListRisksReq(BaseModel): + account_id: str + status: RiskStatus | None = None + level: RiskLevel | None = None + + +class GetRiskReq(BaseModel): + risk_id: str + account_id: str = "123456789012" + + +class AddRiskReq(BaseModel): + account_id: str + title: str + description: str + category: str + likelihood: RiskLevel + impact: RiskLevel + treatment: Treatment + treatment_plan: str | None = None + related_finding: str | None = None + + +class UpdateRiskReq(BaseModel): + risk_id: str + account_id: str = "123456789012" + treatment: Treatment | None = None + treatment_plan: str | None = None + status: RiskStatus | None = None + review_notes: str | None = None + + +# ---------- endpoints ---------- + +@router.post("/list_findings") +def list_findings(req: ListFindingsReq, request: Request): + return _timed("list_findings", req.model_dump(exclude_none=True), + lambda: findings_tool.list_findings(store=_store(request), **req.model_dump(exclude_none=True))) + + +@router.post("/get_finding") +def get_finding(req: IdReq, request: Request): + return _timed("get_finding", {"finding_id": req.finding_id}, + lambda: findings_tool.get_finding(store=_store(request), finding_id=req.finding_id or "")) + + +@router.post("/list_top_blockers") +def list_top_blockers(req: LimitReq, request: Request): + return _timed("list_top_blockers", {"limit": req.limit}, + lambda: findings_tool.list_top_blockers(store=_store(request), limit=req.limit)) + + +@router.post("/get_resource_findings") +def get_resource_findings(req: IdReq, request: Request): + return _timed("get_resource_findings", {"resource_id": req.resource_id}, + lambda: findings_tool.get_resource_findings(store=_store(request), resource_id=req.resource_id or "")) + + +@router.post("/get_compliance_score") +def get_compliance_score(req: GetComplianceScoreReq, request: Request): + return _timed("get_compliance_score", req.model_dump(), + lambda: scores_tool.get_compliance_score(store=_store(request), framework=req.framework)) + + +@router.post("/get_multi_framework_score") +def get_multi_framework_score(request: Request): + return _timed("get_multi_framework_score", {}, + lambda: scores_tool.get_multi_framework_score(store=_store(request))) + + +@router.post("/get_score_trend") +def get_score_trend(req: GetScoreTrendReq, request: Request): + return _timed("get_score_trend", req.model_dump(), + lambda: scores_tool.get_score_trend(store=_store(request), framework=req.framework, limit=req.limit)) + + +@router.post("/get_control_summary") +def get_control_summary(req: GetControlSummaryReq, request: Request): + return _timed("get_control_summary", req.model_dump(exclude_none=True), + lambda: controls_tool.get_control_summary(store=_store(request), framework=req.framework, control_id=req.control_id)) + + +@router.post("/list_scans") +def list_scans(req: LimitReq, request: Request): + return _timed("list_scans", {"limit": req.limit}, + lambda: scans_tool.list_scans(store=_store(request), limit=req.limit)) + + +@router.post("/get_latest_scan") +def get_latest_scan(request: Request): + return _timed("get_latest_scan", {}, + lambda: scans_tool.get_latest_scan(store=_store(request))) + + +@router.post("/list_risk_items") +def list_risk_items(req: ListRisksReq, request: Request): + return _timed("list_risk_items", req.model_dump(exclude_none=True), + lambda: risks_tool.list_risk_items(store=_store(request), account_id=req.account_id, status=req.status, level=req.level)) + + +@router.post("/get_risk_item") +def get_risk_item(req: GetRiskReq, request: Request): + return _timed("get_risk_item", req.model_dump(), + lambda: risks_tool.get_risk_item(store=_store(request), risk_id=req.risk_id, account_id=req.account_id)) + + +@router.post("/add_risk_item") +def add_risk_item(req: AddRiskReq, request: Request): + return _timed("add_risk_item", req.model_dump(exclude_none=True), + lambda: risks_tool.add_risk_item(store=_store(request), **req.model_dump(exclude_none=True))) + + +@router.post("/update_risk") +def update_risk(req: UpdateRiskReq, request: Request): + return _timed("update_risk", req.model_dump(exclude_none=True), + lambda: risks_tool.update_risk(store=_store(request), **req.model_dump(exclude_none=True))) diff --git a/src/shasta/voice/tools/scans.py b/src/shasta/voice/tools/scans.py new file mode 100644 index 0000000..2b6d783 --- /dev/null +++ b/src/shasta/voice/tools/scans.py @@ -0,0 +1,15 @@ +"""Tool functions for scan queries.""" +from typing import Any + +from shasta.voice.store import Store + + +def list_scans(*, store: Store, limit: int = 10) -> list[dict[str, Any]]: + return [s.model_dump(mode="json") for s in store.list_scans(limit=limit)] + + +def get_latest_scan(*, store: Store) -> dict[str, Any]: + s = store.get_latest_scan() + if s is None: + return {"error": "no_scan_data"} + return s.model_dump(mode="json") diff --git a/src/shasta/voice/tools/scores.py b/src/shasta/voice/tools/scores.py new file mode 100644 index 0000000..6614d9f --- /dev/null +++ b/src/shasta/voice/tools/scores.py @@ -0,0 +1,25 @@ +"""Tool functions for compliance scores.""" +from typing import Any + +from shasta.voice.store import Store + +_VALID_FRAMEWORKS = {"soc2", "iso27001", "hipaa", "iso42001", "eu_ai_act", "ai_governance"} + + +def get_compliance_score(*, store: Store, framework: str) -> dict[str, Any]: + if framework not in _VALID_FRAMEWORKS: + return {"error": "invalid_framework", "framework": framework, "valid": sorted(_VALID_FRAMEWORKS)} + score = store.get_compliance_score(framework) # type: ignore[arg-type] + if score is None: + return {"error": "framework_not_applicable", "framework": framework, "reason": "no_findings_or_scorer_unavailable"} + return score.model_dump(mode="json") + + +def get_multi_framework_score(*, store: Store) -> dict[str, Any]: + return store.get_multi_framework_score().model_dump(mode="json") + + +def get_score_trend(*, store: Store, framework: str, limit: int = 10) -> dict[str, Any]: + if framework not in _VALID_FRAMEWORKS: + return {"error": "invalid_framework", "framework": framework} + return store.get_score_trend(framework, limit=limit).model_dump(mode="json") # type: ignore[arg-type] diff --git a/src/shasta/voice/web/dist/assets/index-D_PzcLTw.css b/src/shasta/voice/web/dist/assets/index-D_PzcLTw.css new file mode 100644 index 0000000..8fd03d2 --- /dev/null +++ b/src/shasta/voice/web/dist/assets/index-D_PzcLTw.css @@ -0,0 +1 @@ +:root{--bg-base: #231F20;--bg-surface: #2D2829;--bg-surface-raised: #3A3537;--text-primary: #FFFFFF;--text-muted: rgba(255, 255, 255, .6);--text-subtle: rgba(255, 255, 255, .4);--brand-purple: #58298F;--brand-magenta: #B61A3F;--brand-yellow: #FCE205;--severity-critical: #B61A3F;--severity-high: #911961;--severity-medium: #731E7A;--severity-low: #58298F;--severity-critical-bg: rgba(182, 26, 63, .16);--severity-high-bg: rgba(145, 25, 97, .16);--severity-medium-bg: rgba(115, 30, 122, .16);--severity-low-bg: rgba(88, 41, 143, .16);--brand-gradient: linear-gradient( 90deg, #58298F 0%, #731E7A 25%, #911961 50%, #A01855 75%, #B61A3F 100% );--border-subtle: rgba(255, 255, 255, .08);--border-card: rgba(255, 255, 255, .12);--font-sans: "Roboto", "Arial", "Trebuchet MS", sans-serif;--fs-body: 15px;--fs-heading: 18px;--fs-title: 24px;--fs-small: 13px;--radius-sm: 6px;--radius-md: 12px;--radius-lg: 16px;--space-1: 4px;--space-2: 8px;--space-3: 12px;--space-4: 16px;--space-5: 24px;--space-6: 32px}*,*:before,*:after{box-sizing:border-box}html,body,#root{margin:0;padding:0;height:100%;background:var(--bg-base);color:var(--text-primary);font-family:var(--font-sans);font-size:var(--fs-body);font-weight:400;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}button{font-family:inherit;font-size:inherit;color:inherit;background:transparent;border:none;cursor:pointer}button:focus-visible{outline:2px solid var(--brand-yellow);outline-offset:2px}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:var(--bg-base)}::-webkit-scrollbar-thumb{background:var(--border-card);border-radius:4px} diff --git a/src/shasta/voice/web/dist/assets/index-idTpsr93.js b/src/shasta/voice/web/dist/assets/index-idTpsr93.js new file mode 100644 index 0000000..e4dd40b --- /dev/null +++ b/src/shasta/voice/web/dist/assets/index-idTpsr93.js @@ -0,0 +1,53 @@ +(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))r(i);new MutationObserver(i=>{for(const s of i)if(s.type==="childList")for(const o of s.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&r(o)}).observe(document,{childList:!0,subtree:!0});function n(i){const s={};return i.integrity&&(s.integrity=i.integrity),i.referrerPolicy&&(s.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?s.credentials="include":i.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function r(i){if(i.ep)return;i.ep=!0;const s=n(i);fetch(i.href,s)}})();function jm(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var Wf={exports:{}},ws={},Hf={exports:{}},O={};/** + * @license React + * react.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Zr=Symbol.for("react.element"),Lm=Symbol.for("react.portal"),Vm=Symbol.for("react.fragment"),Om=Symbol.for("react.strict_mode"),Nm=Symbol.for("react.profiler"),Im=Symbol.for("react.provider"),zm=Symbol.for("react.context"),Fm=Symbol.for("react.forward_ref"),Bm=Symbol.for("react.suspense"),Um=Symbol.for("react.memo"),$m=Symbol.for("react.lazy"),hu=Symbol.iterator;function Wm(e){return e===null||typeof e!="object"?null:(e=hu&&e[hu]||e["@@iterator"],typeof e=="function"?e:null)}var Kf={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},Gf=Object.assign,Qf={};function Gn(e,t,n){this.props=e,this.context=t,this.refs=Qf,this.updater=n||Kf}Gn.prototype.isReactComponent={};Gn.prototype.setState=function(e,t){if(typeof e!="object"&&typeof e!="function"&&e!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")};Gn.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")};function Yf(){}Yf.prototype=Gn.prototype;function Fl(e,t,n){this.props=e,this.context=t,this.refs=Qf,this.updater=n||Kf}var Bl=Fl.prototype=new Yf;Bl.constructor=Fl;Gf(Bl,Gn.prototype);Bl.isPureReactComponent=!0;var mu=Array.isArray,Xf=Object.prototype.hasOwnProperty,Ul={current:null},Zf={key:!0,ref:!0,__self:!0,__source:!0};function bf(e,t,n){var r,i={},s=null,o=null;if(t!=null)for(r in t.ref!==void 0&&(o=t.ref),t.key!==void 0&&(s=""+t.key),t)Xf.call(t,r)&&!Zf.hasOwnProperty(r)&&(i[r]=t[r]);var l=arguments.length-2;if(l===1)i.children=n;else if(1>>1,re=_[X];if(0>>1;Xi(Ws,L))Hti(li,Ws)?(_[X]=li,_[Ht]=L,X=Ht):(_[X]=Ws,_[Wt]=L,X=Wt);else if(Hti(li,L))_[X]=li,_[Ht]=L,X=Ht;else break e}}return j}function i(_,j){var L=_.sortIndex-j.sortIndex;return L!==0?L:_.id-j.id}if(typeof performance=="object"&&typeof performance.now=="function"){var s=performance;e.unstable_now=function(){return s.now()}}else{var o=Date,l=o.now();e.unstable_now=function(){return o.now()-l}}var a=[],u=[],c=1,f=null,d=3,g=!1,v=!1,y=!1,k=typeof setTimeout=="function"?setTimeout:null,p=typeof clearTimeout=="function"?clearTimeout:null,h=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function m(_){for(var j=n(u);j!==null;){if(j.callback===null)r(u);else if(j.startTime<=_)r(u),j.sortIndex=j.expirationTime,t(a,j);else break;j=n(u)}}function S(_){if(y=!1,m(_),!v)if(n(a)!==null)v=!0,si(w);else{var j=n(u);j!==null&&q(S,j.startTime-_)}}function w(_,j){v=!1,y&&(y=!1,p(C),C=-1),g=!0;var L=d;try{for(m(j),f=n(a);f!==null&&(!(f.expirationTime>j)||_&&!ne());){var X=f.callback;if(typeof X=="function"){f.callback=null,d=f.priorityLevel;var re=X(f.expirationTime<=j);j=e.unstable_now(),typeof re=="function"?f.callback=re:f===n(a)&&r(a),m(j)}else r(a);f=n(a)}if(f!==null)var oi=!0;else{var Wt=n(u);Wt!==null&&q(S,Wt.startTime-j),oi=!1}return oi}finally{f=null,d=L,g=!1}}var T=!1,E=null,C=-1,V=5,M=-1;function ne(){return!(e.unstable_now()-M_||125<_?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):V=0<_?Math.floor(1e3/_):5},e.unstable_getCurrentPriorityLevel=function(){return d},e.unstable_getFirstCallbackNode=function(){return n(a)},e.unstable_next=function(_){switch(d){case 1:case 2:case 3:var j=3;break;default:j=d}var L=d;d=j;try{return _()}finally{d=L}},e.unstable_pauseExecution=function(){},e.unstable_requestPaint=function(){},e.unstable_runWithPriority=function(_,j){switch(_){case 1:case 2:case 3:case 4:case 5:break;default:_=3}var L=d;d=_;try{return j()}finally{d=L}},e.unstable_scheduleCallback=function(_,j,L){var X=e.unstable_now();switch(typeof L=="object"&&L!==null?(L=L.delay,L=typeof L=="number"&&0X?(_.sortIndex=L,t(u,_),n(a)===null&&_===n(u)&&(y?(p(C),C=-1):y=!0,q(S,L-X))):(_.sortIndex=re,t(a,_),v||g||(v=!0,si(w))),_},e.unstable_shouldYield=ne,e.unstable_wrapCallback=function(_){var j=d;return function(){var L=d;d=j;try{return _.apply(this,arguments)}finally{d=L}}}})(nd);td.exports=nd;var eg=td.exports;/** + * @license React + * react-dom.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var tg=A,De=eg;function P(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Mo=Object.prototype.hasOwnProperty,ng=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,vu={},yu={};function rg(e){return Mo.call(yu,e)?!0:Mo.call(vu,e)?!1:ng.test(e)?yu[e]=!0:(vu[e]=!0,!1)}function ig(e,t,n,r){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return r?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function sg(e,t,n,r){if(t===null||typeof t>"u"||ig(e,t,n,r))return!0;if(r)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function xe(e,t,n,r,i,s,o){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=r,this.attributeNamespace=i,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=s,this.removeEmptyString=o}var ue={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){ue[e]=new xe(e,0,!1,e,null,!1,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];ue[t]=new xe(t,1,!1,e[1],null,!1,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(e){ue[e]=new xe(e,2,!1,e.toLowerCase(),null,!1,!1)});["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){ue[e]=new xe(e,2,!1,e,null,!1,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){ue[e]=new xe(e,3,!1,e.toLowerCase(),null,!1,!1)});["checked","multiple","muted","selected"].forEach(function(e){ue[e]=new xe(e,3,!0,e,null,!1,!1)});["capture","download"].forEach(function(e){ue[e]=new xe(e,4,!1,e,null,!1,!1)});["cols","rows","size","span"].forEach(function(e){ue[e]=new xe(e,6,!1,e,null,!1,!1)});["rowSpan","start"].forEach(function(e){ue[e]=new xe(e,5,!1,e.toLowerCase(),null,!1,!1)});var Wl=/[\-:]([a-z])/g;function Hl(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(Wl,Hl);ue[t]=new xe(t,1,!1,e,null,!1,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(Wl,Hl);ue[t]=new xe(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)});["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(Wl,Hl);ue[t]=new xe(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)});["tabIndex","crossOrigin"].forEach(function(e){ue[e]=new xe(e,1,!1,e.toLowerCase(),null,!1,!1)});ue.xlinkHref=new xe("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1);["src","href","action","formAction"].forEach(function(e){ue[e]=new xe(e,1,!1,e.toLowerCase(),null,!0,!0)});function Kl(e,t,n,r){var i=ue.hasOwnProperty(t)?ue[t]:null;(i!==null?i.type!==0:r||!(2l||i[o]!==s[l]){var a=` +`+i[o].replace(" at new "," at ");return e.displayName&&a.includes("")&&(a=a.replace("",e.displayName)),a}while(1<=o&&0<=l);break}}}finally{Gs=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?ar(e):""}function og(e){switch(e.tag){case 5:return ar(e.type);case 16:return ar("Lazy");case 13:return ar("Suspense");case 19:return ar("SuspenseList");case 0:case 2:case 15:return e=Qs(e.type,!1),e;case 11:return e=Qs(e.type.render,!1),e;case 1:return e=Qs(e.type,!0),e;default:return""}}function Oo(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case hn:return"Fragment";case pn:return"Portal";case jo:return"Profiler";case Gl:return"StrictMode";case Lo:return"Suspense";case Vo:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case sd:return(e.displayName||"Context")+".Consumer";case id:return(e._context.displayName||"Context")+".Provider";case Ql:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case Yl:return t=e.displayName||null,t!==null?t:Oo(e.type)||"Memo";case xt:t=e._payload,e=e._init;try{return Oo(e(t))}catch{}}return null}function lg(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return Oo(t);case 8:return t===Gl?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function Lt(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function ld(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function ag(e){var t=ld(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var i=n.get,s=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return i.call(this)},set:function(o){r=""+o,s.call(this,o)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(o){r=""+o},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function ci(e){e._valueTracker||(e._valueTracker=ag(e))}function ad(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=ld(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function Hi(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function No(e,t){var n=t.checked;return G({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function Su(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=Lt(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function ud(e,t){t=t.checked,t!=null&&Kl(e,"checked",t,!1)}function Io(e,t){ud(e,t);var n=Lt(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?zo(e,t.type,n):t.hasOwnProperty("defaultValue")&&zo(e,t.type,Lt(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function wu(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function zo(e,t,n){(t!=="number"||Hi(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var ur=Array.isArray;function Dn(e,t,n,r){if(e=e.options,t){t={};for(var i=0;i"+t.valueOf().toString()+"",t=fi.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function Rr(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var mr={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},ug=["Webkit","ms","Moz","O"];Object.keys(mr).forEach(function(e){ug.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),mr[t]=mr[e]})});function pd(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||mr.hasOwnProperty(e)&&mr[e]?(""+t).trim():t+"px"}function hd(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var r=n.indexOf("--")===0,i=pd(n,t[n],r);n==="float"&&(n="cssFloat"),r?e.setProperty(n,i):e[n]=i}}var cg=G({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function Uo(e,t){if(t){if(cg[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(P(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(P(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(P(61))}if(t.style!=null&&typeof t.style!="object")throw Error(P(62))}}function $o(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var Wo=null;function Xl(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var Ho=null,Mn=null,jn=null;function Cu(e){if(e=qr(e)){if(typeof Ho!="function")throw Error(P(280));var t=e.stateNode;t&&(t=Es(t),Ho(e.stateNode,e.type,t))}}function md(e){Mn?jn?jn.push(e):jn=[e]:Mn=e}function gd(){if(Mn){var e=Mn,t=jn;if(jn=Mn=null,Cu(e),t)for(e=0;e>>=0,e===0?32:31-(wg(e)/kg|0)|0}var di=64,pi=4194304;function cr(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function Yi(e,t){var n=e.pendingLanes;if(n===0)return 0;var r=0,i=e.suspendedLanes,s=e.pingedLanes,o=n&268435455;if(o!==0){var l=o&~i;l!==0?r=cr(l):(s&=o,s!==0&&(r=cr(s)))}else o=n&~i,o!==0?r=cr(o):s!==0&&(r=cr(s));if(r===0)return 0;if(t!==0&&t!==r&&!(t&i)&&(i=r&-r,s=t&-t,i>=s||i===16&&(s&4194240)!==0))return t;if(r&4&&(r|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=r;0n;n++)t.push(e);return t}function br(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-Qe(t),e[t]=n}function Eg(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0=vr),Lu=" ",Vu=!1;function Nd(e,t){switch(e){case"keyup":return ev.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Id(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var mn=!1;function nv(e,t){switch(e){case"compositionend":return Id(t);case"keypress":return t.which!==32?null:(Vu=!0,Lu);case"textInput":return e=t.data,e===Lu&&Vu?null:e;default:return null}}function rv(e,t){if(mn)return e==="compositionend"||!ra&&Nd(e,t)?(e=Vd(),ji=ea=Tt=null,mn=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=zu(n)}}function Ud(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Ud(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function $d(){for(var e=window,t=Hi();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=Hi(e.document)}return t}function ia(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}function dv(e){var t=$d(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&Ud(n.ownerDocument.documentElement,n)){if(r!==null&&ia(n)){if(t=r.start,e=r.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var i=n.textContent.length,s=Math.min(r.start,i);r=r.end===void 0?s:Math.min(r.end,i),!e.extend&&s>r&&(i=r,r=s,s=i),i=Fu(n,s);var o=Fu(n,r);i&&o&&(e.rangeCount!==1||e.anchorNode!==i.node||e.anchorOffset!==i.offset||e.focusNode!==o.node||e.focusOffset!==o.offset)&&(t=t.createRange(),t.setStart(i.node,i.offset),e.removeAllRanges(),s>r?(e.addRange(t),e.extend(o.node,o.offset)):(t.setEnd(o.node,o.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,gn=null,Zo=null,xr=null,bo=!1;function Bu(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;bo||gn==null||gn!==Hi(r)||(r=gn,"selectionStart"in r&&ia(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),xr&&Or(xr,r)||(xr=r,r=bi(Zo,"onSelect"),0xn||(e.current=rl[xn],rl[xn]=null,xn--)}function z(e,t){xn++,rl[xn]=e.current,e.current=t}var Vt={},me=zt(Vt),ke=zt(!1),rn=Vt;function In(e,t){var n=e.type.contextTypes;if(!n)return Vt;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var i={},s;for(s in n)i[s]=t[s];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=i),i}function Te(e){return e=e.childContextTypes,e!=null}function qi(){B(ke),B(me)}function Qu(e,t,n){if(me.current!==Vt)throw Error(P(168));z(me,t),z(ke,n)}function bd(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var i in r)if(!(i in t))throw Error(P(108,lg(e)||"Unknown",i));return G({},n,r)}function es(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||Vt,rn=me.current,z(me,e),z(ke,ke.current),!0}function Yu(e,t,n){var r=e.stateNode;if(!r)throw Error(P(169));n?(e=bd(e,t,rn),r.__reactInternalMemoizedMergedChildContext=e,B(ke),B(me),z(me,e)):B(ke),z(ke,n)}var it=null,_s=!1,lo=!1;function Jd(e){it===null?it=[e]:it.push(e)}function Cv(e){_s=!0,Jd(e)}function Ft(){if(!lo&&it!==null){lo=!0;var e=0,t=I;try{var n=it;for(I=1;e>=o,i-=o,st=1<<32-Qe(t)+i|n<C?(V=E,E=null):V=E.sibling;var M=d(p,E,m[C],S);if(M===null){E===null&&(E=V);break}e&&E&&M.alternate===null&&t(p,E),h=s(M,h,C),T===null?w=M:T.sibling=M,T=M,E=V}if(C===m.length)return n(p,E),$&&Gt(p,C),w;if(E===null){for(;CC?(V=E,E=null):V=E.sibling;var ne=d(p,E,M.value,S);if(ne===null){E===null&&(E=V);break}e&&E&&ne.alternate===null&&t(p,E),h=s(ne,h,C),T===null?w=ne:T.sibling=ne,T=ne,E=V}if(M.done)return n(p,E),$&&Gt(p,C),w;if(E===null){for(;!M.done;C++,M=m.next())M=f(p,M.value,S),M!==null&&(h=s(M,h,C),T===null?w=M:T.sibling=M,T=M);return $&&Gt(p,C),w}for(E=r(p,E);!M.done;C++,M=m.next())M=g(E,p,C,M.value,S),M!==null&&(e&&M.alternate!==null&&E.delete(M.key===null?C:M.key),h=s(M,h,C),T===null?w=M:T.sibling=M,T=M);return e&&E.forEach(function(gt){return t(p,gt)}),$&&Gt(p,C),w}function k(p,h,m,S){if(typeof m=="object"&&m!==null&&m.type===hn&&m.key===null&&(m=m.props.children),typeof m=="object"&&m!==null){switch(m.$$typeof){case ui:e:{for(var w=m.key,T=h;T!==null;){if(T.key===w){if(w=m.type,w===hn){if(T.tag===7){n(p,T.sibling),h=i(T,m.props.children),h.return=p,p=h;break e}}else if(T.elementType===w||typeof w=="object"&&w!==null&&w.$$typeof===xt&&bu(w)===T.type){n(p,T.sibling),h=i(T,m.props),h.ref=rr(p,T,m),h.return=p,p=h;break e}n(p,T);break}else t(p,T);T=T.sibling}m.type===hn?(h=tn(m.props.children,p.mode,S,m.key),h.return=p,p=h):(S=Bi(m.type,m.key,m.props,null,p.mode,S),S.ref=rr(p,h,m),S.return=p,p=S)}return o(p);case pn:e:{for(T=m.key;h!==null;){if(h.key===T)if(h.tag===4&&h.stateNode.containerInfo===m.containerInfo&&h.stateNode.implementation===m.implementation){n(p,h.sibling),h=i(h,m.children||[]),h.return=p,p=h;break e}else{n(p,h);break}else t(p,h);h=h.sibling}h=go(m,p.mode,S),h.return=p,p=h}return o(p);case xt:return T=m._init,k(p,h,T(m._payload),S)}if(ur(m))return v(p,h,m,S);if(Jn(m))return y(p,h,m,S);Si(p,m)}return typeof m=="string"&&m!==""||typeof m=="number"?(m=""+m,h!==null&&h.tag===6?(n(p,h.sibling),h=i(h,m),h.return=p,p=h):(n(p,h),h=mo(m,p.mode,S),h.return=p,p=h),o(p)):n(p,h)}return k}var Fn=np(!0),rp=np(!1),rs=zt(null),is=null,kn=null,aa=null;function ua(){aa=kn=is=null}function ca(e){var t=rs.current;B(rs),e._currentValue=t}function ol(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function Vn(e,t){is=e,aa=kn=null,e=e.dependencies,e!==null&&e.firstContext!==null&&(e.lanes&t&&(we=!0),e.firstContext=null)}function Be(e){var t=e._currentValue;if(aa!==e)if(e={context:e,memoizedValue:t,next:null},kn===null){if(is===null)throw Error(P(308));kn=e,is.dependencies={lanes:0,firstContext:e}}else kn=kn.next=e;return t}var bt=null;function fa(e){bt===null?bt=[e]:bt.push(e)}function ip(e,t,n,r){var i=t.interleaved;return i===null?(n.next=n,fa(t)):(n.next=i.next,i.next=n),t.interleaved=n,dt(e,r)}function dt(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var St=!1;function da(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function sp(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function lt(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function Rt(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,N&2){var i=r.pending;return i===null?t.next=t:(t.next=i.next,i.next=t),r.pending=t,dt(e,n)}return i=r.interleaved,i===null?(t.next=t,fa(r)):(t.next=i.next,i.next=t),r.interleaved=t,dt(e,n)}function Vi(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,bl(e,n)}}function Ju(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var i=null,s=null;if(n=n.firstBaseUpdate,n!==null){do{var o={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};s===null?i=s=o:s=s.next=o,n=n.next}while(n!==null);s===null?i=s=t:s=s.next=t}else i=s=t;n={baseState:r.baseState,firstBaseUpdate:i,lastBaseUpdate:s,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function ss(e,t,n,r){var i=e.updateQueue;St=!1;var s=i.firstBaseUpdate,o=i.lastBaseUpdate,l=i.shared.pending;if(l!==null){i.shared.pending=null;var a=l,u=a.next;a.next=null,o===null?s=u:o.next=u,o=a;var c=e.alternate;c!==null&&(c=c.updateQueue,l=c.lastBaseUpdate,l!==o&&(l===null?c.firstBaseUpdate=u:l.next=u,c.lastBaseUpdate=a))}if(s!==null){var f=i.baseState;o=0,c=u=a=null,l=s;do{var d=l.lane,g=l.eventTime;if((r&d)===d){c!==null&&(c=c.next={eventTime:g,lane:0,tag:l.tag,payload:l.payload,callback:l.callback,next:null});e:{var v=e,y=l;switch(d=t,g=n,y.tag){case 1:if(v=y.payload,typeof v=="function"){f=v.call(g,f,d);break e}f=v;break e;case 3:v.flags=v.flags&-65537|128;case 0:if(v=y.payload,d=typeof v=="function"?v.call(g,f,d):v,d==null)break e;f=G({},f,d);break e;case 2:St=!0}}l.callback!==null&&l.lane!==0&&(e.flags|=64,d=i.effects,d===null?i.effects=[l]:d.push(l))}else g={eventTime:g,lane:d,tag:l.tag,payload:l.payload,callback:l.callback,next:null},c===null?(u=c=g,a=f):c=c.next=g,o|=d;if(l=l.next,l===null){if(l=i.shared.pending,l===null)break;d=l,l=d.next,d.next=null,i.lastBaseUpdate=d,i.shared.pending=null}}while(!0);if(c===null&&(a=f),i.baseState=a,i.firstBaseUpdate=u,i.lastBaseUpdate=c,t=i.shared.interleaved,t!==null){i=t;do o|=i.lane,i=i.next;while(i!==t)}else s===null&&(i.shared.lanes=0);ln|=o,e.lanes=o,e.memoizedState=f}}function qu(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var r=uo.transition;uo.transition={};try{e(!1),t()}finally{I=n,uo.transition=r}}function kp(){return Ue().memoizedState}function Av(e,t,n){var r=Mt(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},Tp(e))Cp(t,n);else if(n=ip(e,t,n,r),n!==null){var i=ve();Ye(n,e,r,i),Pp(n,t,r)}}function Rv(e,t,n){var r=Mt(e),i={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(Tp(e))Cp(t,i);else{var s=e.alternate;if(e.lanes===0&&(s===null||s.lanes===0)&&(s=t.lastRenderedReducer,s!==null))try{var o=t.lastRenderedState,l=s(o,n);if(i.hasEagerState=!0,i.eagerState=l,Xe(l,o)){var a=t.interleaved;a===null?(i.next=i,fa(t)):(i.next=a.next,a.next=i),t.interleaved=i;return}}catch{}finally{}n=ip(e,t,i,r),n!==null&&(i=ve(),Ye(n,e,r,i),Pp(n,t,r))}}function Tp(e){var t=e.alternate;return e===K||t!==null&&t===K}function Cp(e,t){Sr=ls=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Pp(e,t,n){if(n&4194240){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,bl(e,n)}}var as={readContext:Be,useCallback:ce,useContext:ce,useEffect:ce,useImperativeHandle:ce,useInsertionEffect:ce,useLayoutEffect:ce,useMemo:ce,useReducer:ce,useRef:ce,useState:ce,useDebugValue:ce,useDeferredValue:ce,useTransition:ce,useMutableSource:ce,useSyncExternalStore:ce,useId:ce,unstable_isNewReconciler:!1},Dv={readContext:Be,useCallback:function(e,t){return be().memoizedState=[e,t===void 0?null:t],e},useContext:Be,useEffect:tc,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,Ni(4194308,4,vp.bind(null,t,e),n)},useLayoutEffect:function(e,t){return Ni(4194308,4,e,t)},useInsertionEffect:function(e,t){return Ni(4,2,e,t)},useMemo:function(e,t){var n=be();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=be();return t=n!==void 0?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=Av.bind(null,K,e),[r.memoizedState,e]},useRef:function(e){var t=be();return e={current:e},t.memoizedState=e},useState:ec,useDebugValue:Sa,useDeferredValue:function(e){return be().memoizedState=e},useTransition:function(){var e=ec(!1),t=e[0];return e=_v.bind(null,e[1]),be().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=K,i=be();if($){if(n===void 0)throw Error(P(407));n=n()}else{if(n=t(),se===null)throw Error(P(349));on&30||up(r,t,n)}i.memoizedState=n;var s={value:n,getSnapshot:t};return i.queue=s,tc(fp.bind(null,r,s,e),[e]),r.flags|=2048,Wr(9,cp.bind(null,r,s,n,t),void 0,null),n},useId:function(){var e=be(),t=se.identifierPrefix;if($){var n=ot,r=st;n=(r&~(1<<32-Qe(r)-1)).toString(32)+n,t=":"+t+"R"+n,n=Ur++,0<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=o.createElement(n,{is:r.is}):(e=o.createElement(n),n==="select"&&(o=e,r.multiple?o.multiple=!0:r.size&&(o.size=r.size))):e=o.createElementNS(e,n),e[Je]=t,e[zr]=r,Op(e,t,!1,!1),t.stateNode=e;e:{switch(o=$o(n,r),n){case"dialog":F("cancel",e),F("close",e),i=r;break;case"iframe":case"object":case"embed":F("load",e),i=r;break;case"video":case"audio":for(i=0;i$n&&(t.flags|=128,r=!0,ir(s,!1),t.lanes=4194304)}else{if(!r)if(e=os(o),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),ir(s,!0),s.tail===null&&s.tailMode==="hidden"&&!o.alternate&&!$)return fe(t),null}else 2*b()-s.renderingStartTime>$n&&n!==1073741824&&(t.flags|=128,r=!0,ir(s,!1),t.lanes=4194304);s.isBackwards?(o.sibling=t.child,t.child=o):(n=s.last,n!==null?n.sibling=o:t.child=o,s.last=o)}return s.tail!==null?(t=s.tail,s.rendering=t,s.tail=t.sibling,s.renderingStartTime=b(),t.sibling=null,n=W.current,z(W,r?n&1|2:n&1),t):(fe(t),null);case 22:case 23:return Ea(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&t.mode&1?Pe&1073741824&&(fe(t),t.subtreeFlags&6&&(t.flags|=8192)):fe(t),null;case 24:return null;case 25:return null}throw Error(P(156,t.tag))}function zv(e,t){switch(oa(t),t.tag){case 1:return Te(t.type)&&qi(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Bn(),B(ke),B(me),ma(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 5:return ha(t),null;case 13:if(B(W),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(P(340));zn()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return B(W),null;case 4:return Bn(),null;case 10:return ca(t.type._context),null;case 22:case 23:return Ea(),null;case 24:return null;default:return null}}var ki=!1,pe=!1,Fv=typeof WeakSet=="function"?WeakSet:Set,R=null;function Tn(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){Y(e,t,r)}else n.current=null}function ml(e,t,n){try{n()}catch(r){Y(e,t,r)}}var dc=!1;function Bv(e,t){if(Jo=Xi,e=$d(),ia(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var i=r.anchorOffset,s=r.focusNode;r=r.focusOffset;try{n.nodeType,s.nodeType}catch{n=null;break e}var o=0,l=-1,a=-1,u=0,c=0,f=e,d=null;t:for(;;){for(var g;f!==n||i!==0&&f.nodeType!==3||(l=o+i),f!==s||r!==0&&f.nodeType!==3||(a=o+r),f.nodeType===3&&(o+=f.nodeValue.length),(g=f.firstChild)!==null;)d=f,f=g;for(;;){if(f===e)break t;if(d===n&&++u===i&&(l=o),d===s&&++c===r&&(a=o),(g=f.nextSibling)!==null)break;f=d,d=f.parentNode}f=g}n=l===-1||a===-1?null:{start:l,end:a}}else n=null}n=n||{start:0,end:0}}else n=null;for(qo={focusedElem:e,selectionRange:n},Xi=!1,R=t;R!==null;)if(t=R,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,R=e;else for(;R!==null;){t=R;try{var v=t.alternate;if(t.flags&1024)switch(t.tag){case 0:case 11:case 15:break;case 1:if(v!==null){var y=v.memoizedProps,k=v.memoizedState,p=t.stateNode,h=p.getSnapshotBeforeUpdate(t.elementType===t.type?y:He(t.type,y),k);p.__reactInternalSnapshotBeforeUpdate=h}break;case 3:var m=t.stateNode.containerInfo;m.nodeType===1?m.textContent="":m.nodeType===9&&m.documentElement&&m.removeChild(m.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(P(163))}}catch(S){Y(t,t.return,S)}if(e=t.sibling,e!==null){e.return=t.return,R=e;break}R=t.return}return v=dc,dc=!1,v}function wr(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var i=r=r.next;do{if((i.tag&e)===e){var s=i.destroy;i.destroy=void 0,s!==void 0&&ml(t,n,s)}i=i.next}while(i!==r)}}function Ds(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function gl(e){var t=e.ref;if(t!==null){var n=e.stateNode;switch(e.tag){case 5:e=n;break;default:e=n}typeof t=="function"?t(e):t.current=e}}function zp(e){var t=e.alternate;t!==null&&(e.alternate=null,zp(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[Je],delete t[zr],delete t[nl],delete t[kv],delete t[Tv])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function Fp(e){return e.tag===5||e.tag===3||e.tag===4}function pc(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||Fp(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function vl(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=Ji));else if(r!==4&&(e=e.child,e!==null))for(vl(e,t,n),e=e.sibling;e!==null;)vl(e,t,n),e=e.sibling}function yl(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(yl(e,t,n),e=e.sibling;e!==null;)yl(e,t,n),e=e.sibling}var oe=null,Ke=!1;function vt(e,t,n){for(n=n.child;n!==null;)Bp(e,t,n),n=n.sibling}function Bp(e,t,n){if(qe&&typeof qe.onCommitFiberUnmount=="function")try{qe.onCommitFiberUnmount(ks,n)}catch{}switch(n.tag){case 5:pe||Tn(n,t);case 6:var r=oe,i=Ke;oe=null,vt(e,t,n),oe=r,Ke=i,oe!==null&&(Ke?(e=oe,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):oe.removeChild(n.stateNode));break;case 18:oe!==null&&(Ke?(e=oe,n=n.stateNode,e.nodeType===8?oo(e.parentNode,n):e.nodeType===1&&oo(e,n),Lr(e)):oo(oe,n.stateNode));break;case 4:r=oe,i=Ke,oe=n.stateNode.containerInfo,Ke=!0,vt(e,t,n),oe=r,Ke=i;break;case 0:case 11:case 14:case 15:if(!pe&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){i=r=r.next;do{var s=i,o=s.destroy;s=s.tag,o!==void 0&&(s&2||s&4)&&ml(n,t,o),i=i.next}while(i!==r)}vt(e,t,n);break;case 1:if(!pe&&(Tn(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(l){Y(n,t,l)}vt(e,t,n);break;case 21:vt(e,t,n);break;case 22:n.mode&1?(pe=(r=pe)||n.memoizedState!==null,vt(e,t,n),pe=r):vt(e,t,n);break;default:vt(e,t,n)}}function hc(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new Fv),t.forEach(function(r){var i=Xv.bind(null,e,r);n.has(r)||(n.add(r),r.then(i,i))})}}function $e(e,t){var n=t.deletions;if(n!==null)for(var r=0;ri&&(i=o),r&=~s}if(r=i,r=b()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*$v(r/1960))-r,10e?16:e,Ct===null)var r=!1;else{if(e=Ct,Ct=null,fs=0,N&6)throw Error(P(331));var i=N;for(N|=4,R=e.current;R!==null;){var s=R,o=s.child;if(R.flags&16){var l=s.deletions;if(l!==null){for(var a=0;ab()-Ca?en(e,0):Ta|=n),Ce(e,t)}function Yp(e,t){t===0&&(e.mode&1?(t=pi,pi<<=1,!(pi&130023424)&&(pi=4194304)):t=1);var n=ve();e=dt(e,t),e!==null&&(br(e,t,n),Ce(e,n))}function Yv(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),Yp(e,n)}function Xv(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,i=e.memoizedState;i!==null&&(n=i.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(P(314))}r!==null&&r.delete(t),Yp(e,n)}var Xp;Xp=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||ke.current)we=!0;else{if(!(e.lanes&n)&&!(t.flags&128))return we=!1,Nv(e,t,n);we=!!(e.flags&131072)}else we=!1,$&&t.flags&1048576&&qd(t,ns,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;Ii(e,t),e=t.pendingProps;var i=In(t,me.current);Vn(t,n),i=va(null,t,r,e,i,n);var s=ya();return t.flags|=1,typeof i=="object"&&i!==null&&typeof i.render=="function"&&i.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,Te(r)?(s=!0,es(t)):s=!1,t.memoizedState=i.state!==null&&i.state!==void 0?i.state:null,da(t),i.updater=Rs,t.stateNode=i,i._reactInternals=t,al(t,r,e,n),t=fl(null,t,r,!0,s,n)):(t.tag=0,$&&s&&sa(t),ge(null,t,i,n),t=t.child),t;case 16:r=t.elementType;e:{switch(Ii(e,t),e=t.pendingProps,i=r._init,r=i(r._payload),t.type=r,i=t.tag=bv(r),e=He(r,e),i){case 0:t=cl(null,t,r,e,n);break e;case 1:t=uc(null,t,r,e,n);break e;case 11:t=lc(null,t,r,e,n);break e;case 14:t=ac(null,t,r,He(r.type,e),n);break e}throw Error(P(306,r,""))}return t;case 0:return r=t.type,i=t.pendingProps,i=t.elementType===r?i:He(r,i),cl(e,t,r,i,n);case 1:return r=t.type,i=t.pendingProps,i=t.elementType===r?i:He(r,i),uc(e,t,r,i,n);case 3:e:{if(jp(t),e===null)throw Error(P(387));r=t.pendingProps,s=t.memoizedState,i=s.element,sp(e,t),ss(t,r,null,n);var o=t.memoizedState;if(r=o.element,s.isDehydrated)if(s={element:r,isDehydrated:!1,cache:o.cache,pendingSuspenseBoundaries:o.pendingSuspenseBoundaries,transitions:o.transitions},t.updateQueue.baseState=s,t.memoizedState=s,t.flags&256){i=Un(Error(P(423)),t),t=cc(e,t,r,n,i);break e}else if(r!==i){i=Un(Error(P(424)),t),t=cc(e,t,r,n,i);break e}else for(_e=At(t.stateNode.containerInfo.firstChild),Ae=t,$=!0,Ge=null,n=rp(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(zn(),r===i){t=pt(e,t,n);break e}ge(e,t,r,n)}t=t.child}return t;case 5:return op(t),e===null&&sl(t),r=t.type,i=t.pendingProps,s=e!==null?e.memoizedProps:null,o=i.children,el(r,i)?o=null:s!==null&&el(r,s)&&(t.flags|=32),Mp(e,t),ge(e,t,o,n),t.child;case 6:return e===null&&sl(t),null;case 13:return Lp(e,t,n);case 4:return pa(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=Fn(t,null,r,n):ge(e,t,r,n),t.child;case 11:return r=t.type,i=t.pendingProps,i=t.elementType===r?i:He(r,i),lc(e,t,r,i,n);case 7:return ge(e,t,t.pendingProps,n),t.child;case 8:return ge(e,t,t.pendingProps.children,n),t.child;case 12:return ge(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,i=t.pendingProps,s=t.memoizedProps,o=i.value,z(rs,r._currentValue),r._currentValue=o,s!==null)if(Xe(s.value,o)){if(s.children===i.children&&!ke.current){t=pt(e,t,n);break e}}else for(s=t.child,s!==null&&(s.return=t);s!==null;){var l=s.dependencies;if(l!==null){o=s.child;for(var a=l.firstContext;a!==null;){if(a.context===r){if(s.tag===1){a=lt(-1,n&-n),a.tag=2;var u=s.updateQueue;if(u!==null){u=u.shared;var c=u.pending;c===null?a.next=a:(a.next=c.next,c.next=a),u.pending=a}}s.lanes|=n,a=s.alternate,a!==null&&(a.lanes|=n),ol(s.return,n,t),l.lanes|=n;break}a=a.next}}else if(s.tag===10)o=s.type===t.type?null:s.child;else if(s.tag===18){if(o=s.return,o===null)throw Error(P(341));o.lanes|=n,l=o.alternate,l!==null&&(l.lanes|=n),ol(o,n,t),o=s.sibling}else o=s.child;if(o!==null)o.return=s;else for(o=s;o!==null;){if(o===t){o=null;break}if(s=o.sibling,s!==null){s.return=o.return,o=s;break}o=o.return}s=o}ge(e,t,i.children,n),t=t.child}return t;case 9:return i=t.type,r=t.pendingProps.children,Vn(t,n),i=Be(i),r=r(i),t.flags|=1,ge(e,t,r,n),t.child;case 14:return r=t.type,i=He(r,t.pendingProps),i=He(r.type,i),ac(e,t,r,i,n);case 15:return Rp(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,i=t.pendingProps,i=t.elementType===r?i:He(r,i),Ii(e,t),t.tag=1,Te(r)?(e=!0,es(t)):e=!1,Vn(t,n),Ep(t,r,i),al(t,r,i,n),fl(null,t,r,!0,e,n);case 19:return Vp(e,t,n);case 22:return Dp(e,t,n)}throw Error(P(156,t.tag))};function Zp(e,t){return Td(e,t)}function Zv(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function ze(e,t,n,r){return new Zv(e,t,n,r)}function Aa(e){return e=e.prototype,!(!e||!e.isReactComponent)}function bv(e){if(typeof e=="function")return Aa(e)?1:0;if(e!=null){if(e=e.$$typeof,e===Ql)return 11;if(e===Yl)return 14}return 2}function jt(e,t){var n=e.alternate;return n===null?(n=ze(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function Bi(e,t,n,r,i,s){var o=2;if(r=e,typeof e=="function")Aa(e)&&(o=1);else if(typeof e=="string")o=5;else e:switch(e){case hn:return tn(n.children,i,s,t);case Gl:o=8,i|=8;break;case jo:return e=ze(12,n,t,i|2),e.elementType=jo,e.lanes=s,e;case Lo:return e=ze(13,n,t,i),e.elementType=Lo,e.lanes=s,e;case Vo:return e=ze(19,n,t,i),e.elementType=Vo,e.lanes=s,e;case od:return js(n,i,s,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case id:o=10;break e;case sd:o=9;break e;case Ql:o=11;break e;case Yl:o=14;break e;case xt:o=16,r=null;break e}throw Error(P(130,e==null?e:typeof e,""))}return t=ze(o,n,t,i),t.elementType=e,t.type=r,t.lanes=s,t}function tn(e,t,n,r){return e=ze(7,e,r,t),e.lanes=n,e}function js(e,t,n,r){return e=ze(22,e,r,t),e.elementType=od,e.lanes=n,e.stateNode={isHidden:!1},e}function mo(e,t,n){return e=ze(6,e,null,t),e.lanes=n,e}function go(e,t,n){return t=ze(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function Jv(e,t,n,r,i){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=Xs(0),this.expirationTimes=Xs(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=Xs(0),this.identifierPrefix=r,this.onRecoverableError=i,this.mutableSourceEagerHydrationData=null}function Ra(e,t,n,r,i,s,o,l,a){return e=new Jv(e,t,n,l,a),t===1?(t=1,s===!0&&(t|=8)):t=0,s=ze(3,null,null,t),e.current=s,s.stateNode=e,s.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},da(s),e}function qv(e,t,n){var r=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(eh)}catch(e){console.error(e)}}eh(),ed.exports=je;var iy=ed.exports,kc=iy;Do.createRoot=kc.createRoot,Do.hydrateRoot=kc.hydrateRoot;const Tc=e=>{let t;const n=new Set,r=(u,c)=>{const f=typeof u=="function"?u(t):u;if(!Object.is(f,t)){const d=t;t=c??(typeof f!="object"||f===null)?f:Object.assign({},t,f),n.forEach(g=>g(t,d))}},i=()=>t,l={setState:r,getState:i,getInitialState:()=>a,subscribe:u=>(n.add(u),()=>n.delete(u))},a=t=e(r,i,l);return l},sy=e=>e?Tc(e):Tc,oy=e=>e;function ly(e,t=oy){const n=lr.useSyncExternalStore(e.subscribe,lr.useCallback(()=>t(e.getState()),[e,t]),lr.useCallback(()=>t(e.getInitialState()),[e,t]));return lr.useDebugValue(n),n}const Cc=e=>{const t=sy(e),n=r=>ly(t,r);return Object.assign(n,t),n},ay=e=>e?Cc(e):Cc,Ee=ay(e=>({connection:"idle",errorMessage:null,transcript:[],activeCard:{kind:"none"},micMuted:!1,setConnection:t=>e({connection:t}),setError:t=>e({errorMessage:t,connection:t?"error":"idle"}),appendTranscript:t=>e(n=>({transcript:[...n.transcript,t]})),updatePartialTranscript:(t,n)=>e(r=>({transcript:r.transcript.map(i=>i.id===t?{...i,text:n,partial:!0}:i)})),finalizeTranscript:t=>e(n=>({transcript:n.transcript.map(r=>r.id===t?{...r,partial:!1}:r)})),setActiveCard:t=>e({activeCard:t}),toggleMicMute:()=>e(t=>({micMuted:!t.micMuted})),reset:()=>e({connection:"idle",errorMessage:null,transcript:[],activeCard:{kind:"none"},micMuted:!1})}));function uy(e){try{const t=JSON.parse(e);return typeof(t==null?void 0:t.type)=="string"?t:null}catch{return null}}function cy(e,t){return{type:"conversation.item.create",item:{type:"function_call_output",call_id:e,output:t}}}function fy(){return{type:"response.create"}}const dy="https://api.openai.com/v1/realtime";async function py(){const e=await fetch("/session/token",{method:"POST"});if(!e.ok){const t=await e.text();throw new Error(`Failed to mint session token: ${e.status} ${t}`)}return e.json()}async function hy(e){const t=await py(),n=await navigator.mediaDevices.getUserMedia({audio:{echoCancellation:!0,noiseSuppression:!0,autoGainControl:!0}}),r=n.getAudioTracks()[0],i=new RTCPeerConnection;i.addTrack(r,n);const s=document.createElement("audio");s.autoplay=!0,s.style.display="none",document.body.appendChild(s),i.ontrack=d=>{s.srcObject=d.streams[0]};const o=i.createDataChannel("oai-events");o.onmessage=d=>{const g=uy(d.data);g&&e.onEvent(g)},i.onconnectionstatechange=()=>e.onConnectionStateChange(i.connectionState);const l=await i.createOffer();await i.setLocalDescription(l);const a=await fetch(`${dy}?model=${encodeURIComponent(t.model)}`,{method:"POST",body:l.sdp??"",headers:{Authorization:`Bearer ${t.client_secret}`,"Content-Type":"application/sdp"}});if(!a.ok)throw i.close(),new Error(`OpenAI SDP exchange failed: ${a.status} ${await a.text()}`);const u=await a.text();return await i.setRemoteDescription({type:"answer",sdp:u}),{pc:i,dataChannel:o,audioElement:s,micTrack:r,send:d=>{o.readyState==="open"&&o.send(JSON.stringify(d))},close:()=>{r.stop(),o.close(),i.close(),s.srcObject=null,s.remove()}}}const my=new Set(["list_findings","get_finding","list_top_blockers","get_resource_findings","get_compliance_score","get_multi_framework_score","get_score_trend","get_control_summary","list_scans","get_latest_scan","list_risk_items","get_risk_item","add_risk_item","update_risk"]);async function gy(e,t){const n=performance.now();if(!my.has(e)){const i={error:"unknown_tool",tool:e};return{output:JSON.stringify(i),parsed:i,toolName:e,latencyMs:0}}let r;try{r=t?JSON.parse(t):{}}catch{const i={error:"invalid_arguments_json",raw:t};return{output:JSON.stringify(i),parsed:i,toolName:e,latencyMs:0}}try{const i=await fetch(`/tools/${e}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});if(!i.ok){const o={error:"tool_unavailable",status:i.status,detail:(await i.text()).slice(0,200)};return{output:JSON.stringify(o),parsed:o,toolName:e,latencyMs:performance.now()-n}}const s=await i.json();return{output:JSON.stringify(s),parsed:s,toolName:e,latencyMs:performance.now()-n}}catch(i){const s={error:"tool_unavailable",detail:i instanceof Error?i.message:String(i)};return{output:JSON.stringify(s),parsed:s,toolName:e,latencyMs:performance.now()-n}}}function vy(e,t){if(t&&typeof t=="object"&&"error"in t)return null;switch(e){case"list_findings":case"list_top_blockers":case"get_resource_findings":return Array.isArray(t)?{kind:"findings_list",data:t}:null;case"get_finding":return t&&typeof t=="object"&&"id"in t?{kind:"finding_detail",data:t}:null;case"get_compliance_score":return t&&typeof t=="object"&&"framework"in t?{kind:"compliance_score",data:t}:null;case"get_multi_framework_score":return t&&typeof t=="object"&&"frameworks"in t?{kind:"multi_framework",data:t}:null;case"get_control_summary":return Array.isArray(t)?{kind:"control_summary",data:t}:null;case"list_risk_items":return Array.isArray(t)?{kind:"risk_list",data:t}:null;case"get_risk_item":return t&&typeof t=="object"&&"risk_id"in t?{kind:"risk_detail",data:t}:null;case"add_risk_item":case"update_risk":return t&&typeof t=="object"&&"success"in t?{kind:"action",data:t}:null;default:return null}}function yy(){const e=Ee(n=>n.connection),t=e==="connected"||e==="listening"||e==="thinking"||e==="speaking"?"var(--brand-yellow)":e==="error"?"var(--severity-critical)":"var(--text-subtle)";return x.jsxs("header",{style:{display:"flex",alignItems:"center",justifyContent:"space-between",padding:"var(--space-4) var(--space-5)",borderBottom:"1px solid var(--border-subtle)",background:"var(--bg-base)"},children:[x.jsxs("div",{style:{display:"flex",alignItems:"baseline",gap:"var(--space-3)"},children:[x.jsx("span",{style:{fontSize:"var(--fs-title)",fontWeight:700,letterSpacing:"-0.01em"},children:"Shasta"}),x.jsx("span",{style:{fontSize:"var(--fs-small)",color:"var(--text-muted)"},children:"Voice Console"})]}),x.jsxs("div",{style:{display:"flex",alignItems:"center",gap:"var(--space-2)"},children:[x.jsx("div",{style:{width:8,height:8,borderRadius:"50%",background:t,transition:"background 200ms"}}),x.jsx("span",{style:{fontSize:"var(--fs-small)",color:"var(--text-muted)"},children:e})]})]})}function xy({onConnect:e,onDisconnect:t}){const n=Ee(a=>a.connection),r=Ee(a=>a.errorMessage),i=n==="connected"||n==="listening"||n==="thinking"||n==="speaking",s=n==="connecting",o=n==="listening"?"var(--brand-yellow)":n==="speaking"?"var(--brand-purple)":n==="thinking"?"var(--brand-magenta)":"var(--border-card)",l=i?"Tap to end":s?"Connecting…":n==="error"?"Reconnect":"Tap to start";return x.jsxs("div",{style:{display:"flex",flexDirection:"column",alignItems:"center",gap:"var(--space-3)"},children:[x.jsx("button",{onClick:i?t:e,disabled:s,style:{width:88,height:88,borderRadius:"50%",background:i?"var(--bg-surface-raised)":"transparent",border:`3px solid ${o}`,color:"var(--text-primary)",fontSize:32,display:"flex",alignItems:"center",justifyContent:"center",transition:"border-color 200ms, background 200ms",cursor:s?"wait":"pointer",animation:n==="listening"?"micPulse 1.4s ease-in-out infinite":"none"},children:i?"🎙":"🎤"}),x.jsx("div",{style:{fontSize:"var(--fs-small)",color:"var(--text-muted)"},children:l}),r&&x.jsx("div",{style:{fontSize:"var(--fs-small)",color:"var(--severity-critical)",maxWidth:320,textAlign:"center"},children:r}),x.jsx("style",{children:` + @keyframes micPulse { + 0%, 100% { box-shadow: 0 0 0 0 rgba(252, 226, 5, 0.5); } + 50% { box-shadow: 0 0 0 12px rgba(252, 226, 5, 0); } + } + `})]})}function Sy(){const e=Ee(n=>n.transcript),t=A.useRef(null);return A.useEffect(()=>{var n;(n=t.current)==null||n.scrollTo({top:t.current.scrollHeight,behavior:"smooth"})},[e]),x.jsxs("div",{style:{height:"100%",background:"var(--bg-surface)",border:"1px solid var(--border-subtle)",borderRadius:"var(--radius-md)",padding:"var(--space-4)",overflowY:"auto",display:"flex",flexDirection:"column",gap:"var(--space-3)"},ref:t,children:[e.length===0&&x.jsx("div",{style:{color:"var(--text-subtle)",fontSize:"var(--fs-small)"},children:"Transcript will appear here as you speak."}),e.map(n=>x.jsxs("div",{children:[x.jsx("div",{style:{fontSize:"var(--fs-small)",fontWeight:700,textTransform:"uppercase",letterSpacing:"0.04em",color:n.who==="user"?"var(--brand-yellow)":"var(--text-muted)",marginBottom:2},children:n.who==="user"?"you":"assistant"}),x.jsxs("div",{style:{color:n.partial?"var(--text-muted)":"var(--text-primary)",lineHeight:1.5},children:[n.text,n.partial&&x.jsx("span",{style:{opacity:.5},children:"…"})]})]},n.id))]})}const La=A.createContext({});function Va(e){const t=A.useRef(null);return t.current===null&&(t.current=e()),t.current}const Is=A.createContext(null),Oa=A.createContext({transformPagePoint:e=>e,isStatic:!1,reducedMotion:"never"});class wy extends A.Component{getSnapshotBeforeUpdate(t){const n=this.props.childRef.current;if(n&&t.isPresent&&!this.props.isPresent){const r=this.props.sizeRef.current;r.height=n.offsetHeight||0,r.width=n.offsetWidth||0,r.top=n.offsetTop,r.left=n.offsetLeft}return null}componentDidUpdate(){}render(){return this.props.children}}function ky({children:e,isPresent:t}){const n=A.useId(),r=A.useRef(null),i=A.useRef({width:0,height:0,top:0,left:0}),{nonce:s}=A.useContext(Oa);return A.useInsertionEffect(()=>{const{width:o,height:l,top:a,left:u}=i.current;if(t||!r.current||!o||!l)return;r.current.dataset.motionPopId=n;const c=document.createElement("style");return s&&(c.nonce=s),document.head.appendChild(c),c.sheet&&c.sheet.insertRule(` + [data-motion-pop-id="${n}"] { + position: absolute !important; + width: ${o}px !important; + height: ${l}px !important; + top: ${a}px !important; + left: ${u}px !important; + } + `),()=>{document.head.removeChild(c)}},[t]),x.jsx(wy,{isPresent:t,childRef:r,sizeRef:i,children:A.cloneElement(e,{ref:r})})}const Ty=({children:e,initial:t,isPresent:n,onExitComplete:r,custom:i,presenceAffectsLayout:s,mode:o})=>{const l=Va(Cy),a=A.useId(),u=A.useCallback(f=>{l.set(f,!0);for(const d of l.values())if(!d)return;r&&r()},[l,r]),c=A.useMemo(()=>({id:a,initial:t,isPresent:n,custom:i,onExitComplete:u,register:f=>(l.set(f,!1),()=>l.delete(f))}),s?[Math.random(),u]:[n,u]);return A.useMemo(()=>{l.forEach((f,d)=>l.set(d,!1))},[n]),A.useEffect(()=>{!n&&!l.size&&r&&r()},[n]),o==="popLayout"&&(e=x.jsx(ky,{isPresent:n,children:e})),x.jsx(Is.Provider,{value:c,children:e})};function Cy(){return new Map}function th(e=!0){const t=A.useContext(Is);if(t===null)return[!0,null];const{isPresent:n,onExitComplete:r,register:i}=t,s=A.useId();A.useEffect(()=>{e&&i(s)},[e]);const o=A.useCallback(()=>e&&r&&r(s),[s,r,e]);return!n&&r?[!1,o]:[!0]}const Pi=e=>e.key||"";function Pc(e){const t=[];return A.Children.forEach(e,n=>{A.isValidElement(n)&&t.push(n)}),t}const Na=typeof window<"u",nh=Na?A.useLayoutEffect:A.useEffect,Py=({children:e,custom:t,initial:n=!0,onExitComplete:r,presenceAffectsLayout:i=!0,mode:s="sync",propagate:o=!1})=>{const[l,a]=th(o),u=A.useMemo(()=>Pc(e),[e]),c=o&&!l?[]:u.map(Pi),f=A.useRef(!0),d=A.useRef(u),g=Va(()=>new Map),[v,y]=A.useState(u),[k,p]=A.useState(u);nh(()=>{f.current=!1,d.current=u;for(let S=0;S{const w=Pi(S),T=o&&!l?!1:u===k||c.includes(w),E=()=>{if(g.has(w))g.set(w,!0);else return;let C=!0;g.forEach(V=>{V||(C=!1)}),C&&(m==null||m(),p(d.current),o&&(a==null||a()),r&&r())};return x.jsx(Ty,{isPresent:T,initial:!f.current||n?void 0:!1,custom:T?void 0:t,presenceAffectsLayout:i,mode:s,onExitComplete:T?void 0:E,children:S},w)})})},Re=e=>e;let rh=Re;function Ia(e){let t;return()=>(t===void 0&&(t=e()),t)}const Wn=(e,t,n)=>{const r=t-e;return r===0?1:(n-e)/r},at=e=>e*1e3,ut=e=>e/1e3,Ey={useManualTiming:!1};function _y(e){let t=new Set,n=new Set,r=!1,i=!1;const s=new WeakSet;let o={delta:0,timestamp:0,isProcessing:!1};function l(u){s.has(u)&&(a.schedule(u),e()),u(o)}const a={schedule:(u,c=!1,f=!1)=>{const g=f&&r?t:n;return c&&s.add(u),g.has(u)||g.add(u),u},cancel:u=>{n.delete(u),s.delete(u)},process:u=>{if(o=u,r){i=!0;return}r=!0,[t,n]=[n,t],t.forEach(l),t.clear(),r=!1,i&&(i=!1,a.process(u))}};return a}const Ei=["read","resolveKeyframes","update","preRender","render","postRender"],Ay=40;function ih(e,t){let n=!1,r=!0;const i={delta:0,timestamp:0,isProcessing:!1},s=()=>n=!0,o=Ei.reduce((p,h)=>(p[h]=_y(s),p),{}),{read:l,resolveKeyframes:a,update:u,preRender:c,render:f,postRender:d}=o,g=()=>{const p=performance.now();n=!1,i.delta=r?1e3/60:Math.max(Math.min(p-i.timestamp,Ay),1),i.timestamp=p,i.isProcessing=!0,l.process(i),a.process(i),u.process(i),c.process(i),f.process(i),d.process(i),i.isProcessing=!1,n&&t&&(r=!1,e(g))},v=()=>{n=!0,r=!0,i.isProcessing||e(g)};return{schedule:Ei.reduce((p,h)=>{const m=o[h];return p[h]=(S,w=!1,T=!1)=>(n||v(),m.schedule(S,w,T)),p},{}),cancel:p=>{for(let h=0;hEc[e].some(n=>!!t[n])};function Ry(e){for(const t in e)Hn[t]={...Hn[t],...e[t]}}const Dy=new Set(["animate","exit","variants","initial","style","values","variants","transition","transformTemplate","custom","inherit","onBeforeLayoutMeasure","onAnimationStart","onAnimationComplete","onUpdate","onDragStart","onDrag","onDragEnd","onMeasureDragConstraints","onDirectionLock","onDragTransitionEnd","_dragX","_dragY","onHoverStart","onHoverEnd","onViewportEnter","onViewportLeave","globalTapTarget","ignoreStrict","viewport"]);function hs(e){return e.startsWith("while")||e.startsWith("drag")&&e!=="draggable"||e.startsWith("layout")||e.startsWith("onTap")||e.startsWith("onPan")||e.startsWith("onLayout")||Dy.has(e)}let oh=e=>!hs(e);function My(e){e&&(oh=t=>t.startsWith("on")?!hs(t):e(t))}try{My(require("@emotion/is-prop-valid").default)}catch{}function jy(e,t,n){const r={};for(const i in e)i==="values"&&typeof e.values=="object"||(oh(i)||n===!0&&hs(i)||!t&&!hs(i)||e.draggable&&i.startsWith("onDrag"))&&(r[i]=e[i]);return r}function Ly(e){if(typeof Proxy>"u")return e;const t=new Map,n=(...r)=>e(...r);return new Proxy(n,{get:(r,i)=>i==="create"?e:(t.has(i)||t.set(i,e(i)),t.get(i))})}const zs=A.createContext({});function Kr(e){return typeof e=="string"||Array.isArray(e)}function Fs(e){return e!==null&&typeof e=="object"&&typeof e.start=="function"}const za=["animate","whileInView","whileFocus","whileHover","whileTap","whileDrag","exit"],Fa=["initial",...za];function Bs(e){return Fs(e.animate)||Fa.some(t=>Kr(e[t]))}function lh(e){return!!(Bs(e)||e.variants)}function Vy(e,t){if(Bs(e)){const{initial:n,animate:r}=e;return{initial:n===!1||Kr(n)?n:void 0,animate:Kr(r)?r:void 0}}return e.inherit!==!1?t:{}}function Oy(e){const{initial:t,animate:n}=Vy(e,A.useContext(zs));return A.useMemo(()=>({initial:t,animate:n}),[_c(t),_c(n)])}function _c(e){return Array.isArray(e)?e.join(" "):e}const Ny=Symbol.for("motionComponentSymbol");function Pn(e){return e&&typeof e=="object"&&Object.prototype.hasOwnProperty.call(e,"current")}function Iy(e,t,n){return A.useCallback(r=>{r&&e.onMount&&e.onMount(r),t&&(r?t.mount(r):t.unmount()),n&&(typeof n=="function"?n(r):Pn(n)&&(n.current=r))},[t])}const Ba=e=>e.replace(/([a-z])([A-Z])/gu,"$1-$2").toLowerCase(),zy="framerAppearId",ah="data-"+Ba(zy),{schedule:Ua}=ih(queueMicrotask,!1),uh=A.createContext({});function Fy(e,t,n,r,i){var s,o;const{visualElement:l}=A.useContext(zs),a=A.useContext(sh),u=A.useContext(Is),c=A.useContext(Oa).reducedMotion,f=A.useRef(null);r=r||a.renderer,!f.current&&r&&(f.current=r(e,{visualState:t,parent:l,props:n,presenceContext:u,blockInitialAnimation:u?u.initial===!1:!1,reducedMotionConfig:c}));const d=f.current,g=A.useContext(uh);d&&!d.projection&&i&&(d.type==="html"||d.type==="svg")&&By(f.current,n,i,g);const v=A.useRef(!1);A.useInsertionEffect(()=>{d&&v.current&&d.update(n,u)});const y=n[ah],k=A.useRef(!!y&&!(!((s=window.MotionHandoffIsComplete)===null||s===void 0)&&s.call(window,y))&&((o=window.MotionHasOptimisedAnimation)===null||o===void 0?void 0:o.call(window,y)));return nh(()=>{d&&(v.current=!0,window.MotionIsMounted=!0,d.updateFeatures(),Ua.render(d.render),k.current&&d.animationState&&d.animationState.animateChanges())}),A.useEffect(()=>{d&&(!k.current&&d.animationState&&d.animationState.animateChanges(),k.current&&(queueMicrotask(()=>{var p;(p=window.MotionHandoffMarkAsComplete)===null||p===void 0||p.call(window,y)}),k.current=!1))}),d}function By(e,t,n,r){const{layoutId:i,layout:s,drag:o,dragConstraints:l,layoutScroll:a,layoutRoot:u}=t;e.projection=new n(e.latestValues,t["data-framer-portal-id"]?void 0:ch(e.parent)),e.projection.setOptions({layoutId:i,layout:s,alwaysMeasureLayout:!!o||l&&Pn(l),visualElement:e,animationType:typeof s=="string"?s:"both",initialPromotionConfig:r,layoutScroll:a,layoutRoot:u})}function ch(e){if(e)return e.options.allowProjection!==!1?e.projection:ch(e.parent)}function Uy({preloadedFeatures:e,createVisualElement:t,useRender:n,useVisualState:r,Component:i}){var s,o;e&&Ry(e);function l(u,c){let f;const d={...A.useContext(Oa),...u,layoutId:$y(u)},{isStatic:g}=d,v=Oy(u),y=r(u,g);if(!g&&Na){Wy();const k=Hy(d);f=k.MeasureLayout,v.visualElement=Fy(i,y,d,t,k.ProjectionNode)}return x.jsxs(zs.Provider,{value:v,children:[f&&v.visualElement?x.jsx(f,{visualElement:v.visualElement,...d}):null,n(i,u,Iy(y,v.visualElement,c),y,g,v.visualElement)]})}l.displayName=`motion.${typeof i=="string"?i:`create(${(o=(s=i.displayName)!==null&&s!==void 0?s:i.name)!==null&&o!==void 0?o:""})`}`;const a=A.forwardRef(l);return a[Ny]=i,a}function $y({layoutId:e}){const t=A.useContext(La).id;return t&&e!==void 0?t+"-"+e:e}function Wy(e,t){A.useContext(sh).strict}function Hy(e){const{drag:t,layout:n}=Hn;if(!t&&!n)return{};const r={...t,...n};return{MeasureLayout:t!=null&&t.isEnabled(e)||n!=null&&n.isEnabled(e)?r.MeasureLayout:void 0,ProjectionNode:r.ProjectionNode}}const Ky=["animate","circle","defs","desc","ellipse","g","image","line","filter","marker","mask","metadata","path","pattern","polygon","polyline","rect","stop","switch","symbol","svg","text","tspan","use","view"];function $a(e){return typeof e!="string"||e.includes("-")?!1:!!(Ky.indexOf(e)>-1||/[A-Z]/u.test(e))}function Ac(e){const t=[{},{}];return e==null||e.values.forEach((n,r)=>{t[0][r]=n.get(),t[1][r]=n.getVelocity()}),t}function Wa(e,t,n,r){if(typeof t=="function"){const[i,s]=Ac(r);t=t(n!==void 0?n:e.custom,i,s)}if(typeof t=="string"&&(t=e.variants&&e.variants[t]),typeof t=="function"){const[i,s]=Ac(r);t=t(n!==void 0?n:e.custom,i,s)}return t}const Tl=e=>Array.isArray(e),Gy=e=>!!(e&&typeof e=="object"&&e.mix&&e.toValue),Qy=e=>Tl(e)?e[e.length-1]||0:e,he=e=>!!(e&&e.getVelocity);function Ui(e){const t=he(e)?e.get():e;return Gy(t)?t.toValue():t}function Yy({scrapeMotionValuesFromProps:e,createRenderState:t,onUpdate:n},r,i,s){const o={latestValues:Xy(r,i,s,e),renderState:t()};return n&&(o.onMount=l=>n({props:r,current:l,...o}),o.onUpdate=l=>n(l)),o}const fh=e=>(t,n)=>{const r=A.useContext(zs),i=A.useContext(Is),s=()=>Yy(e,t,r,i);return n?s():Va(s)};function Xy(e,t,n,r){const i={},s=r(e,{});for(const d in s)i[d]=Ui(s[d]);let{initial:o,animate:l}=e;const a=Bs(e),u=lh(e);t&&u&&!a&&e.inherit!==!1&&(o===void 0&&(o=t.initial),l===void 0&&(l=t.animate));let c=n?n.initial===!1:!1;c=c||o===!1;const f=c?l:o;if(f&&typeof f!="boolean"&&!Fs(f)){const d=Array.isArray(f)?f:[f];for(let g=0;gt=>typeof t=="string"&&t.startsWith(e),ph=dh("--"),Zy=dh("var(--"),Ha=e=>Zy(e)?by.test(e.split("/*")[0].trim()):!1,by=/var\(--(?:[\w-]+\s*|[\w-]+\s*,(?:\s*[^)(\s]|\s*\((?:[^)(]|\([^)(]*\))*\))+\s*)\)$/iu,hh=(e,t)=>t&&typeof e=="number"?t.transform(e):e,ht=(e,t,n)=>n>t?t:ntypeof e=="number",parse:parseFloat,transform:e=>e},Gr={...Zn,transform:e=>ht(0,1,e)},_i={...Zn,default:1},ti=e=>({test:t=>typeof t=="string"&&t.endsWith(e)&&t.split(" ").length===1,parse:parseFloat,transform:t=>`${t}${e}`}),yt=ti("deg"),tt=ti("%"),D=ti("px"),Jy=ti("vh"),qy=ti("vw"),Rc={...tt,parse:e=>tt.parse(e)/100,transform:e=>tt.transform(e*100)},e0={borderWidth:D,borderTopWidth:D,borderRightWidth:D,borderBottomWidth:D,borderLeftWidth:D,borderRadius:D,radius:D,borderTopLeftRadius:D,borderTopRightRadius:D,borderBottomRightRadius:D,borderBottomLeftRadius:D,width:D,maxWidth:D,height:D,maxHeight:D,top:D,right:D,bottom:D,left:D,padding:D,paddingTop:D,paddingRight:D,paddingBottom:D,paddingLeft:D,margin:D,marginTop:D,marginRight:D,marginBottom:D,marginLeft:D,backgroundPositionX:D,backgroundPositionY:D},t0={rotate:yt,rotateX:yt,rotateY:yt,rotateZ:yt,scale:_i,scaleX:_i,scaleY:_i,scaleZ:_i,skew:yt,skewX:yt,skewY:yt,distance:D,translateX:D,translateY:D,translateZ:D,x:D,y:D,z:D,perspective:D,transformPerspective:D,opacity:Gr,originX:Rc,originY:Rc,originZ:D},Dc={...Zn,transform:Math.round},Ka={...e0,...t0,zIndex:Dc,size:D,fillOpacity:Gr,strokeOpacity:Gr,numOctaves:Dc},n0={x:"translateX",y:"translateY",z:"translateZ",transformPerspective:"perspective"},r0=Xn.length;function i0(e,t,n){let r="",i=!0;for(let s=0;s({style:{},transform:{},transformOrigin:{},vars:{}}),mh=()=>({...Ya(),attrs:{}}),Xa=e=>typeof e=="string"&&e.toLowerCase()==="svg";function gh(e,{style:t,vars:n},r,i){Object.assign(e.style,t,i&&i.getProjectionStyles(r));for(const s in n)e.style.setProperty(s,n[s])}const vh=new Set(["baseFrequency","diffuseConstant","kernelMatrix","kernelUnitLength","keySplines","keyTimes","limitingConeAngle","markerHeight","markerWidth","numOctaves","targetX","targetY","surfaceScale","specularConstant","specularExponent","stdDeviation","tableValues","viewBox","gradientTransform","pathLength","startOffset","textLength","lengthAdjust"]);function yh(e,t,n,r){gh(e,t,void 0,r);for(const i in t.attrs)e.setAttribute(vh.has(i)?i:Ba(i),t.attrs[i])}const ms={};function u0(e){Object.assign(ms,e)}function xh(e,{layout:t,layoutId:n}){return fn.has(e)||e.startsWith("origin")||(t||n!==void 0)&&(!!ms[e]||e==="opacity")}function Za(e,t,n){var r;const{style:i}=e,s={};for(const o in i)(he(i[o])||t.style&&he(t.style[o])||xh(o,e)||((r=n==null?void 0:n.getValue(o))===null||r===void 0?void 0:r.liveStyle)!==void 0)&&(s[o]=i[o]);return s}function Sh(e,t,n){const r=Za(e,t,n);for(const i in e)if(he(e[i])||he(t[i])){const s=Xn.indexOf(i)!==-1?"attr"+i.charAt(0).toUpperCase()+i.substring(1):i;r[s]=e[i]}return r}function c0(e,t){try{t.dimensions=typeof e.getBBox=="function"?e.getBBox():e.getBoundingClientRect()}catch{t.dimensions={x:0,y:0,width:0,height:0}}}const jc=["x","y","width","height","cx","cy","r"],f0={useVisualState:fh({scrapeMotionValuesFromProps:Sh,createRenderState:mh,onUpdate:({props:e,prevProps:t,current:n,renderState:r,latestValues:i})=>{if(!n)return;let s=!!e.drag;if(!s){for(const l in i)if(fn.has(l)){s=!0;break}}if(!s)return;let o=!t;if(t)for(let l=0;l{c0(n,r),U.render(()=>{Qa(r,i,Xa(n.tagName),e.transformTemplate),yh(n,r)})})}})},d0={useVisualState:fh({scrapeMotionValuesFromProps:Za,createRenderState:Ya})};function wh(e,t,n){for(const r in t)!he(t[r])&&!xh(r,n)&&(e[r]=t[r])}function p0({transformTemplate:e},t){return A.useMemo(()=>{const n=Ya();return Ga(n,t,e),Object.assign({},n.vars,n.style)},[t])}function h0(e,t){const n=e.style||{},r={};return wh(r,n,e),Object.assign(r,p0(e,t)),r}function m0(e,t){const n={},r=h0(e,t);return e.drag&&e.dragListener!==!1&&(n.draggable=!1,r.userSelect=r.WebkitUserSelect=r.WebkitTouchCallout="none",r.touchAction=e.drag===!0?"none":`pan-${e.drag==="x"?"y":"x"}`),e.tabIndex===void 0&&(e.onTap||e.onTapStart||e.whileTap)&&(n.tabIndex=0),n.style=r,n}function g0(e,t,n,r){const i=A.useMemo(()=>{const s=mh();return Qa(s,t,Xa(r),e.transformTemplate),{...s.attrs,style:{...s.style}}},[t]);if(e.style){const s={};wh(s,e.style,e),i.style={...s,...i.style}}return i}function v0(e=!1){return(n,r,i,{latestValues:s},o)=>{const a=($a(n)?g0:m0)(r,s,o,n),u=jy(r,typeof n=="string",e),c=n!==A.Fragment?{...u,...a,ref:i}:{},{children:f}=r,d=A.useMemo(()=>he(f)?f.get():f,[f]);return A.createElement(n,{...c,children:d})}}function y0(e,t){return function(r,{forwardMotionProps:i}={forwardMotionProps:!1}){const o={...$a(r)?f0:d0,preloadedFeatures:e,useRender:v0(i),createVisualElement:t,Component:r};return Uy(o)}}function kh(e,t){if(!Array.isArray(t))return!1;const n=t.length;if(n!==e.length)return!1;for(let r=0;rwindow.ScrollTimeline!==void 0);class S0{constructor(t){this.stop=()=>this.runAll("stop"),this.animations=t.filter(Boolean)}get finished(){return Promise.all(this.animations.map(t=>"finished"in t?t.finished:t))}getAll(t){return this.animations[0][t]}setAll(t,n){for(let r=0;r{if(x0()&&i.attachTimeline)return i.attachTimeline(t);if(typeof n=="function")return n(i)});return()=>{r.forEach((i,s)=>{i&&i(),this.animations[s].stop()})}}get time(){return this.getAll("time")}set time(t){this.setAll("time",t)}get speed(){return this.getAll("speed")}set speed(t){this.setAll("speed",t)}get startTime(){return this.getAll("startTime")}get duration(){let t=0;for(let n=0;nn[t]())}flatten(){this.runAll("flatten")}play(){this.runAll("play")}pause(){this.runAll("pause")}cancel(){this.runAll("cancel")}complete(){this.runAll("complete")}}class w0 extends S0{then(t,n){return Promise.all(this.animations).then(t).catch(n)}}function ba(e,t){return e?e[t]||e.default||e:void 0}const Cl=2e4;function Th(e){let t=0;const n=50;let r=e.next(t);for(;!r.done&&t=Cl?1/0:t}function Ja(e){return typeof e=="function"}function Lc(e,t){e.timeline=t,e.onfinish=null}const qa=e=>Array.isArray(e)&&typeof e[0]=="number",k0={linearEasing:void 0};function T0(e,t){const n=Ia(e);return()=>{var r;return(r=k0[t])!==null&&r!==void 0?r:n()}}const gs=T0(()=>{try{document.createElement("div").animate({opacity:0},{easing:"linear(0, 1)"})}catch{return!1}return!0},"linearEasing"),Ch=(e,t,n=10)=>{let r="";const i=Math.max(Math.round(t/n),2);for(let s=0;s`cubic-bezier(${e}, ${t}, ${n}, ${r})`,Pl={linear:"linear",ease:"ease",easeIn:"ease-in",easeOut:"ease-out",easeInOut:"ease-in-out",circIn:dr([0,.65,.55,1]),circOut:dr([.55,0,1,.45]),backIn:dr([.31,.01,.66,-.59]),backOut:dr([.33,1.53,.69,.99])};function Eh(e,t){if(e)return typeof e=="function"&&gs()?Ch(e,t):qa(e)?dr(e):Array.isArray(e)?e.map(n=>Eh(n,t)||Pl.easeOut):Pl[e]}const We={x:!1,y:!1};function _h(){return We.x||We.y}function C0(e,t,n){var r;if(e instanceof Element)return[e];if(typeof e=="string"){let i=document;const s=(r=void 0)!==null&&r!==void 0?r:i.querySelectorAll(e);return s?Array.from(s):[]}return Array.from(e)}function Ah(e,t){const n=C0(e),r=new AbortController,i={passive:!0,...t,signal:r.signal};return[n,i,()=>r.abort()]}function Vc(e){return t=>{t.pointerType==="touch"||_h()||e(t)}}function P0(e,t,n={}){const[r,i,s]=Ah(e,n),o=Vc(l=>{const{target:a}=l,u=t(l);if(typeof u!="function"||!a)return;const c=Vc(f=>{u(f),a.removeEventListener("pointerleave",c)});a.addEventListener("pointerleave",c,i)});return r.forEach(l=>{l.addEventListener("pointerenter",o,i)}),s}const Rh=(e,t)=>t?e===t?!0:Rh(e,t.parentElement):!1,eu=e=>e.pointerType==="mouse"?typeof e.button!="number"||e.button<=0:e.isPrimary!==!1,E0=new Set(["BUTTON","INPUT","SELECT","TEXTAREA","A"]);function _0(e){return E0.has(e.tagName)||e.tabIndex!==-1}const pr=new WeakSet;function Oc(e){return t=>{t.key==="Enter"&&e(t)}}function yo(e,t){e.dispatchEvent(new PointerEvent("pointer"+t,{isPrimary:!0,bubbles:!0}))}const A0=(e,t)=>{const n=e.currentTarget;if(!n)return;const r=Oc(()=>{if(pr.has(n))return;yo(n,"down");const i=Oc(()=>{yo(n,"up")}),s=()=>yo(n,"cancel");n.addEventListener("keyup",i,t),n.addEventListener("blur",s,t)});n.addEventListener("keydown",r,t),n.addEventListener("blur",()=>n.removeEventListener("keydown",r),t)};function Nc(e){return eu(e)&&!_h()}function R0(e,t,n={}){const[r,i,s]=Ah(e,n),o=l=>{const a=l.currentTarget;if(!Nc(l)||pr.has(a))return;pr.add(a);const u=t(l),c=(g,v)=>{window.removeEventListener("pointerup",f),window.removeEventListener("pointercancel",d),!(!Nc(g)||!pr.has(a))&&(pr.delete(a),typeof u=="function"&&u(g,{success:v}))},f=g=>{c(g,n.useGlobalTarget||Rh(a,g.target))},d=g=>{c(g,!1)};window.addEventListener("pointerup",f,i),window.addEventListener("pointercancel",d,i)};return r.forEach(l=>{!_0(l)&&l.getAttribute("tabindex")===null&&(l.tabIndex=0),(n.useGlobalTarget?window:l).addEventListener("pointerdown",o,i),l.addEventListener("focus",u=>A0(u,i),i)}),s}function D0(e){return e==="x"||e==="y"?We[e]?null:(We[e]=!0,()=>{We[e]=!1}):We.x||We.y?null:(We.x=We.y=!0,()=>{We.x=We.y=!1})}const Dh=new Set(["width","height","top","left","right","bottom",...Xn]);let $i;function M0(){$i=void 0}const nt={now:()=>($i===void 0&&nt.set(le.isProcessing||Ey.useManualTiming?le.timestamp:performance.now()),$i),set:e=>{$i=e,queueMicrotask(M0)}};function tu(e,t){e.indexOf(t)===-1&&e.push(t)}function nu(e,t){const n=e.indexOf(t);n>-1&&e.splice(n,1)}class ru{constructor(){this.subscriptions=[]}add(t){return tu(this.subscriptions,t),()=>nu(this.subscriptions,t)}notify(t,n,r){const i=this.subscriptions.length;if(i)if(i===1)this.subscriptions[0](t,n,r);else for(let s=0;s!isNaN(parseFloat(e));class L0{constructor(t,n={}){this.version="11.18.2",this.canTrackVelocity=null,this.events={},this.updateAndNotify=(r,i=!0)=>{const s=nt.now();this.updatedAt!==s&&this.setPrevFrameValue(),this.prev=this.current,this.setCurrent(r),this.current!==this.prev&&this.events.change&&this.events.change.notify(this.current),i&&this.events.renderRequest&&this.events.renderRequest.notify(this.current)},this.hasAnimated=!1,this.setCurrent(t),this.owner=n.owner}setCurrent(t){this.current=t,this.updatedAt=nt.now(),this.canTrackVelocity===null&&t!==void 0&&(this.canTrackVelocity=j0(this.current))}setPrevFrameValue(t=this.current){this.prevFrameValue=t,this.prevUpdatedAt=this.updatedAt}onChange(t){return this.on("change",t)}on(t,n){this.events[t]||(this.events[t]=new ru);const r=this.events[t].add(n);return t==="change"?()=>{r(),U.read(()=>{this.events.change.getSize()||this.stop()})}:r}clearListeners(){for(const t in this.events)this.events[t].clear()}attach(t,n){this.passiveEffect=t,this.stopPassiveEffect=n}set(t,n=!0){!n||!this.passiveEffect?this.updateAndNotify(t,n):this.passiveEffect(t,this.updateAndNotify)}setWithVelocity(t,n,r){this.set(n),this.prev=void 0,this.prevFrameValue=t,this.prevUpdatedAt=this.updatedAt-r}jump(t,n=!0){this.updateAndNotify(t),this.prev=t,this.prevUpdatedAt=this.prevFrameValue=void 0,n&&this.stop(),this.stopPassiveEffect&&this.stopPassiveEffect()}get(){return this.current}getPrevious(){return this.prev}getVelocity(){const t=nt.now();if(!this.canTrackVelocity||this.prevFrameValue===void 0||t-this.updatedAt>Ic)return 0;const n=Math.min(this.updatedAt-this.prevUpdatedAt,Ic);return Mh(parseFloat(this.current)-parseFloat(this.prevFrameValue),n)}start(t){return this.stop(),new Promise(n=>{this.hasAnimated=!0,this.animation=t(n),this.events.animationStart&&this.events.animationStart.notify()}).then(()=>{this.events.animationComplete&&this.events.animationComplete.notify(),this.clearAnimation()})}stop(){this.animation&&(this.animation.stop(),this.events.animationCancel&&this.events.animationCancel.notify()),this.clearAnimation()}isAnimating(){return!!this.animation}clearAnimation(){delete this.animation}destroy(){this.clearListeners(),this.stop(),this.stopPassiveEffect&&this.stopPassiveEffect()}}function Qr(e,t){return new L0(e,t)}function V0(e,t,n){e.hasValue(t)?e.getValue(t).set(n):e.addValue(t,Qr(n))}function O0(e,t){const n=Us(e,t);let{transitionEnd:r={},transition:i={},...s}=n||{};s={...s,...r};for(const o in s){const l=Qy(s[o]);V0(e,o,l)}}function N0(e){return!!(he(e)&&e.add)}function El(e,t){const n=e.getValue("willChange");if(N0(n))return n.add(t)}function jh(e){return e.props[ah]}const Lh=(e,t,n)=>(((1-3*n+3*t)*e+(3*n-6*t))*e+3*t)*e,I0=1e-7,z0=12;function F0(e,t,n,r,i){let s,o,l=0;do o=t+(n-t)/2,s=Lh(o,r,i)-e,s>0?n=o:t=o;while(Math.abs(s)>I0&&++lF0(s,0,1,e,n);return s=>s===0||s===1?s:Lh(i(s),t,r)}const Vh=e=>t=>t<=.5?e(2*t)/2:(2-e(2*(1-t)))/2,Oh=e=>t=>1-e(1-t),Nh=ni(.33,1.53,.69,.99),iu=Oh(Nh),Ih=Vh(iu),zh=e=>(e*=2)<1?.5*iu(e):.5*(2-Math.pow(2,-10*(e-1))),su=e=>1-Math.sin(Math.acos(e)),Fh=Oh(su),Bh=Vh(su),Uh=e=>/^0[^.\s]+$/u.test(e);function B0(e){return typeof e=="number"?e===0:e!==null?e==="none"||e==="0"||Uh(e):!0}const Cr=e=>Math.round(e*1e5)/1e5,ou=/-?(?:\d+(?:\.\d+)?|\.\d+)/gu;function U0(e){return e==null}const $0=/^(?:#[\da-f]{3,8}|(?:rgb|hsl)a?\((?:-?[\d.]+%?[,\s]+){2}-?[\d.]+%?\s*(?:[,/]\s*)?(?:\b\d+(?:\.\d+)?|\.\d+)?%?\))$/iu,lu=(e,t)=>n=>!!(typeof n=="string"&&$0.test(n)&&n.startsWith(e)||t&&!U0(n)&&Object.prototype.hasOwnProperty.call(n,t)),$h=(e,t,n)=>r=>{if(typeof r!="string")return r;const[i,s,o,l]=r.match(ou);return{[e]:parseFloat(i),[t]:parseFloat(s),[n]:parseFloat(o),alpha:l!==void 0?parseFloat(l):1}},W0=e=>ht(0,255,e),xo={...Zn,transform:e=>Math.round(W0(e))},qt={test:lu("rgb","red"),parse:$h("red","green","blue"),transform:({red:e,green:t,blue:n,alpha:r=1})=>"rgba("+xo.transform(e)+", "+xo.transform(t)+", "+xo.transform(n)+", "+Cr(Gr.transform(r))+")"};function H0(e){let t="",n="",r="",i="";return e.length>5?(t=e.substring(1,3),n=e.substring(3,5),r=e.substring(5,7),i=e.substring(7,9)):(t=e.substring(1,2),n=e.substring(2,3),r=e.substring(3,4),i=e.substring(4,5),t+=t,n+=n,r+=r,i+=i),{red:parseInt(t,16),green:parseInt(n,16),blue:parseInt(r,16),alpha:i?parseInt(i,16)/255:1}}const _l={test:lu("#"),parse:H0,transform:qt.transform},En={test:lu("hsl","hue"),parse:$h("hue","saturation","lightness"),transform:({hue:e,saturation:t,lightness:n,alpha:r=1})=>"hsla("+Math.round(e)+", "+tt.transform(Cr(t))+", "+tt.transform(Cr(n))+", "+Cr(Gr.transform(r))+")"},de={test:e=>qt.test(e)||_l.test(e)||En.test(e),parse:e=>qt.test(e)?qt.parse(e):En.test(e)?En.parse(e):_l.parse(e),transform:e=>typeof e=="string"?e:e.hasOwnProperty("red")?qt.transform(e):En.transform(e)},K0=/(?:#[\da-f]{3,8}|(?:rgb|hsl)a?\((?:-?[\d.]+%?[,\s]+){2}-?[\d.]+%?\s*(?:[,/]\s*)?(?:\b\d+(?:\.\d+)?|\.\d+)?%?\))/giu;function G0(e){var t,n;return isNaN(e)&&typeof e=="string"&&(((t=e.match(ou))===null||t===void 0?void 0:t.length)||0)+(((n=e.match(K0))===null||n===void 0?void 0:n.length)||0)>0}const Wh="number",Hh="color",Q0="var",Y0="var(",zc="${}",X0=/var\s*\(\s*--(?:[\w-]+\s*|[\w-]+\s*,(?:\s*[^)(\s]|\s*\((?:[^)(]|\([^)(]*\))*\))+\s*)\)|#[\da-f]{3,8}|(?:rgb|hsl)a?\((?:-?[\d.]+%?[,\s]+){2}-?[\d.]+%?\s*(?:[,/]\s*)?(?:\b\d+(?:\.\d+)?|\.\d+)?%?\)|-?(?:\d+(?:\.\d+)?|\.\d+)/giu;function Yr(e){const t=e.toString(),n=[],r={color:[],number:[],var:[]},i=[];let s=0;const l=t.replace(X0,a=>(de.test(a)?(r.color.push(s),i.push(Hh),n.push(de.parse(a))):a.startsWith(Y0)?(r.var.push(s),i.push(Q0),n.push(a)):(r.number.push(s),i.push(Wh),n.push(parseFloat(a))),++s,zc)).split(zc);return{values:n,split:l,indexes:r,types:i}}function Kh(e){return Yr(e).values}function Gh(e){const{split:t,types:n}=Yr(e),r=t.length;return i=>{let s="";for(let o=0;otypeof e=="number"?0:e;function b0(e){const t=Kh(e);return Gh(e)(t.map(Z0))}const Nt={test:G0,parse:Kh,createTransformer:Gh,getAnimatableNone:b0},J0=new Set(["brightness","contrast","saturate","opacity"]);function q0(e){const[t,n]=e.slice(0,-1).split("(");if(t==="drop-shadow")return e;const[r]=n.match(ou)||[];if(!r)return e;const i=n.replace(r,"");let s=J0.has(t)?1:0;return r!==n&&(s*=100),t+"("+s+i+")"}const e1=/\b([a-z-]*)\(.*?\)/gu,Al={...Nt,getAnimatableNone:e=>{const t=e.match(e1);return t?t.map(q0).join(" "):e}},t1={...Ka,color:de,backgroundColor:de,outlineColor:de,fill:de,stroke:de,borderColor:de,borderTopColor:de,borderRightColor:de,borderBottomColor:de,borderLeftColor:de,filter:Al,WebkitFilter:Al},au=e=>t1[e];function Qh(e,t){let n=au(e);return n!==Al&&(n=Nt),n.getAnimatableNone?n.getAnimatableNone(t):void 0}const n1=new Set(["auto","none","0"]);function r1(e,t,n){let r=0,i;for(;re===Zn||e===D,Bc=(e,t)=>parseFloat(e.split(", ")[t]),Uc=(e,t)=>(n,{transform:r})=>{if(r==="none"||!r)return 0;const i=r.match(/^matrix3d\((.+)\)$/u);if(i)return Bc(i[1],t);{const s=r.match(/^matrix\((.+)\)$/u);return s?Bc(s[1],e):0}},i1=new Set(["x","y","z"]),s1=Xn.filter(e=>!i1.has(e));function o1(e){const t=[];return s1.forEach(n=>{const r=e.getValue(n);r!==void 0&&(t.push([n,r.get()]),r.set(n.startsWith("scale")?1:0))}),t}const Kn={width:({x:e},{paddingLeft:t="0",paddingRight:n="0"})=>e.max-e.min-parseFloat(t)-parseFloat(n),height:({y:e},{paddingTop:t="0",paddingBottom:n="0"})=>e.max-e.min-parseFloat(t)-parseFloat(n),top:(e,{top:t})=>parseFloat(t),left:(e,{left:t})=>parseFloat(t),bottom:({y:e},{top:t})=>parseFloat(t)+(e.max-e.min),right:({x:e},{left:t})=>parseFloat(t)+(e.max-e.min),x:Uc(4,13),y:Uc(5,14)};Kn.translateX=Kn.x;Kn.translateY=Kn.y;const nn=new Set;let Rl=!1,Dl=!1;function Yh(){if(Dl){const e=Array.from(nn).filter(r=>r.needsMeasurement),t=new Set(e.map(r=>r.element)),n=new Map;t.forEach(r=>{const i=o1(r);i.length&&(n.set(r,i),r.render())}),e.forEach(r=>r.measureInitialState()),t.forEach(r=>{r.render();const i=n.get(r);i&&i.forEach(([s,o])=>{var l;(l=r.getValue(s))===null||l===void 0||l.set(o)})}),e.forEach(r=>r.measureEndState()),e.forEach(r=>{r.suspendedScrollY!==void 0&&window.scrollTo(0,r.suspendedScrollY)})}Dl=!1,Rl=!1,nn.forEach(e=>e.complete()),nn.clear()}function Xh(){nn.forEach(e=>{e.readKeyframes(),e.needsMeasurement&&(Dl=!0)})}function l1(){Xh(),Yh()}class uu{constructor(t,n,r,i,s,o=!1){this.isComplete=!1,this.isAsync=!1,this.needsMeasurement=!1,this.isScheduled=!1,this.unresolvedKeyframes=[...t],this.onComplete=n,this.name=r,this.motionValue=i,this.element=s,this.isAsync=o}scheduleResolve(){this.isScheduled=!0,this.isAsync?(nn.add(this),Rl||(Rl=!0,U.read(Xh),U.resolveKeyframes(Yh))):(this.readKeyframes(),this.complete())}readKeyframes(){const{unresolvedKeyframes:t,name:n,element:r,motionValue:i}=this;for(let s=0;s/^-?(?:\d+(?:\.\d+)?|\.\d+)$/u.test(e),a1=/^var\(--(?:([\w-]+)|([\w-]+), ?([a-zA-Z\d ()%#.,-]+))\)/u;function u1(e){const t=a1.exec(e);if(!t)return[,];const[,n,r,i]=t;return[`--${n??r}`,i]}function bh(e,t,n=1){const[r,i]=u1(e);if(!r)return;const s=window.getComputedStyle(t).getPropertyValue(r);if(s){const o=s.trim();return Zh(o)?parseFloat(o):o}return Ha(i)?bh(i,t,n+1):i}const Jh=e=>t=>t.test(e),c1={test:e=>e==="auto",parse:e=>e},qh=[Zn,D,tt,yt,qy,Jy,c1],$c=e=>qh.find(Jh(e));class em extends uu{constructor(t,n,r,i,s){super(t,n,r,i,s,!0)}readKeyframes(){const{unresolvedKeyframes:t,element:n,name:r}=this;if(!n||!n.current)return;super.readKeyframes();for(let a=0;a{n.getValue(a).set(u)}),this.resolveNoneKeyframes()}}const Wc=(e,t)=>t==="zIndex"?!1:!!(typeof e=="number"||Array.isArray(e)||typeof e=="string"&&(Nt.test(e)||e==="0")&&!e.startsWith("url("));function f1(e){const t=e[0];if(e.length===1)return!0;for(let n=0;ne!==null;function $s(e,{repeat:t,repeatType:n="loop"},r){const i=e.filter(p1),s=t&&n!=="loop"&&t%2===1?0:i.length-1;return!s||r===void 0?i[s]:r}const h1=40;class tm{constructor({autoplay:t=!0,delay:n=0,type:r="keyframes",repeat:i=0,repeatDelay:s=0,repeatType:o="loop",...l}){this.isStopped=!1,this.hasAttemptedResolve=!1,this.createdAt=nt.now(),this.options={autoplay:t,delay:n,type:r,repeat:i,repeatDelay:s,repeatType:o,...l},this.updateFinishedPromise()}calcStartTime(){return this.resolvedAt?this.resolvedAt-this.createdAt>h1?this.resolvedAt:this.createdAt:this.createdAt}get resolved(){return!this._resolved&&!this.hasAttemptedResolve&&l1(),this._resolved}onKeyframesResolved(t,n){this.resolvedAt=nt.now(),this.hasAttemptedResolve=!0;const{name:r,type:i,velocity:s,delay:o,onComplete:l,onUpdate:a,isGenerator:u}=this.options;if(!u&&!d1(t,r,i,s))if(o)this.options.duration=0;else{a&&a($s(t,this.options,n)),l&&l(),this.resolveFinishedPromise();return}const c=this.initPlayback(t,n);c!==!1&&(this._resolved={keyframes:t,finalKeyframe:n,...c},this.onPostResolved())}onPostResolved(){}then(t,n){return this.currentFinishedPromise.then(t,n)}flatten(){this.options.type="keyframes",this.options.ease="linear"}updateFinishedPromise(){this.currentFinishedPromise=new Promise(t=>{this.resolveFinishedPromise=t})}}const H=(e,t,n)=>e+(t-e)*n;function So(e,t,n){return n<0&&(n+=1),n>1&&(n-=1),n<1/6?e+(t-e)*6*n:n<1/2?t:n<2/3?e+(t-e)*(2/3-n)*6:e}function m1({hue:e,saturation:t,lightness:n,alpha:r}){e/=360,t/=100,n/=100;let i=0,s=0,o=0;if(!t)i=s=o=n;else{const l=n<.5?n*(1+t):n+t-n*t,a=2*n-l;i=So(a,l,e+1/3),s=So(a,l,e),o=So(a,l,e-1/3)}return{red:Math.round(i*255),green:Math.round(s*255),blue:Math.round(o*255),alpha:r}}function vs(e,t){return n=>n>0?t:e}const wo=(e,t,n)=>{const r=e*e,i=n*(t*t-r)+r;return i<0?0:Math.sqrt(i)},g1=[_l,qt,En],v1=e=>g1.find(t=>t.test(e));function Hc(e){const t=v1(e);if(!t)return!1;let n=t.parse(e);return t===En&&(n=m1(n)),n}const Kc=(e,t)=>{const n=Hc(e),r=Hc(t);if(!n||!r)return vs(e,t);const i={...n};return s=>(i.red=wo(n.red,r.red,s),i.green=wo(n.green,r.green,s),i.blue=wo(n.blue,r.blue,s),i.alpha=H(n.alpha,r.alpha,s),qt.transform(i))},y1=(e,t)=>n=>t(e(n)),ri=(...e)=>e.reduce(y1),Ml=new Set(["none","hidden"]);function x1(e,t){return Ml.has(e)?n=>n<=0?e:t:n=>n>=1?t:e}function S1(e,t){return n=>H(e,t,n)}function cu(e){return typeof e=="number"?S1:typeof e=="string"?Ha(e)?vs:de.test(e)?Kc:T1:Array.isArray(e)?nm:typeof e=="object"?de.test(e)?Kc:w1:vs}function nm(e,t){const n=[...e],r=n.length,i=e.map((s,o)=>cu(s)(s,t[o]));return s=>{for(let o=0;o{for(const s in r)n[s]=r[s](i);return n}}function k1(e,t){var n;const r=[],i={color:0,var:0,number:0};for(let s=0;s{const n=Nt.createTransformer(t),r=Yr(e),i=Yr(t);return r.indexes.var.length===i.indexes.var.length&&r.indexes.color.length===i.indexes.color.length&&r.indexes.number.length>=i.indexes.number.length?Ml.has(e)&&!i.values.length||Ml.has(t)&&!r.values.length?x1(e,t):ri(nm(k1(r,i),i.values),n):vs(e,t)};function rm(e,t,n){return typeof e=="number"&&typeof t=="number"&&typeof n=="number"?H(e,t,n):cu(e)(e,t)}const C1=5;function im(e,t,n){const r=Math.max(t-C1,0);return Mh(n-e(r),t-r)}const Q={stiffness:100,damping:10,mass:1,velocity:0,duration:800,bounce:.3,visualDuration:.3,restSpeed:{granular:.01,default:2},restDelta:{granular:.005,default:.5},minDuration:.01,maxDuration:10,minDamping:.05,maxDamping:1},ko=.001;function P1({duration:e=Q.duration,bounce:t=Q.bounce,velocity:n=Q.velocity,mass:r=Q.mass}){let i,s,o=1-t;o=ht(Q.minDamping,Q.maxDamping,o),e=ht(Q.minDuration,Q.maxDuration,ut(e)),o<1?(i=u=>{const c=u*o,f=c*e,d=c-n,g=jl(u,o),v=Math.exp(-f);return ko-d/g*v},s=u=>{const f=u*o*e,d=f*n+n,g=Math.pow(o,2)*Math.pow(u,2)*e,v=Math.exp(-f),y=jl(Math.pow(u,2),o);return(-i(u)+ko>0?-1:1)*((d-g)*v)/y}):(i=u=>{const c=Math.exp(-u*e),f=(u-n)*e+1;return-ko+c*f},s=u=>{const c=Math.exp(-u*e),f=(n-u)*(e*e);return c*f});const l=5/e,a=_1(i,s,l);if(e=at(e),isNaN(a))return{stiffness:Q.stiffness,damping:Q.damping,duration:e};{const u=Math.pow(a,2)*r;return{stiffness:u,damping:o*2*Math.sqrt(r*u),duration:e}}}const E1=12;function _1(e,t,n){let r=n;for(let i=1;ie[n]!==void 0)}function D1(e){let t={velocity:Q.velocity,stiffness:Q.stiffness,damping:Q.damping,mass:Q.mass,isResolvedFromDuration:!1,...e};if(!Gc(e,R1)&&Gc(e,A1))if(e.visualDuration){const n=e.visualDuration,r=2*Math.PI/(n*1.2),i=r*r,s=2*ht(.05,1,1-(e.bounce||0))*Math.sqrt(i);t={...t,mass:Q.mass,stiffness:i,damping:s}}else{const n=P1(e);t={...t,...n,mass:Q.mass},t.isResolvedFromDuration=!0}return t}function sm(e=Q.visualDuration,t=Q.bounce){const n=typeof e!="object"?{visualDuration:e,keyframes:[0,1],bounce:t}:e;let{restSpeed:r,restDelta:i}=n;const s=n.keyframes[0],o=n.keyframes[n.keyframes.length-1],l={done:!1,value:s},{stiffness:a,damping:u,mass:c,duration:f,velocity:d,isResolvedFromDuration:g}=D1({...n,velocity:-ut(n.velocity||0)}),v=d||0,y=u/(2*Math.sqrt(a*c)),k=o-s,p=ut(Math.sqrt(a/c)),h=Math.abs(k)<5;r||(r=h?Q.restSpeed.granular:Q.restSpeed.default),i||(i=h?Q.restDelta.granular:Q.restDelta.default);let m;if(y<1){const w=jl(p,y);m=T=>{const E=Math.exp(-y*p*T);return o-E*((v+y*p*k)/w*Math.sin(w*T)+k*Math.cos(w*T))}}else if(y===1)m=w=>o-Math.exp(-p*w)*(k+(v+p*k)*w);else{const w=p*Math.sqrt(y*y-1);m=T=>{const E=Math.exp(-y*p*T),C=Math.min(w*T,300);return o-E*((v+y*p*k)*Math.sinh(C)+w*k*Math.cosh(C))/w}}const S={calculatedDuration:g&&f||null,next:w=>{const T=m(w);if(g)l.done=w>=f;else{let E=0;y<1&&(E=w===0?at(v):im(m,w,T));const C=Math.abs(E)<=r,V=Math.abs(o-T)<=i;l.done=C&&V}return l.value=l.done?o:T,l},toString:()=>{const w=Math.min(Th(S),Cl),T=Ch(E=>S.next(w*E).value,w,30);return w+"ms "+T}};return S}function Qc({keyframes:e,velocity:t=0,power:n=.8,timeConstant:r=325,bounceDamping:i=10,bounceStiffness:s=500,modifyTarget:o,min:l,max:a,restDelta:u=.5,restSpeed:c}){const f=e[0],d={done:!1,value:f},g=C=>l!==void 0&&Ca,v=C=>l===void 0?a:a===void 0||Math.abs(l-C)-y*Math.exp(-C/r),m=C=>p+h(C),S=C=>{const V=h(C),M=m(C);d.done=Math.abs(V)<=u,d.value=d.done?p:M};let w,T;const E=C=>{g(d.value)&&(w=C,T=sm({keyframes:[d.value,v(d.value)],velocity:im(m,C,d.value),damping:i,stiffness:s,restDelta:u,restSpeed:c}))};return E(0),{calculatedDuration:null,next:C=>{let V=!1;return!T&&w===void 0&&(V=!0,S(C),E(C)),w!==void 0&&C>=w?T.next(C-w):(!V&&S(C),d)}}}const M1=ni(.42,0,1,1),j1=ni(0,0,.58,1),om=ni(.42,0,.58,1),L1=e=>Array.isArray(e)&&typeof e[0]!="number",V1={linear:Re,easeIn:M1,easeInOut:om,easeOut:j1,circIn:su,circInOut:Bh,circOut:Fh,backIn:iu,backInOut:Ih,backOut:Nh,anticipate:zh},Yc=e=>{if(qa(e)){rh(e.length===4);const[t,n,r,i]=e;return ni(t,n,r,i)}else if(typeof e=="string")return V1[e];return e};function O1(e,t,n){const r=[],i=n||rm,s=e.length-1;for(let o=0;ot[0];if(s===2&&t[0]===t[1])return()=>t[1];const o=e[0]===e[1];e[0]>e[s-1]&&(e=[...e].reverse(),t=[...t].reverse());const l=O1(t,r,i),a=l.length,u=c=>{if(o&&c1)for(;fu(ht(e[0],e[s-1],c)):u}function I1(e,t){const n=e[e.length-1];for(let r=1;r<=t;r++){const i=Wn(0,t,r);e.push(H(n,1,i))}}function z1(e){const t=[0];return I1(t,e.length-1),t}function F1(e,t){return e.map(n=>n*t)}function B1(e,t){return e.map(()=>t||om).splice(0,e.length-1)}function ys({duration:e=300,keyframes:t,times:n,ease:r="easeInOut"}){const i=L1(r)?r.map(Yc):Yc(r),s={done:!1,value:t[0]},o=F1(n&&n.length===t.length?n:z1(t),e),l=N1(o,t,{ease:Array.isArray(i)?i:B1(t,i)});return{calculatedDuration:e,next:a=>(s.value=l(a),s.done=a>=e,s)}}const U1=e=>{const t=({timestamp:n})=>e(n);return{start:()=>U.update(t,!0),stop:()=>Ot(t),now:()=>le.isProcessing?le.timestamp:nt.now()}},$1={decay:Qc,inertia:Qc,tween:ys,keyframes:ys,spring:sm},W1=e=>e/100;class fu extends tm{constructor(t){super(t),this.holdTime=null,this.cancelTime=null,this.currentTime=0,this.playbackSpeed=1,this.pendingPlayState="running",this.startTime=null,this.state="idle",this.stop=()=>{if(this.resolver.cancel(),this.isStopped=!0,this.state==="idle")return;this.teardown();const{onStop:a}=this.options;a&&a()};const{name:n,motionValue:r,element:i,keyframes:s}=this.options,o=(i==null?void 0:i.KeyframeResolver)||uu,l=(a,u)=>this.onKeyframesResolved(a,u);this.resolver=new o(s,l,n,r,i),this.resolver.scheduleResolve()}flatten(){super.flatten(),this._resolved&&Object.assign(this._resolved,this.initPlayback(this._resolved.keyframes))}initPlayback(t){const{type:n="keyframes",repeat:r=0,repeatDelay:i=0,repeatType:s,velocity:o=0}=this.options,l=Ja(n)?n:$1[n]||ys;let a,u;l!==ys&&typeof t[0]!="number"&&(a=ri(W1,rm(t[0],t[1])),t=[0,100]);const c=l({...this.options,keyframes:t});s==="mirror"&&(u=l({...this.options,keyframes:[...t].reverse(),velocity:-o})),c.calculatedDuration===null&&(c.calculatedDuration=Th(c));const{calculatedDuration:f}=c,d=f+i,g=d*(r+1)-i;return{generator:c,mirroredGenerator:u,mapPercentToKeyframes:a,calculatedDuration:f,resolvedDuration:d,totalDuration:g}}onPostResolved(){const{autoplay:t=!0}=this.options;this.play(),this.pendingPlayState==="paused"||!t?this.pause():this.state=this.pendingPlayState}tick(t,n=!1){const{resolved:r}=this;if(!r){const{keyframes:C}=this.options;return{done:!0,value:C[C.length-1]}}const{finalKeyframe:i,generator:s,mirroredGenerator:o,mapPercentToKeyframes:l,keyframes:a,calculatedDuration:u,totalDuration:c,resolvedDuration:f}=r;if(this.startTime===null)return s.next(0);const{delay:d,repeat:g,repeatType:v,repeatDelay:y,onUpdate:k}=this.options;this.speed>0?this.startTime=Math.min(this.startTime,t):this.speed<0&&(this.startTime=Math.min(t-c/this.speed,this.startTime)),n?this.currentTime=t:this.holdTime!==null?this.currentTime=this.holdTime:this.currentTime=Math.round(t-this.startTime)*this.speed;const p=this.currentTime-d*(this.speed>=0?1:-1),h=this.speed>=0?p<0:p>c;this.currentTime=Math.max(p,0),this.state==="finished"&&this.holdTime===null&&(this.currentTime=c);let m=this.currentTime,S=s;if(g){const C=Math.min(this.currentTime,c)/f;let V=Math.floor(C),M=C%1;!M&&C>=1&&(M=1),M===1&&V--,V=Math.min(V,g+1),!!(V%2)&&(v==="reverse"?(M=1-M,y&&(M-=y/f)):v==="mirror"&&(S=o)),m=ht(0,1,M)*f}const w=h?{done:!1,value:a[0]}:S.next(m);l&&(w.value=l(w.value));let{done:T}=w;!h&&u!==null&&(T=this.speed>=0?this.currentTime>=c:this.currentTime<=0);const E=this.holdTime===null&&(this.state==="finished"||this.state==="running"&&T);return E&&i!==void 0&&(w.value=$s(a,this.options,i)),k&&k(w.value),E&&this.finish(),w}get duration(){const{resolved:t}=this;return t?ut(t.calculatedDuration):0}get time(){return ut(this.currentTime)}set time(t){t=at(t),this.currentTime=t,this.holdTime!==null||this.speed===0?this.holdTime=t:this.driver&&(this.startTime=this.driver.now()-t/this.speed)}get speed(){return this.playbackSpeed}set speed(t){const n=this.playbackSpeed!==t;this.playbackSpeed=t,n&&(this.time=ut(this.currentTime))}play(){if(this.resolver.isScheduled||this.resolver.resume(),!this._resolved){this.pendingPlayState="running";return}if(this.isStopped)return;const{driver:t=U1,onPlay:n,startTime:r}=this.options;this.driver||(this.driver=t(s=>this.tick(s))),n&&n();const i=this.driver.now();this.holdTime!==null?this.startTime=i-this.holdTime:this.startTime?this.state==="finished"&&(this.startTime=i):this.startTime=r??this.calcStartTime(),this.state==="finished"&&this.updateFinishedPromise(),this.cancelTime=this.startTime,this.holdTime=null,this.state="running",this.driver.start()}pause(){var t;if(!this._resolved){this.pendingPlayState="paused";return}this.state="paused",this.holdTime=(t=this.currentTime)!==null&&t!==void 0?t:0}complete(){this.state!=="running"&&this.play(),this.pendingPlayState=this.state="finished",this.holdTime=null}finish(){this.teardown(),this.state="finished";const{onComplete:t}=this.options;t&&t()}cancel(){this.cancelTime!==null&&this.tick(this.cancelTime),this.teardown(),this.updateFinishedPromise()}teardown(){this.state="idle",this.stopDriver(),this.resolveFinishedPromise(),this.updateFinishedPromise(),this.startTime=this.cancelTime=null,this.resolver.cancel()}stopDriver(){this.driver&&(this.driver.stop(),this.driver=void 0)}sample(t){return this.startTime=0,this.tick(t,!0)}}const H1=new Set(["opacity","clipPath","filter","transform"]);function K1(e,t,n,{delay:r=0,duration:i=300,repeat:s=0,repeatType:o="loop",ease:l="easeInOut",times:a}={}){const u={[t]:n};a&&(u.offset=a);const c=Eh(l,i);return Array.isArray(c)&&(u.easing=c),e.animate(u,{delay:r,duration:i,easing:Array.isArray(c)?"linear":c,fill:"both",iterations:s+1,direction:o==="reverse"?"alternate":"normal"})}const G1=Ia(()=>Object.hasOwnProperty.call(Element.prototype,"animate")),xs=10,Q1=2e4;function Y1(e){return Ja(e.type)||e.type==="spring"||!Ph(e.ease)}function X1(e,t){const n=new fu({...t,keyframes:e,repeat:0,delay:0,isGenerator:!0});let r={done:!1,value:e[0]};const i=[];let s=0;for(;!r.done&&sthis.onKeyframesResolved(o,l),n,r,i),this.resolver.scheduleResolve()}initPlayback(t,n){let{duration:r=300,times:i,ease:s,type:o,motionValue:l,name:a,startTime:u}=this.options;if(!l.owner||!l.owner.current)return!1;if(typeof s=="string"&&gs()&&Z1(s)&&(s=lm[s]),Y1(this.options)){const{onComplete:f,onUpdate:d,motionValue:g,element:v,...y}=this.options,k=X1(t,y);t=k.keyframes,t.length===1&&(t[1]=t[0]),r=k.duration,i=k.times,s=k.ease,o="keyframes"}const c=K1(l.owner.current,a,t,{...this.options,duration:r,times:i,ease:s});return c.startTime=u??this.calcStartTime(),this.pendingTimeline?(Lc(c,this.pendingTimeline),this.pendingTimeline=void 0):c.onfinish=()=>{const{onComplete:f}=this.options;l.set($s(t,this.options,n)),f&&f(),this.cancel(),this.resolveFinishedPromise()},{animation:c,duration:r,times:i,type:o,ease:s,keyframes:t}}get duration(){const{resolved:t}=this;if(!t)return 0;const{duration:n}=t;return ut(n)}get time(){const{resolved:t}=this;if(!t)return 0;const{animation:n}=t;return ut(n.currentTime||0)}set time(t){const{resolved:n}=this;if(!n)return;const{animation:r}=n;r.currentTime=at(t)}get speed(){const{resolved:t}=this;if(!t)return 1;const{animation:n}=t;return n.playbackRate}set speed(t){const{resolved:n}=this;if(!n)return;const{animation:r}=n;r.playbackRate=t}get state(){const{resolved:t}=this;if(!t)return"idle";const{animation:n}=t;return n.playState}get startTime(){const{resolved:t}=this;if(!t)return null;const{animation:n}=t;return n.startTime}attachTimeline(t){if(!this._resolved)this.pendingTimeline=t;else{const{resolved:n}=this;if(!n)return Re;const{animation:r}=n;Lc(r,t)}return Re}play(){if(this.isStopped)return;const{resolved:t}=this;if(!t)return;const{animation:n}=t;n.playState==="finished"&&this.updateFinishedPromise(),n.play()}pause(){const{resolved:t}=this;if(!t)return;const{animation:n}=t;n.pause()}stop(){if(this.resolver.cancel(),this.isStopped=!0,this.state==="idle")return;this.resolveFinishedPromise(),this.updateFinishedPromise();const{resolved:t}=this;if(!t)return;const{animation:n,keyframes:r,duration:i,type:s,ease:o,times:l}=t;if(n.playState==="idle"||n.playState==="finished")return;if(this.time){const{motionValue:u,onUpdate:c,onComplete:f,element:d,...g}=this.options,v=new fu({...g,keyframes:r,duration:i,type:s,ease:o,times:l,isGenerator:!0}),y=at(this.time);u.setWithVelocity(v.sample(y-xs).value,v.sample(y).value,xs)}const{onStop:a}=this.options;a&&a(),this.cancel()}complete(){const{resolved:t}=this;t&&t.animation.finish()}cancel(){const{resolved:t}=this;t&&t.animation.cancel()}static supports(t){const{motionValue:n,name:r,repeatDelay:i,repeatType:s,damping:o,type:l}=t;if(!n||!n.owner||!(n.owner.current instanceof HTMLElement))return!1;const{onUpdate:a,transformTemplate:u}=n.owner.getProps();return G1()&&r&&H1.has(r)&&!a&&!u&&!i&&s!=="mirror"&&o!==0&&l!=="inertia"}}const b1={type:"spring",stiffness:500,damping:25,restSpeed:10},J1=e=>({type:"spring",stiffness:550,damping:e===0?2*Math.sqrt(550):30,restSpeed:10}),q1={type:"keyframes",duration:.8},ex={type:"keyframes",ease:[.25,.1,.35,1],duration:.3},tx=(e,{keyframes:t})=>t.length>2?q1:fn.has(e)?e.startsWith("scale")?J1(t[1]):b1:ex;function nx({when:e,delay:t,delayChildren:n,staggerChildren:r,staggerDirection:i,repeat:s,repeatType:o,repeatDelay:l,from:a,elapsed:u,...c}){return!!Object.keys(c).length}const du=(e,t,n,r={},i,s)=>o=>{const l=ba(r,e)||{},a=l.delay||r.delay||0;let{elapsed:u=0}=r;u=u-at(a);let c={keyframes:Array.isArray(n)?n:[null,n],ease:"easeOut",velocity:t.getVelocity(),...l,delay:-u,onUpdate:d=>{t.set(d),l.onUpdate&&l.onUpdate(d)},onComplete:()=>{o(),l.onComplete&&l.onComplete()},name:e,motionValue:t,element:s?void 0:i};nx(l)||(c={...c,...tx(e,c)}),c.duration&&(c.duration=at(c.duration)),c.repeatDelay&&(c.repeatDelay=at(c.repeatDelay)),c.from!==void 0&&(c.keyframes[0]=c.from);let f=!1;if((c.type===!1||c.duration===0&&!c.repeatDelay)&&(c.duration=0,c.delay===0&&(f=!0)),f&&!s&&t.get()!==void 0){const d=$s(c.keyframes,l);if(d!==void 0)return U.update(()=>{c.onUpdate(d),c.onComplete()}),new w0([])}return!s&&Xc.supports(c)?new Xc(c):new fu(c)};function rx({protectedKeys:e,needsAnimating:t},n){const r=e.hasOwnProperty(n)&&t[n]!==!0;return t[n]=!1,r}function am(e,t,{delay:n=0,transitionOverride:r,type:i}={}){var s;let{transition:o=e.getDefaultTransition(),transitionEnd:l,...a}=t;r&&(o=r);const u=[],c=i&&e.animationState&&e.animationState.getState()[i];for(const f in a){const d=e.getValue(f,(s=e.latestValues[f])!==null&&s!==void 0?s:null),g=a[f];if(g===void 0||c&&rx(c,f))continue;const v={delay:n,...ba(o||{},f)};let y=!1;if(window.MotionHandoffAnimation){const p=jh(e);if(p){const h=window.MotionHandoffAnimation(p,f,U);h!==null&&(v.startTime=h,y=!0)}}El(e,f),d.start(du(f,d,g,e.shouldReduceMotion&&Dh.has(f)?{type:!1}:v,e,y));const k=d.animation;k&&u.push(k)}return l&&Promise.all(u).then(()=>{U.update(()=>{l&&O0(e,l)})}),u}function Ll(e,t,n={}){var r;const i=Us(e,t,n.type==="exit"?(r=e.presenceContext)===null||r===void 0?void 0:r.custom:void 0);let{transition:s=e.getDefaultTransition()||{}}=i||{};n.transitionOverride&&(s=n.transitionOverride);const o=i?()=>Promise.all(am(e,i,n)):()=>Promise.resolve(),l=e.variantChildren&&e.variantChildren.size?(u=0)=>{const{delayChildren:c=0,staggerChildren:f,staggerDirection:d}=s;return ix(e,t,c+u,f,d,n)}:()=>Promise.resolve(),{when:a}=s;if(a){const[u,c]=a==="beforeChildren"?[o,l]:[l,o];return u().then(()=>c())}else return Promise.all([o(),l(n.delay)])}function ix(e,t,n=0,r=0,i=1,s){const o=[],l=(e.variantChildren.size-1)*r,a=i===1?(u=0)=>u*r:(u=0)=>l-u*r;return Array.from(e.variantChildren).sort(sx).forEach((u,c)=>{u.notify("AnimationStart",t),o.push(Ll(u,t,{...s,delay:n+a(c)}).then(()=>u.notify("AnimationComplete",t)))}),Promise.all(o)}function sx(e,t){return e.sortNodePosition(t)}function ox(e,t,n={}){e.notify("AnimationStart",t);let r;if(Array.isArray(t)){const i=t.map(s=>Ll(e,s,n));r=Promise.all(i)}else if(typeof t=="string")r=Ll(e,t,n);else{const i=typeof t=="function"?Us(e,t,n.custom):t;r=Promise.all(am(e,i,n))}return r.then(()=>{e.notify("AnimationComplete",t)})}const lx=Fa.length;function um(e){if(!e)return;if(!e.isControllingVariants){const n=e.parent?um(e.parent)||{}:{};return e.props.initial!==void 0&&(n.initial=e.props.initial),n}const t={};for(let n=0;nPromise.all(t.map(({animation:n,options:r})=>ox(e,n,r)))}function fx(e){let t=cx(e),n=Zc(),r=!0;const i=a=>(u,c)=>{var f;const d=Us(e,c,a==="exit"?(f=e.presenceContext)===null||f===void 0?void 0:f.custom:void 0);if(d){const{transition:g,transitionEnd:v,...y}=d;u={...u,...y,...v}}return u};function s(a){t=a(e)}function o(a){const{props:u}=e,c=um(e.parent)||{},f=[],d=new Set;let g={},v=1/0;for(let k=0;kv&&S,V=!1;const M=Array.isArray(m)?m:[m];let ne=M.reduce(i(p),{});w===!1&&(ne={});const{prevResolvedValues:gt={}}=h,$t={...gt,...ne},bn=q=>{C=!0,d.has(q)&&(V=!0,d.delete(q)),h.needsAnimating[q]=!0;const _=e.getValue(q);_&&(_.liveStyle=!1)};for(const q in $t){const _=ne[q],j=gt[q];if(g.hasOwnProperty(q))continue;let L=!1;Tl(_)&&Tl(j)?L=!kh(_,j):L=_!==j,L?_!=null?bn(q):d.add(q):_!==void 0&&d.has(q)?bn(q):h.protectedKeys[q]=!0}h.prevProp=m,h.prevResolvedValues=ne,h.isActive&&(g={...g,...ne}),r&&e.blockInitialAnimation&&(C=!1),C&&(!(T&&E)||V)&&f.push(...M.map(q=>({animation:q,options:{type:p}})))}if(d.size){const k={};d.forEach(p=>{const h=e.getBaseTarget(p),m=e.getValue(p);m&&(m.liveStyle=!0),k[p]=h??null}),f.push({animation:k})}let y=!!f.length;return r&&(u.initial===!1||u.initial===u.animate)&&!e.manuallyAnimateOnMount&&(y=!1),r=!1,y?t(f):Promise.resolve()}function l(a,u){var c;if(n[a].isActive===u)return Promise.resolve();(c=e.variantChildren)===null||c===void 0||c.forEach(d=>{var g;return(g=d.animationState)===null||g===void 0?void 0:g.setActive(a,u)}),n[a].isActive=u;const f=o(a);for(const d in n)n[d].protectedKeys={};return f}return{animateChanges:o,setActive:l,setAnimateFunction:s,getState:()=>n,reset:()=>{n=Zc(),r=!0}}}function dx(e,t){return typeof t=="string"?t!==e:Array.isArray(t)?!kh(t,e):!1}function Kt(e=!1){return{isActive:e,protectedKeys:{},needsAnimating:{},prevResolvedValues:{}}}function Zc(){return{animate:Kt(!0),whileInView:Kt(),whileHover:Kt(),whileTap:Kt(),whileDrag:Kt(),whileFocus:Kt(),exit:Kt()}}class Bt{constructor(t){this.isMounted=!1,this.node=t}update(){}}class px extends Bt{constructor(t){super(t),t.animationState||(t.animationState=fx(t))}updateAnimationControlsSubscription(){const{animate:t}=this.node.getProps();Fs(t)&&(this.unmountControls=t.subscribe(this.node))}mount(){this.updateAnimationControlsSubscription()}update(){const{animate:t}=this.node.getProps(),{animate:n}=this.node.prevProps||{};t!==n&&this.updateAnimationControlsSubscription()}unmount(){var t;this.node.animationState.reset(),(t=this.unmountControls)===null||t===void 0||t.call(this)}}let hx=0;class mx extends Bt{constructor(){super(...arguments),this.id=hx++}update(){if(!this.node.presenceContext)return;const{isPresent:t,onExitComplete:n}=this.node.presenceContext,{isPresent:r}=this.node.prevPresenceContext||{};if(!this.node.animationState||t===r)return;const i=this.node.animationState.setActive("exit",!t);n&&!t&&i.then(()=>n(this.id))}mount(){const{register:t}=this.node.presenceContext||{};t&&(this.unmount=t(this.id))}unmount(){}}const gx={animation:{Feature:px},exit:{Feature:mx}};function Xr(e,t,n,r={passive:!0}){return e.addEventListener(t,n,r),()=>e.removeEventListener(t,n)}function ii(e){return{point:{x:e.pageX,y:e.pageY}}}const vx=e=>t=>eu(t)&&e(t,ii(t));function Pr(e,t,n,r){return Xr(e,t,vx(n),r)}const bc=(e,t)=>Math.abs(e-t);function yx(e,t){const n=bc(e.x,t.x),r=bc(e.y,t.y);return Math.sqrt(n**2+r**2)}class cm{constructor(t,n,{transformPagePoint:r,contextWindow:i,dragSnapToOrigin:s=!1}={}){if(this.startEvent=null,this.lastMoveEvent=null,this.lastMoveEventInfo=null,this.handlers={},this.contextWindow=window,this.updatePoint=()=>{if(!(this.lastMoveEvent&&this.lastMoveEventInfo))return;const f=Co(this.lastMoveEventInfo,this.history),d=this.startEvent!==null,g=yx(f.offset,{x:0,y:0})>=3;if(!d&&!g)return;const{point:v}=f,{timestamp:y}=le;this.history.push({...v,timestamp:y});const{onStart:k,onMove:p}=this.handlers;d||(k&&k(this.lastMoveEvent,f),this.startEvent=this.lastMoveEvent),p&&p(this.lastMoveEvent,f)},this.handlePointerMove=(f,d)=>{this.lastMoveEvent=f,this.lastMoveEventInfo=To(d,this.transformPagePoint),U.update(this.updatePoint,!0)},this.handlePointerUp=(f,d)=>{this.end();const{onEnd:g,onSessionEnd:v,resumeAnimation:y}=this.handlers;if(this.dragSnapToOrigin&&y&&y(),!(this.lastMoveEvent&&this.lastMoveEventInfo))return;const k=Co(f.type==="pointercancel"?this.lastMoveEventInfo:To(d,this.transformPagePoint),this.history);this.startEvent&&g&&g(f,k),v&&v(f,k)},!eu(t))return;this.dragSnapToOrigin=s,this.handlers=n,this.transformPagePoint=r,this.contextWindow=i||window;const o=ii(t),l=To(o,this.transformPagePoint),{point:a}=l,{timestamp:u}=le;this.history=[{...a,timestamp:u}];const{onSessionStart:c}=n;c&&c(t,Co(l,this.history)),this.removeListeners=ri(Pr(this.contextWindow,"pointermove",this.handlePointerMove),Pr(this.contextWindow,"pointerup",this.handlePointerUp),Pr(this.contextWindow,"pointercancel",this.handlePointerUp))}updateHandlers(t){this.handlers=t}end(){this.removeListeners&&this.removeListeners(),Ot(this.updatePoint)}}function To(e,t){return t?{point:t(e.point)}:e}function Jc(e,t){return{x:e.x-t.x,y:e.y-t.y}}function Co({point:e},t){return{point:e,delta:Jc(e,fm(t)),offset:Jc(e,xx(t)),velocity:Sx(t,.1)}}function xx(e){return e[0]}function fm(e){return e[e.length-1]}function Sx(e,t){if(e.length<2)return{x:0,y:0};let n=e.length-1,r=null;const i=fm(e);for(;n>=0&&(r=e[n],!(i.timestamp-r.timestamp>at(t)));)n--;if(!r)return{x:0,y:0};const s=ut(i.timestamp-r.timestamp);if(s===0)return{x:0,y:0};const o={x:(i.x-r.x)/s,y:(i.y-r.y)/s};return o.x===1/0&&(o.x=0),o.y===1/0&&(o.y=0),o}const dm=1e-4,wx=1-dm,kx=1+dm,pm=.01,Tx=0-pm,Cx=0+pm;function Me(e){return e.max-e.min}function Px(e,t,n){return Math.abs(e-t)<=n}function qc(e,t,n,r=.5){e.origin=r,e.originPoint=H(t.min,t.max,e.origin),e.scale=Me(n)/Me(t),e.translate=H(n.min,n.max,e.origin)-e.originPoint,(e.scale>=wx&&e.scale<=kx||isNaN(e.scale))&&(e.scale=1),(e.translate>=Tx&&e.translate<=Cx||isNaN(e.translate))&&(e.translate=0)}function Er(e,t,n,r){qc(e.x,t.x,n.x,r?r.originX:void 0),qc(e.y,t.y,n.y,r?r.originY:void 0)}function ef(e,t,n){e.min=n.min+t.min,e.max=e.min+Me(t)}function Ex(e,t,n){ef(e.x,t.x,n.x),ef(e.y,t.y,n.y)}function tf(e,t,n){e.min=t.min-n.min,e.max=e.min+Me(t)}function _r(e,t,n){tf(e.x,t.x,n.x),tf(e.y,t.y,n.y)}function _x(e,{min:t,max:n},r){return t!==void 0&&en&&(e=r?H(n,e,r.max):Math.min(e,n)),e}function nf(e,t,n){return{min:t!==void 0?e.min+t:void 0,max:n!==void 0?e.max+n-(e.max-e.min):void 0}}function Ax(e,{top:t,left:n,bottom:r,right:i}){return{x:nf(e.x,n,i),y:nf(e.y,t,r)}}function rf(e,t){let n=t.min-e.min,r=t.max-e.max;return t.max-t.minr?n=Wn(t.min,t.max-r,e.min):r>i&&(n=Wn(e.min,e.max-i,t.min)),ht(0,1,n)}function Mx(e,t){const n={};return t.min!==void 0&&(n.min=t.min-e.min),t.max!==void 0&&(n.max=t.max-e.min),n}const Vl=.35;function jx(e=Vl){return e===!1?e=0:e===!0&&(e=Vl),{x:sf(e,"left","right"),y:sf(e,"top","bottom")}}function sf(e,t,n){return{min:of(e,t),max:of(e,n)}}function of(e,t){return typeof e=="number"?e:e[t]||0}const lf=()=>({translate:0,scale:1,origin:0,originPoint:0}),_n=()=>({x:lf(),y:lf()}),af=()=>({min:0,max:0}),Z=()=>({x:af(),y:af()});function Oe(e){return[e("x"),e("y")]}function hm({top:e,left:t,right:n,bottom:r}){return{x:{min:t,max:n},y:{min:e,max:r}}}function Lx({x:e,y:t}){return{top:t.min,right:e.max,bottom:t.max,left:e.min}}function Vx(e,t){if(!t)return e;const n=t({x:e.left,y:e.top}),r=t({x:e.right,y:e.bottom});return{top:n.y,left:n.x,bottom:r.y,right:r.x}}function Po(e){return e===void 0||e===1}function Ol({scale:e,scaleX:t,scaleY:n}){return!Po(e)||!Po(t)||!Po(n)}function Yt(e){return Ol(e)||mm(e)||e.z||e.rotate||e.rotateX||e.rotateY||e.skewX||e.skewY}function mm(e){return uf(e.x)||uf(e.y)}function uf(e){return e&&e!=="0%"}function Ss(e,t,n){const r=e-n,i=t*r;return n+i}function cf(e,t,n,r,i){return i!==void 0&&(e=Ss(e,i,r)),Ss(e,n,r)+t}function Nl(e,t=0,n=1,r,i){e.min=cf(e.min,t,n,r,i),e.max=cf(e.max,t,n,r,i)}function gm(e,{x:t,y:n}){Nl(e.x,t.translate,t.scale,t.originPoint),Nl(e.y,n.translate,n.scale,n.originPoint)}const ff=.999999999999,df=1.0000000000001;function Ox(e,t,n,r=!1){const i=n.length;if(!i)return;t.x=t.y=1;let s,o;for(let l=0;lff&&(t.x=1),t.yff&&(t.y=1)}function An(e,t){e.min=e.min+t,e.max=e.max+t}function pf(e,t,n,r,i=.5){const s=H(e.min,e.max,i);Nl(e,t,n,s,r)}function Rn(e,t){pf(e.x,t.x,t.scaleX,t.scale,t.originX),pf(e.y,t.y,t.scaleY,t.scale,t.originY)}function vm(e,t){return hm(Vx(e.getBoundingClientRect(),t))}function Nx(e,t,n){const r=vm(e,n),{scroll:i}=t;return i&&(An(r.x,i.offset.x),An(r.y,i.offset.y)),r}const ym=({current:e})=>e?e.ownerDocument.defaultView:null,Ix=new WeakMap;class zx{constructor(t){this.openDragLock=null,this.isDragging=!1,this.currentDirection=null,this.originPoint={x:0,y:0},this.constraints=!1,this.hasMutatedConstraints=!1,this.elastic=Z(),this.visualElement=t}start(t,{snapToCursor:n=!1}={}){const{presenceContext:r}=this.visualElement;if(r&&r.isPresent===!1)return;const i=c=>{const{dragSnapToOrigin:f}=this.getProps();f?this.pauseAnimation():this.stopAnimation(),n&&this.snapToCursor(ii(c).point)},s=(c,f)=>{const{drag:d,dragPropagation:g,onDragStart:v}=this.getProps();if(d&&!g&&(this.openDragLock&&this.openDragLock(),this.openDragLock=D0(d),!this.openDragLock))return;this.isDragging=!0,this.currentDirection=null,this.resolveConstraints(),this.visualElement.projection&&(this.visualElement.projection.isAnimationBlocked=!0,this.visualElement.projection.target=void 0),Oe(k=>{let p=this.getAxisMotionValue(k).get()||0;if(tt.test(p)){const{projection:h}=this.visualElement;if(h&&h.layout){const m=h.layout.layoutBox[k];m&&(p=Me(m)*(parseFloat(p)/100))}}this.originPoint[k]=p}),v&&U.postRender(()=>v(c,f)),El(this.visualElement,"transform");const{animationState:y}=this.visualElement;y&&y.setActive("whileDrag",!0)},o=(c,f)=>{const{dragPropagation:d,dragDirectionLock:g,onDirectionLock:v,onDrag:y}=this.getProps();if(!d&&!this.openDragLock)return;const{offset:k}=f;if(g&&this.currentDirection===null){this.currentDirection=Fx(k),this.currentDirection!==null&&v&&v(this.currentDirection);return}this.updateAxis("x",f.point,k),this.updateAxis("y",f.point,k),this.visualElement.render(),y&&y(c,f)},l=(c,f)=>this.stop(c,f),a=()=>Oe(c=>{var f;return this.getAnimationState(c)==="paused"&&((f=this.getAxisMotionValue(c).animation)===null||f===void 0?void 0:f.play())}),{dragSnapToOrigin:u}=this.getProps();this.panSession=new cm(t,{onSessionStart:i,onStart:s,onMove:o,onSessionEnd:l,resumeAnimation:a},{transformPagePoint:this.visualElement.getTransformPagePoint(),dragSnapToOrigin:u,contextWindow:ym(this.visualElement)})}stop(t,n){const r=this.isDragging;if(this.cancel(),!r)return;const{velocity:i}=n;this.startAnimation(i);const{onDragEnd:s}=this.getProps();s&&U.postRender(()=>s(t,n))}cancel(){this.isDragging=!1;const{projection:t,animationState:n}=this.visualElement;t&&(t.isAnimationBlocked=!1),this.panSession&&this.panSession.end(),this.panSession=void 0;const{dragPropagation:r}=this.getProps();!r&&this.openDragLock&&(this.openDragLock(),this.openDragLock=null),n&&n.setActive("whileDrag",!1)}updateAxis(t,n,r){const{drag:i}=this.getProps();if(!r||!Ai(t,i,this.currentDirection))return;const s=this.getAxisMotionValue(t);let o=this.originPoint[t]+r[t];this.constraints&&this.constraints[t]&&(o=_x(o,this.constraints[t],this.elastic[t])),s.set(o)}resolveConstraints(){var t;const{dragConstraints:n,dragElastic:r}=this.getProps(),i=this.visualElement.projection&&!this.visualElement.projection.layout?this.visualElement.projection.measure(!1):(t=this.visualElement.projection)===null||t===void 0?void 0:t.layout,s=this.constraints;n&&Pn(n)?this.constraints||(this.constraints=this.resolveRefConstraints()):n&&i?this.constraints=Ax(i.layoutBox,n):this.constraints=!1,this.elastic=jx(r),s!==this.constraints&&i&&this.constraints&&!this.hasMutatedConstraints&&Oe(o=>{this.constraints!==!1&&this.getAxisMotionValue(o)&&(this.constraints[o]=Mx(i.layoutBox[o],this.constraints[o]))})}resolveRefConstraints(){const{dragConstraints:t,onMeasureDragConstraints:n}=this.getProps();if(!t||!Pn(t))return!1;const r=t.current,{projection:i}=this.visualElement;if(!i||!i.layout)return!1;const s=Nx(r,i.root,this.visualElement.getTransformPagePoint());let o=Rx(i.layout.layoutBox,s);if(n){const l=n(Lx(o));this.hasMutatedConstraints=!!l,l&&(o=hm(l))}return o}startAnimation(t){const{drag:n,dragMomentum:r,dragElastic:i,dragTransition:s,dragSnapToOrigin:o,onDragTransitionEnd:l}=this.getProps(),a=this.constraints||{},u=Oe(c=>{if(!Ai(c,n,this.currentDirection))return;let f=a&&a[c]||{};o&&(f={min:0,max:0});const d=i?200:1e6,g=i?40:1e7,v={type:"inertia",velocity:r?t[c]:0,bounceStiffness:d,bounceDamping:g,timeConstant:750,restDelta:1,restSpeed:10,...s,...f};return this.startAxisValueAnimation(c,v)});return Promise.all(u).then(l)}startAxisValueAnimation(t,n){const r=this.getAxisMotionValue(t);return El(this.visualElement,t),r.start(du(t,r,0,n,this.visualElement,!1))}stopAnimation(){Oe(t=>this.getAxisMotionValue(t).stop())}pauseAnimation(){Oe(t=>{var n;return(n=this.getAxisMotionValue(t).animation)===null||n===void 0?void 0:n.pause()})}getAnimationState(t){var n;return(n=this.getAxisMotionValue(t).animation)===null||n===void 0?void 0:n.state}getAxisMotionValue(t){const n=`_drag${t.toUpperCase()}`,r=this.visualElement.getProps(),i=r[n];return i||this.visualElement.getValue(t,(r.initial?r.initial[t]:void 0)||0)}snapToCursor(t){Oe(n=>{const{drag:r}=this.getProps();if(!Ai(n,r,this.currentDirection))return;const{projection:i}=this.visualElement,s=this.getAxisMotionValue(n);if(i&&i.layout){const{min:o,max:l}=i.layout.layoutBox[n];s.set(t[n]-H(o,l,.5))}})}scalePositionWithinConstraints(){if(!this.visualElement.current)return;const{drag:t,dragConstraints:n}=this.getProps(),{projection:r}=this.visualElement;if(!Pn(n)||!r||!this.constraints)return;this.stopAnimation();const i={x:0,y:0};Oe(o=>{const l=this.getAxisMotionValue(o);if(l&&this.constraints!==!1){const a=l.get();i[o]=Dx({min:a,max:a},this.constraints[o])}});const{transformTemplate:s}=this.visualElement.getProps();this.visualElement.current.style.transform=s?s({},""):"none",r.root&&r.root.updateScroll(),r.updateLayout(),this.resolveConstraints(),Oe(o=>{if(!Ai(o,t,null))return;const l=this.getAxisMotionValue(o),{min:a,max:u}=this.constraints[o];l.set(H(a,u,i[o]))})}addListeners(){if(!this.visualElement.current)return;Ix.set(this.visualElement,this);const t=this.visualElement.current,n=Pr(t,"pointerdown",a=>{const{drag:u,dragListener:c=!0}=this.getProps();u&&c&&this.start(a)}),r=()=>{const{dragConstraints:a}=this.getProps();Pn(a)&&a.current&&(this.constraints=this.resolveRefConstraints())},{projection:i}=this.visualElement,s=i.addEventListener("measure",r);i&&!i.layout&&(i.root&&i.root.updateScroll(),i.updateLayout()),U.read(r);const o=Xr(window,"resize",()=>this.scalePositionWithinConstraints()),l=i.addEventListener("didUpdate",({delta:a,hasLayoutChanged:u})=>{this.isDragging&&u&&(Oe(c=>{const f=this.getAxisMotionValue(c);f&&(this.originPoint[c]+=a[c].translate,f.set(f.get()+a[c].translate))}),this.visualElement.render())});return()=>{o(),n(),s(),l&&l()}}getProps(){const t=this.visualElement.getProps(),{drag:n=!1,dragDirectionLock:r=!1,dragPropagation:i=!1,dragConstraints:s=!1,dragElastic:o=Vl,dragMomentum:l=!0}=t;return{...t,drag:n,dragDirectionLock:r,dragPropagation:i,dragConstraints:s,dragElastic:o,dragMomentum:l}}}function Ai(e,t,n){return(t===!0||t===e)&&(n===null||n===e)}function Fx(e,t=10){let n=null;return Math.abs(e.y)>t?n="y":Math.abs(e.x)>t&&(n="x"),n}class Bx extends Bt{constructor(t){super(t),this.removeGroupControls=Re,this.removeListeners=Re,this.controls=new zx(t)}mount(){const{dragControls:t}=this.node.getProps();t&&(this.removeGroupControls=t.subscribe(this.controls)),this.removeListeners=this.controls.addListeners()||Re}unmount(){this.removeGroupControls(),this.removeListeners()}}const hf=e=>(t,n)=>{e&&U.postRender(()=>e(t,n))};class Ux extends Bt{constructor(){super(...arguments),this.removePointerDownListener=Re}onPointerDown(t){this.session=new cm(t,this.createPanHandlers(),{transformPagePoint:this.node.getTransformPagePoint(),contextWindow:ym(this.node)})}createPanHandlers(){const{onPanSessionStart:t,onPanStart:n,onPan:r,onPanEnd:i}=this.node.getProps();return{onSessionStart:hf(t),onStart:hf(n),onMove:r,onEnd:(s,o)=>{delete this.session,i&&U.postRender(()=>i(s,o))}}}mount(){this.removePointerDownListener=Pr(this.node.current,"pointerdown",t=>this.onPointerDown(t))}update(){this.session&&this.session.updateHandlers(this.createPanHandlers())}unmount(){this.removePointerDownListener(),this.session&&this.session.end()}}const Wi={hasAnimatedSinceResize:!0,hasEverUpdated:!1};function mf(e,t){return t.max===t.min?0:e/(t.max-t.min)*100}const or={correct:(e,t)=>{if(!t.target)return e;if(typeof e=="string")if(D.test(e))e=parseFloat(e);else return e;const n=mf(e,t.target.x),r=mf(e,t.target.y);return`${n}% ${r}%`}},$x={correct:(e,{treeScale:t,projectionDelta:n})=>{const r=e,i=Nt.parse(e);if(i.length>5)return r;const s=Nt.createTransformer(e),o=typeof i[0]!="number"?1:0,l=n.x.scale*t.x,a=n.y.scale*t.y;i[0+o]/=l,i[1+o]/=a;const u=H(l,a,.5);return typeof i[2+o]=="number"&&(i[2+o]/=u),typeof i[3+o]=="number"&&(i[3+o]/=u),s(i)}};class Wx extends A.Component{componentDidMount(){const{visualElement:t,layoutGroup:n,switchLayoutGroup:r,layoutId:i}=this.props,{projection:s}=t;u0(Hx),s&&(n.group&&n.group.add(s),r&&r.register&&i&&r.register(s),s.root.didUpdate(),s.addEventListener("animationComplete",()=>{this.safeToRemove()}),s.setOptions({...s.options,onExitComplete:()=>this.safeToRemove()})),Wi.hasEverUpdated=!0}getSnapshotBeforeUpdate(t){const{layoutDependency:n,visualElement:r,drag:i,isPresent:s}=this.props,o=r.projection;return o&&(o.isPresent=s,i||t.layoutDependency!==n||n===void 0?o.willUpdate():this.safeToRemove(),t.isPresent!==s&&(s?o.promote():o.relegate()||U.postRender(()=>{const l=o.getStack();(!l||!l.members.length)&&this.safeToRemove()}))),null}componentDidUpdate(){const{projection:t}=this.props.visualElement;t&&(t.root.didUpdate(),Ua.postRender(()=>{!t.currentAnimation&&t.isLead()&&this.safeToRemove()}))}componentWillUnmount(){const{visualElement:t,layoutGroup:n,switchLayoutGroup:r}=this.props,{projection:i}=t;i&&(i.scheduleCheckAfterUnmount(),n&&n.group&&n.group.remove(i),r&&r.deregister&&r.deregister(i))}safeToRemove(){const{safeToRemove:t}=this.props;t&&t()}render(){return null}}function xm(e){const[t,n]=th(),r=A.useContext(La);return x.jsx(Wx,{...e,layoutGroup:r,switchLayoutGroup:A.useContext(uh),isPresent:t,safeToRemove:n})}const Hx={borderRadius:{...or,applyTo:["borderTopLeftRadius","borderTopRightRadius","borderBottomLeftRadius","borderBottomRightRadius"]},borderTopLeftRadius:or,borderTopRightRadius:or,borderBottomLeftRadius:or,borderBottomRightRadius:or,boxShadow:$x};function Kx(e,t,n){const r=he(e)?e:Qr(e);return r.start(du("",r,t,n)),r.animation}function Gx(e){return e instanceof SVGElement&&e.tagName!=="svg"}const Qx=(e,t)=>e.depth-t.depth;class Yx{constructor(){this.children=[],this.isDirty=!1}add(t){tu(this.children,t),this.isDirty=!0}remove(t){nu(this.children,t),this.isDirty=!0}forEach(t){this.isDirty&&this.children.sort(Qx),this.isDirty=!1,this.children.forEach(t)}}function Xx(e,t){const n=nt.now(),r=({timestamp:i})=>{const s=i-n;s>=t&&(Ot(r),e(s-t))};return U.read(r,!0),()=>Ot(r)}const Sm=["TopLeft","TopRight","BottomLeft","BottomRight"],Zx=Sm.length,gf=e=>typeof e=="string"?parseFloat(e):e,vf=e=>typeof e=="number"||D.test(e);function bx(e,t,n,r,i,s){i?(e.opacity=H(0,n.opacity!==void 0?n.opacity:1,Jx(r)),e.opacityExit=H(t.opacity!==void 0?t.opacity:1,0,qx(r))):s&&(e.opacity=H(t.opacity!==void 0?t.opacity:1,n.opacity!==void 0?n.opacity:1,r));for(let o=0;ort?1:n(Wn(e,t,r))}function xf(e,t){e.min=t.min,e.max=t.max}function Ve(e,t){xf(e.x,t.x),xf(e.y,t.y)}function Sf(e,t){e.translate=t.translate,e.scale=t.scale,e.originPoint=t.originPoint,e.origin=t.origin}function wf(e,t,n,r,i){return e-=t,e=Ss(e,1/n,r),i!==void 0&&(e=Ss(e,1/i,r)),e}function eS(e,t=0,n=1,r=.5,i,s=e,o=e){if(tt.test(t)&&(t=parseFloat(t),t=H(o.min,o.max,t/100)-o.min),typeof t!="number")return;let l=H(s.min,s.max,r);e===s&&(l-=t),e.min=wf(e.min,t,n,l,i),e.max=wf(e.max,t,n,l,i)}function kf(e,t,[n,r,i],s,o){eS(e,t[n],t[r],t[i],t.scale,s,o)}const tS=["x","scaleX","originX"],nS=["y","scaleY","originY"];function Tf(e,t,n,r){kf(e.x,t,tS,n?n.x:void 0,r?r.x:void 0),kf(e.y,t,nS,n?n.y:void 0,r?r.y:void 0)}function Cf(e){return e.translate===0&&e.scale===1}function km(e){return Cf(e.x)&&Cf(e.y)}function Pf(e,t){return e.min===t.min&&e.max===t.max}function rS(e,t){return Pf(e.x,t.x)&&Pf(e.y,t.y)}function Ef(e,t){return Math.round(e.min)===Math.round(t.min)&&Math.round(e.max)===Math.round(t.max)}function Tm(e,t){return Ef(e.x,t.x)&&Ef(e.y,t.y)}function _f(e){return Me(e.x)/Me(e.y)}function Af(e,t){return e.translate===t.translate&&e.scale===t.scale&&e.originPoint===t.originPoint}class iS{constructor(){this.members=[]}add(t){tu(this.members,t),t.scheduleRender()}remove(t){if(nu(this.members,t),t===this.prevLead&&(this.prevLead=void 0),t===this.lead){const n=this.members[this.members.length-1];n&&this.promote(n)}}relegate(t){const n=this.members.findIndex(i=>t===i);if(n===0)return!1;let r;for(let i=n;i>=0;i--){const s=this.members[i];if(s.isPresent!==!1){r=s;break}}return r?(this.promote(r),!0):!1}promote(t,n){const r=this.lead;if(t!==r&&(this.prevLead=r,this.lead=t,t.show(),r)){r.instance&&r.scheduleRender(),t.scheduleRender(),t.resumeFrom=r,n&&(t.resumeFrom.preserveOpacity=!0),r.snapshot&&(t.snapshot=r.snapshot,t.snapshot.latestValues=r.animationValues||r.latestValues),t.root&&t.root.isUpdating&&(t.isLayoutDirty=!0);const{crossfade:i}=t.options;i===!1&&r.hide()}}exitAnimationComplete(){this.members.forEach(t=>{const{options:n,resumingFrom:r}=t;n.onExitComplete&&n.onExitComplete(),r&&r.options.onExitComplete&&r.options.onExitComplete()})}scheduleRender(){this.members.forEach(t=>{t.instance&&t.scheduleRender(!1)})}removeLeadSnapshot(){this.lead&&this.lead.snapshot&&(this.lead.snapshot=void 0)}}function sS(e,t,n){let r="";const i=e.x.translate/t.x,s=e.y.translate/t.y,o=(n==null?void 0:n.z)||0;if((i||s||o)&&(r=`translate3d(${i}px, ${s}px, ${o}px) `),(t.x!==1||t.y!==1)&&(r+=`scale(${1/t.x}, ${1/t.y}) `),n){const{transformPerspective:u,rotate:c,rotateX:f,rotateY:d,skewX:g,skewY:v}=n;u&&(r=`perspective(${u}px) ${r}`),c&&(r+=`rotate(${c}deg) `),f&&(r+=`rotateX(${f}deg) `),d&&(r+=`rotateY(${d}deg) `),g&&(r+=`skewX(${g}deg) `),v&&(r+=`skewY(${v}deg) `)}const l=e.x.scale*t.x,a=e.y.scale*t.y;return(l!==1||a!==1)&&(r+=`scale(${l}, ${a})`),r||"none"}const Xt={type:"projectionFrame",totalNodes:0,resolvedTargetDeltas:0,recalculatedProjection:0},hr=typeof window<"u"&&window.MotionDebug!==void 0,Eo=["","X","Y","Z"],oS={visibility:"hidden"},Rf=1e3;let lS=0;function _o(e,t,n,r){const{latestValues:i}=t;i[e]&&(n[e]=i[e],t.setStaticValue(e,0),r&&(r[e]=0))}function Cm(e){if(e.hasCheckedOptimisedAppear=!0,e.root===e)return;const{visualElement:t}=e.options;if(!t)return;const n=jh(t);if(window.MotionHasOptimisedAnimation(n,"transform")){const{layout:i,layoutId:s}=e.options;window.MotionCancelOptimisedAnimation(n,"transform",U,!(i||s))}const{parent:r}=e;r&&!r.hasCheckedOptimisedAppear&&Cm(r)}function Pm({attachResizeListener:e,defaultParent:t,measureScroll:n,checkIsScrollRoot:r,resetTransform:i}){return class{constructor(o={},l=t==null?void 0:t()){this.id=lS++,this.animationId=0,this.children=new Set,this.options={},this.isTreeAnimating=!1,this.isAnimationBlocked=!1,this.isLayoutDirty=!1,this.isProjectionDirty=!1,this.isSharedProjectionDirty=!1,this.isTransformDirty=!1,this.updateManuallyBlocked=!1,this.updateBlockedByResize=!1,this.isUpdating=!1,this.isSVG=!1,this.needsReset=!1,this.shouldResetTransform=!1,this.hasCheckedOptimisedAppear=!1,this.treeScale={x:1,y:1},this.eventHandlers=new Map,this.hasTreeAnimated=!1,this.updateScheduled=!1,this.scheduleUpdate=()=>this.update(),this.projectionUpdateScheduled=!1,this.checkUpdateFailed=()=>{this.isUpdating&&(this.isUpdating=!1,this.clearAllSnapshots())},this.updateProjection=()=>{this.projectionUpdateScheduled=!1,hr&&(Xt.totalNodes=Xt.resolvedTargetDeltas=Xt.recalculatedProjection=0),this.nodes.forEach(cS),this.nodes.forEach(mS),this.nodes.forEach(gS),this.nodes.forEach(fS),hr&&window.MotionDebug.record(Xt)},this.resolvedRelativeTargetAt=0,this.hasProjected=!1,this.isVisible=!0,this.animationProgress=0,this.sharedNodes=new Map,this.latestValues=o,this.root=l?l.root||l:this,this.path=l?[...l.path,l]:[],this.parent=l,this.depth=l?l.depth+1:0;for(let a=0;athis.root.updateBlockedByResize=!1;e(o,()=>{this.root.updateBlockedByResize=!0,f&&f(),f=Xx(d,250),Wi.hasAnimatedSinceResize&&(Wi.hasAnimatedSinceResize=!1,this.nodes.forEach(Mf))})}a&&this.root.registerSharedNode(a,this),this.options.animate!==!1&&c&&(a||u)&&this.addEventListener("didUpdate",({delta:f,hasLayoutChanged:d,hasRelativeTargetChanged:g,layout:v})=>{if(this.isTreeAnimationBlocked()){this.target=void 0,this.relativeTarget=void 0;return}const y=this.options.transition||c.getDefaultTransition()||wS,{onLayoutAnimationStart:k,onLayoutAnimationComplete:p}=c.getProps(),h=!this.targetLayout||!Tm(this.targetLayout,v)||g,m=!d&&g;if(this.options.layoutRoot||this.resumeFrom&&this.resumeFrom.instance||m||d&&(h||!this.currentAnimation)){this.resumeFrom&&(this.resumingFrom=this.resumeFrom,this.resumingFrom.resumingFrom=void 0),this.setAnimationOrigin(f,m);const S={...ba(y,"layout"),onPlay:k,onComplete:p};(c.shouldReduceMotion||this.options.layoutRoot)&&(S.delay=0,S.type=!1),this.startAnimation(S)}else d||Mf(this),this.isLead()&&this.options.onExitComplete&&this.options.onExitComplete();this.targetLayout=v})}unmount(){this.options.layoutId&&this.willUpdate(),this.root.nodes.remove(this);const o=this.getStack();o&&o.remove(this),this.parent&&this.parent.children.delete(this),this.instance=void 0,Ot(this.updateProjection)}blockUpdate(){this.updateManuallyBlocked=!0}unblockUpdate(){this.updateManuallyBlocked=!1}isUpdateBlocked(){return this.updateManuallyBlocked||this.updateBlockedByResize}isTreeAnimationBlocked(){return this.isAnimationBlocked||this.parent&&this.parent.isTreeAnimationBlocked()||!1}startUpdate(){this.isUpdateBlocked()||(this.isUpdating=!0,this.nodes&&this.nodes.forEach(vS),this.animationId++)}getTransformTemplate(){const{visualElement:o}=this.options;return o&&o.getProps().transformTemplate}willUpdate(o=!0){if(this.root.hasTreeAnimated=!0,this.root.isUpdateBlocked()){this.options.onExitComplete&&this.options.onExitComplete();return}if(window.MotionCancelOptimisedAnimation&&!this.hasCheckedOptimisedAppear&&Cm(this),!this.root.isUpdating&&this.root.startUpdate(),this.isLayoutDirty)return;this.isLayoutDirty=!0;for(let c=0;c{this.isLayoutDirty?this.root.didUpdate():this.root.checkUpdateFailed()})}updateSnapshot(){this.snapshot||!this.instance||(this.snapshot=this.measure())}updateLayout(){if(!this.instance||(this.updateScroll(),!(this.options.alwaysMeasureLayout&&this.isLead())&&!this.isLayoutDirty))return;if(this.resumeFrom&&!this.resumeFrom.instance)for(let a=0;a{const w=S/1e3;jf(f.x,o.x,w),jf(f.y,o.y,w),this.setTargetDelta(f),this.relativeTarget&&this.relativeTargetOrigin&&this.layout&&this.relativeParent&&this.relativeParent.layout&&(_r(d,this.layout.layoutBox,this.relativeParent.layout.layoutBox),xS(this.relativeTarget,this.relativeTargetOrigin,d,w),m&&rS(this.relativeTarget,m)&&(this.isProjectionDirty=!1),m||(m=Z()),Ve(m,this.relativeTarget)),y&&(this.animationValues=c,bx(c,u,this.latestValues,w,h,p)),this.root.scheduleUpdateProjection(),this.scheduleRender(),this.animationProgress=w},this.mixTargetDelta(this.options.layoutRoot?1e3:0)}startAnimation(o){this.notifyListeners("animationStart"),this.currentAnimation&&this.currentAnimation.stop(),this.resumingFrom&&this.resumingFrom.currentAnimation&&this.resumingFrom.currentAnimation.stop(),this.pendingAnimation&&(Ot(this.pendingAnimation),this.pendingAnimation=void 0),this.pendingAnimation=U.update(()=>{Wi.hasAnimatedSinceResize=!0,this.currentAnimation=Kx(0,Rf,{...o,onUpdate:l=>{this.mixTargetDelta(l),o.onUpdate&&o.onUpdate(l)},onComplete:()=>{o.onComplete&&o.onComplete(),this.completeAnimation()}}),this.resumingFrom&&(this.resumingFrom.currentAnimation=this.currentAnimation),this.pendingAnimation=void 0})}completeAnimation(){this.resumingFrom&&(this.resumingFrom.currentAnimation=void 0,this.resumingFrom.preserveOpacity=void 0);const o=this.getStack();o&&o.exitAnimationComplete(),this.resumingFrom=this.currentAnimation=this.animationValues=void 0,this.notifyListeners("animationComplete")}finishAnimation(){this.currentAnimation&&(this.mixTargetDelta&&this.mixTargetDelta(Rf),this.currentAnimation.stop()),this.completeAnimation()}applyTransformsToTarget(){const o=this.getLead();let{targetWithTransforms:l,target:a,layout:u,latestValues:c}=o;if(!(!l||!a||!u)){if(this!==o&&this.layout&&u&&Em(this.options.animationType,this.layout.layoutBox,u.layoutBox)){a=this.target||Z();const f=Me(this.layout.layoutBox.x);a.x.min=o.target.x.min,a.x.max=a.x.min+f;const d=Me(this.layout.layoutBox.y);a.y.min=o.target.y.min,a.y.max=a.y.min+d}Ve(l,a),Rn(l,c),Er(this.projectionDeltaWithTransform,this.layoutCorrected,l,c)}}registerSharedNode(o,l){this.sharedNodes.has(o)||this.sharedNodes.set(o,new iS),this.sharedNodes.get(o).add(l);const u=l.options.initialPromotionConfig;l.promote({transition:u?u.transition:void 0,preserveFollowOpacity:u&&u.shouldPreserveFollowOpacity?u.shouldPreserveFollowOpacity(l):void 0})}isLead(){const o=this.getStack();return o?o.lead===this:!0}getLead(){var o;const{layoutId:l}=this.options;return l?((o=this.getStack())===null||o===void 0?void 0:o.lead)||this:this}getPrevLead(){var o;const{layoutId:l}=this.options;return l?(o=this.getStack())===null||o===void 0?void 0:o.prevLead:void 0}getStack(){const{layoutId:o}=this.options;if(o)return this.root.sharedNodes.get(o)}promote({needsReset:o,transition:l,preserveFollowOpacity:a}={}){const u=this.getStack();u&&u.promote(this,a),o&&(this.projectionDelta=void 0,this.needsReset=!0),l&&this.setOptions({transition:l})}relegate(){const o=this.getStack();return o?o.relegate(this):!1}resetSkewAndRotation(){const{visualElement:o}=this.options;if(!o)return;let l=!1;const{latestValues:a}=o;if((a.z||a.rotate||a.rotateX||a.rotateY||a.rotateZ||a.skewX||a.skewY)&&(l=!0),!l)return;const u={};a.z&&_o("z",o,u,this.animationValues);for(let c=0;c{var l;return(l=o.currentAnimation)===null||l===void 0?void 0:l.stop()}),this.root.nodes.forEach(Df),this.root.sharedNodes.clear()}}}function aS(e){e.updateLayout()}function uS(e){var t;const n=((t=e.resumeFrom)===null||t===void 0?void 0:t.snapshot)||e.snapshot;if(e.isLead()&&e.layout&&n&&e.hasListeners("didUpdate")){const{layoutBox:r,measuredBox:i}=e.layout,{animationType:s}=e.options,o=n.source!==e.layout.source;s==="size"?Oe(f=>{const d=o?n.measuredBox[f]:n.layoutBox[f],g=Me(d);d.min=r[f].min,d.max=d.min+g}):Em(s,n.layoutBox,r)&&Oe(f=>{const d=o?n.measuredBox[f]:n.layoutBox[f],g=Me(r[f]);d.max=d.min+g,e.relativeTarget&&!e.currentAnimation&&(e.isProjectionDirty=!0,e.relativeTarget[f].max=e.relativeTarget[f].min+g)});const l=_n();Er(l,r,n.layoutBox);const a=_n();o?Er(a,e.applyTransform(i,!0),n.measuredBox):Er(a,r,n.layoutBox);const u=!km(l);let c=!1;if(!e.resumeFrom){const f=e.getClosestProjectingParent();if(f&&!f.resumeFrom){const{snapshot:d,layout:g}=f;if(d&&g){const v=Z();_r(v,n.layoutBox,d.layoutBox);const y=Z();_r(y,r,g.layoutBox),Tm(v,y)||(c=!0),f.options.layoutRoot&&(e.relativeTarget=y,e.relativeTargetOrigin=v,e.relativeParent=f)}}}e.notifyListeners("didUpdate",{layout:r,snapshot:n,delta:a,layoutDelta:l,hasLayoutChanged:u,hasRelativeTargetChanged:c})}else if(e.isLead()){const{onExitComplete:r}=e.options;r&&r()}e.options.transition=void 0}function cS(e){hr&&Xt.totalNodes++,e.parent&&(e.isProjecting()||(e.isProjectionDirty=e.parent.isProjectionDirty),e.isSharedProjectionDirty||(e.isSharedProjectionDirty=!!(e.isProjectionDirty||e.parent.isProjectionDirty||e.parent.isSharedProjectionDirty)),e.isTransformDirty||(e.isTransformDirty=e.parent.isTransformDirty))}function fS(e){e.isProjectionDirty=e.isSharedProjectionDirty=e.isTransformDirty=!1}function dS(e){e.clearSnapshot()}function Df(e){e.clearMeasurements()}function pS(e){e.isLayoutDirty=!1}function hS(e){const{visualElement:t}=e.options;t&&t.getProps().onBeforeLayoutMeasure&&t.notify("BeforeLayoutMeasure"),e.resetTransform()}function Mf(e){e.finishAnimation(),e.targetDelta=e.relativeTarget=e.target=void 0,e.isProjectionDirty=!0}function mS(e){e.resolveTargetDelta()}function gS(e){e.calcProjection()}function vS(e){e.resetSkewAndRotation()}function yS(e){e.removeLeadSnapshot()}function jf(e,t,n){e.translate=H(t.translate,0,n),e.scale=H(t.scale,1,n),e.origin=t.origin,e.originPoint=t.originPoint}function Lf(e,t,n,r){e.min=H(t.min,n.min,r),e.max=H(t.max,n.max,r)}function xS(e,t,n,r){Lf(e.x,t.x,n.x,r),Lf(e.y,t.y,n.y,r)}function SS(e){return e.animationValues&&e.animationValues.opacityExit!==void 0}const wS={duration:.45,ease:[.4,0,.1,1]},Vf=e=>typeof navigator<"u"&&navigator.userAgent&&navigator.userAgent.toLowerCase().includes(e),Of=Vf("applewebkit/")&&!Vf("chrome/")?Math.round:Re;function Nf(e){e.min=Of(e.min),e.max=Of(e.max)}function kS(e){Nf(e.x),Nf(e.y)}function Em(e,t,n){return e==="position"||e==="preserve-aspect"&&!Px(_f(t),_f(n),.2)}function TS(e){var t;return e!==e.root&&((t=e.scroll)===null||t===void 0?void 0:t.wasRoot)}const CS=Pm({attachResizeListener:(e,t)=>Xr(e,"resize",t),measureScroll:()=>({x:document.documentElement.scrollLeft||document.body.scrollLeft,y:document.documentElement.scrollTop||document.body.scrollTop}),checkIsScrollRoot:()=>!0}),Ao={current:void 0},_m=Pm({measureScroll:e=>({x:e.scrollLeft,y:e.scrollTop}),defaultParent:()=>{if(!Ao.current){const e=new CS({});e.mount(window),e.setOptions({layoutScroll:!0}),Ao.current=e}return Ao.current},resetTransform:(e,t)=>{e.style.transform=t!==void 0?t:"none"},checkIsScrollRoot:e=>window.getComputedStyle(e).position==="fixed"}),PS={pan:{Feature:Ux},drag:{Feature:Bx,ProjectionNode:_m,MeasureLayout:xm}};function If(e,t,n){const{props:r}=e;e.animationState&&r.whileHover&&e.animationState.setActive("whileHover",n==="Start");const i="onHover"+n,s=r[i];s&&U.postRender(()=>s(t,ii(t)))}class ES extends Bt{mount(){const{current:t}=this.node;t&&(this.unmount=P0(t,n=>(If(this.node,n,"Start"),r=>If(this.node,r,"End"))))}unmount(){}}class _S extends Bt{constructor(){super(...arguments),this.isActive=!1}onFocus(){let t=!1;try{t=this.node.current.matches(":focus-visible")}catch{t=!0}!t||!this.node.animationState||(this.node.animationState.setActive("whileFocus",!0),this.isActive=!0)}onBlur(){!this.isActive||!this.node.animationState||(this.node.animationState.setActive("whileFocus",!1),this.isActive=!1)}mount(){this.unmount=ri(Xr(this.node.current,"focus",()=>this.onFocus()),Xr(this.node.current,"blur",()=>this.onBlur()))}unmount(){}}function zf(e,t,n){const{props:r}=e;e.animationState&&r.whileTap&&e.animationState.setActive("whileTap",n==="Start");const i="onTap"+(n==="End"?"":n),s=r[i];s&&U.postRender(()=>s(t,ii(t)))}class AS extends Bt{mount(){const{current:t}=this.node;t&&(this.unmount=R0(t,n=>(zf(this.node,n,"Start"),(r,{success:i})=>zf(this.node,r,i?"End":"Cancel")),{useGlobalTarget:this.node.props.globalTapTarget}))}unmount(){}}const Il=new WeakMap,Ro=new WeakMap,RS=e=>{const t=Il.get(e.target);t&&t(e)},DS=e=>{e.forEach(RS)};function MS({root:e,...t}){const n=e||document;Ro.has(n)||Ro.set(n,{});const r=Ro.get(n),i=JSON.stringify(t);return r[i]||(r[i]=new IntersectionObserver(DS,{root:e,...t})),r[i]}function jS(e,t,n){const r=MS(t);return Il.set(e,n),r.observe(e),()=>{Il.delete(e),r.unobserve(e)}}const LS={some:0,all:1};class VS extends Bt{constructor(){super(...arguments),this.hasEnteredView=!1,this.isInView=!1}startObserver(){this.unmount();const{viewport:t={}}=this.node.getProps(),{root:n,margin:r,amount:i="some",once:s}=t,o={root:n?n.current:void 0,rootMargin:r,threshold:typeof i=="number"?i:LS[i]},l=a=>{const{isIntersecting:u}=a;if(this.isInView===u||(this.isInView=u,s&&!u&&this.hasEnteredView))return;u&&(this.hasEnteredView=!0),this.node.animationState&&this.node.animationState.setActive("whileInView",u);const{onViewportEnter:c,onViewportLeave:f}=this.node.getProps(),d=u?c:f;d&&d(a)};return jS(this.node.current,o,l)}mount(){this.startObserver()}update(){if(typeof IntersectionObserver>"u")return;const{props:t,prevProps:n}=this.node;["amount","margin","root"].some(OS(t,n))&&this.startObserver()}unmount(){}}function OS({viewport:e={}},{viewport:t={}}={}){return n=>e[n]!==t[n]}const NS={inView:{Feature:VS},tap:{Feature:AS},focus:{Feature:_S},hover:{Feature:ES}},IS={layout:{ProjectionNode:_m,MeasureLayout:xm}},zl={current:null},Am={current:!1};function zS(){if(Am.current=!0,!!Na)if(window.matchMedia){const e=window.matchMedia("(prefers-reduced-motion)"),t=()=>zl.current=e.matches;e.addListener(t),t()}else zl.current=!1}const FS=[...qh,de,Nt],BS=e=>FS.find(Jh(e)),Ff=new WeakMap;function US(e,t,n){for(const r in t){const i=t[r],s=n[r];if(he(i))e.addValue(r,i);else if(he(s))e.addValue(r,Qr(i,{owner:e}));else if(s!==i)if(e.hasValue(r)){const o=e.getValue(r);o.liveStyle===!0?o.jump(i):o.hasAnimated||o.set(i)}else{const o=e.getStaticValue(r);e.addValue(r,Qr(o!==void 0?o:i,{owner:e}))}}for(const r in n)t[r]===void 0&&e.removeValue(r);return t}const Bf=["AnimationStart","AnimationComplete","Update","BeforeLayoutMeasure","LayoutMeasure","LayoutAnimationStart","LayoutAnimationComplete"];class $S{scrapeMotionValuesFromProps(t,n,r){return{}}constructor({parent:t,props:n,presenceContext:r,reducedMotionConfig:i,blockInitialAnimation:s,visualState:o},l={}){this.current=null,this.children=new Set,this.isVariantNode=!1,this.isControllingVariants=!1,this.shouldReduceMotion=null,this.values=new Map,this.KeyframeResolver=uu,this.features={},this.valueSubscriptions=new Map,this.prevMotionValues={},this.events={},this.propEventSubscriptions={},this.notifyUpdate=()=>this.notify("Update",this.latestValues),this.render=()=>{this.current&&(this.triggerBuild(),this.renderInstance(this.current,this.renderState,this.props.style,this.projection))},this.renderScheduledAt=0,this.scheduleRender=()=>{const g=nt.now();this.renderScheduledAtthis.bindToMotionValue(r,n)),Am.current||zS(),this.shouldReduceMotion=this.reducedMotionConfig==="never"?!1:this.reducedMotionConfig==="always"?!0:zl.current,this.parent&&this.parent.children.add(this),this.update(this.props,this.presenceContext)}unmount(){Ff.delete(this.current),this.projection&&this.projection.unmount(),Ot(this.notifyUpdate),Ot(this.render),this.valueSubscriptions.forEach(t=>t()),this.valueSubscriptions.clear(),this.removeFromVariantTree&&this.removeFromVariantTree(),this.parent&&this.parent.children.delete(this);for(const t in this.events)this.events[t].clear();for(const t in this.features){const n=this.features[t];n&&(n.unmount(),n.isMounted=!1)}this.current=null}bindToMotionValue(t,n){this.valueSubscriptions.has(t)&&this.valueSubscriptions.get(t)();const r=fn.has(t),i=n.on("change",l=>{this.latestValues[t]=l,this.props.onUpdate&&U.preRender(this.notifyUpdate),r&&this.projection&&(this.projection.isTransformDirty=!0)}),s=n.on("renderRequest",this.scheduleRender);let o;window.MotionCheckAppearSync&&(o=window.MotionCheckAppearSync(this,t,n)),this.valueSubscriptions.set(t,()=>{i(),s(),o&&o(),n.owner&&n.stop()})}sortNodePosition(t){return!this.current||!this.sortInstanceNodePosition||this.type!==t.type?0:this.sortInstanceNodePosition(this.current,t.current)}updateFeatures(){let t="animation";for(t in Hn){const n=Hn[t];if(!n)continue;const{isEnabled:r,Feature:i}=n;if(!this.features[t]&&i&&r(this.props)&&(this.features[t]=new i(this)),this.features[t]){const s=this.features[t];s.isMounted?s.update():(s.mount(),s.isMounted=!0)}}}triggerBuild(){this.build(this.renderState,this.latestValues,this.props)}measureViewportBox(){return this.current?this.measureInstanceViewportBox(this.current,this.props):Z()}getStaticValue(t){return this.latestValues[t]}setStaticValue(t,n){this.latestValues[t]=n}update(t,n){(t.transformTemplate||this.props.transformTemplate)&&this.scheduleRender(),this.prevProps=this.props,this.props=t,this.prevPresenceContext=this.presenceContext,this.presenceContext=n;for(let r=0;rn.variantChildren.delete(t)}addValue(t,n){const r=this.values.get(t);n!==r&&(r&&this.removeValue(t),this.bindToMotionValue(t,n),this.values.set(t,n),this.latestValues[t]=n.get())}removeValue(t){this.values.delete(t);const n=this.valueSubscriptions.get(t);n&&(n(),this.valueSubscriptions.delete(t)),delete this.latestValues[t],this.removeValueFromRenderState(t,this.renderState)}hasValue(t){return this.values.has(t)}getValue(t,n){if(this.props.values&&this.props.values[t])return this.props.values[t];let r=this.values.get(t);return r===void 0&&n!==void 0&&(r=Qr(n===null?void 0:n,{owner:this}),this.addValue(t,r)),r}readValue(t,n){var r;let i=this.latestValues[t]!==void 0||!this.current?this.latestValues[t]:(r=this.getBaseTargetFromProps(this.props,t))!==null&&r!==void 0?r:this.readValueFromInstance(this.current,t,this.options);return i!=null&&(typeof i=="string"&&(Zh(i)||Uh(i))?i=parseFloat(i):!BS(i)&&Nt.test(n)&&(i=Qh(t,n)),this.setBaseTarget(t,he(i)?i.get():i)),he(i)?i.get():i}setBaseTarget(t,n){this.baseTarget[t]=n}getBaseTarget(t){var n;const{initial:r}=this.props;let i;if(typeof r=="string"||typeof r=="object"){const o=Wa(this.props,r,(n=this.presenceContext)===null||n===void 0?void 0:n.custom);o&&(i=o[t])}if(r&&i!==void 0)return i;const s=this.getBaseTargetFromProps(this.props,t);return s!==void 0&&!he(s)?s:this.initialValues[t]!==void 0&&i===void 0?void 0:this.baseTarget[t]}on(t,n){return this.events[t]||(this.events[t]=new ru),this.events[t].add(n)}notify(t,...n){this.events[t]&&this.events[t].notify(...n)}}class Rm extends $S{constructor(){super(...arguments),this.KeyframeResolver=em}sortInstanceNodePosition(t,n){return t.compareDocumentPosition(n)&2?1:-1}getBaseTargetFromProps(t,n){return t.style?t.style[n]:void 0}removeValueFromRenderState(t,{vars:n,style:r}){delete n[t],delete r[t]}handleChildMotionValue(){this.childSubscription&&(this.childSubscription(),delete this.childSubscription);const{children:t}=this.props;he(t)&&(this.childSubscription=t.on("change",n=>{this.current&&(this.current.textContent=`${n}`)}))}}function WS(e){return window.getComputedStyle(e)}class HS extends Rm{constructor(){super(...arguments),this.type="html",this.renderInstance=gh}readValueFromInstance(t,n){if(fn.has(n)){const r=au(n);return r&&r.default||0}else{const r=WS(t),i=(ph(n)?r.getPropertyValue(n):r[n])||0;return typeof i=="string"?i.trim():i}}measureInstanceViewportBox(t,{transformPagePoint:n}){return vm(t,n)}build(t,n,r){Ga(t,n,r.transformTemplate)}scrapeMotionValuesFromProps(t,n,r){return Za(t,n,r)}}class KS extends Rm{constructor(){super(...arguments),this.type="svg",this.isSVGTag=!1,this.measureInstanceViewportBox=Z}getBaseTargetFromProps(t,n){return t[n]}readValueFromInstance(t,n){if(fn.has(n)){const r=au(n);return r&&r.default||0}return n=vh.has(n)?n:Ba(n),t.getAttribute(n)}scrapeMotionValuesFromProps(t,n,r){return Sh(t,n,r)}build(t,n,r){Qa(t,n,this.isSVGTag,r.transformTemplate)}renderInstance(t,n,r,i){yh(t,n,r,i)}mount(t){this.isSVGTag=Xa(t.tagName),super.mount(t)}}const GS=(e,t)=>$a(e)?new KS(t):new HS(t,{allowProjection:e!==A.Fragment}),QS=y0({...gx,...NS,...PS,...IS},GS),Ut=Ly(QS);function YS({action:e}){const t=e.success;return x.jsxs(Ut.div,{initial:{opacity:0,y:8},animate:{opacity:1,y:0},exit:{opacity:0,y:-8},transition:{duration:.2},style:{background:"var(--bg-surface)",border:`1px solid ${t?"var(--brand-yellow)":"var(--severity-critical)"}`,borderRadius:"var(--radius-md)",padding:"var(--space-5)",display:"flex",alignItems:"center",gap:"var(--space-4)"},children:[x.jsx("div",{style:{fontSize:28,color:t?"var(--brand-yellow)":"var(--severity-critical)"},children:t?"✓":"✗"}),x.jsxs("div",{children:[x.jsx("div",{style:{fontWeight:700},children:e.message}),e.new_status&&e.timestamp&&x.jsxs("div",{style:{fontSize:"var(--fs-small)",color:"var(--text-muted)"},children:["New status: ",e.new_status," · ",new Date(e.timestamp).toLocaleTimeString()]})]})]})}const XS={soc2:"SOC 2",iso27001:"ISO 27001",hipaa:"HIPAA",iso42001:"ISO 42001",eu_ai_act:"EU AI Act",ai_governance:"AI Governance"};function ZS({score:e}){const t=e.grade==="A"?"var(--brand-yellow)":e.grade==="B"?"var(--severity-low)":e.grade==="C"?"var(--severity-medium)":"var(--severity-critical)";return x.jsxs(Ut.div,{initial:{opacity:0,y:8},animate:{opacity:1,y:0},exit:{opacity:0,y:-8},transition:{duration:.2},style:{background:"var(--bg-surface)",border:"1px solid var(--border-card)",borderRadius:"var(--radius-md)",padding:"var(--space-5)"},children:[x.jsx("h2",{style:{margin:0,fontSize:"var(--fs-heading)",fontWeight:700},children:XS[e.framework]??e.framework}),x.jsxs("div",{style:{display:"flex",alignItems:"baseline",gap:"var(--space-3)",marginTop:"var(--space-3)"},children:[x.jsxs("span",{style:{fontSize:56,fontWeight:700,color:t,lineHeight:1},children:[e.score_percentage.toFixed(0),x.jsx("span",{style:{fontSize:24,color:"var(--text-muted)"},children:"%"})]}),x.jsx("span",{style:{fontSize:28,fontWeight:700,color:t},children:e.grade})]}),x.jsxs("div",{style:{marginTop:"var(--space-4)",display:"grid",gridTemplateColumns:"repeat(4, 1fr)",gap:"var(--space-2)",fontSize:"var(--fs-small)"},children:[x.jsxs("div",{children:[x.jsx("div",{style:{color:"var(--text-muted)"},children:"Passing"}),x.jsx("div",{style:{color:"var(--brand-yellow)",fontWeight:700},children:e.passing})]}),x.jsxs("div",{children:[x.jsx("div",{style:{color:"var(--text-muted)"},children:"Failing"}),x.jsx("div",{style:{color:"var(--severity-critical)",fontWeight:700},children:e.failing})]}),x.jsxs("div",{children:[x.jsx("div",{style:{color:"var(--text-muted)"},children:"Partial"}),x.jsx("div",{style:{color:"var(--severity-medium)",fontWeight:700},children:e.partial})]}),x.jsxs("div",{children:[x.jsx("div",{style:{color:"var(--text-muted)"},children:"Findings"}),x.jsx("div",{style:{fontWeight:700},children:e.total_findings})]})]})]})}const Uf={pass:"var(--brand-yellow)",fail:"var(--severity-critical)",partial:"var(--severity-medium)",not_assessed:"var(--text-subtle)",requires_policy:"var(--severity-low)"};function bS({controls:e}){return x.jsxs(Ut.div,{initial:{opacity:0,y:8},animate:{opacity:1,y:0},exit:{opacity:0,y:-8},transition:{duration:.2},style:{background:"var(--bg-surface)",border:"1px solid var(--border-card)",borderRadius:"var(--radius-md)",padding:"var(--space-5)"},children:[x.jsxs("h2",{style:{margin:0,fontSize:"var(--fs-heading)",fontWeight:700},children:[e.length," control",e.length===1?"":"s"]}),x.jsx("div",{style:{display:"flex",flexDirection:"column",gap:"var(--space-2)",marginTop:"var(--space-4)"},children:e.map(t=>x.jsxs("div",{style:{display:"grid",gridTemplateColumns:"auto 1fr auto auto auto",gap:"var(--space-3)",alignItems:"center",padding:"var(--space-3)",background:"var(--bg-surface-raised)",borderRadius:"var(--radius-sm)",borderLeft:`2px solid ${Uf[t.overall_status]??"transparent"}`},children:[x.jsx("span",{style:{fontWeight:700,color:"var(--brand-purple)"},children:t.control_id}),x.jsx("span",{style:{color:"var(--text-primary)",whiteSpace:"nowrap",overflow:"hidden",textOverflow:"ellipsis"},children:t.title}),x.jsxs("span",{style:{fontSize:"var(--fs-small)",color:"var(--brand-yellow)"},children:[t.pass_count," pass"]}),x.jsxs("span",{style:{fontSize:"var(--fs-small)",color:"var(--severity-critical)"},children:[t.fail_count," fail"]}),x.jsx("span",{style:{fontSize:"var(--fs-small)",color:Uf[t.overall_status]??"var(--text-muted)",textTransform:"uppercase",letterSpacing:"0.04em"},children:t.overall_status})]},t.control_id))})]})}const JS={critical:{fg:"var(--severity-critical)",bg:"var(--severity-critical-bg)"},high:{fg:"var(--severity-high)",bg:"var(--severity-high-bg)"},medium:{fg:"var(--severity-medium)",bg:"var(--severity-medium-bg)"},low:{fg:"var(--severity-low)",bg:"var(--severity-low-bg)"},info:{fg:"var(--text-muted)",bg:"rgba(255,255,255,0.08)"}};function Dm({severity:e}){const{fg:t,bg:n}=JS[e];return x.jsx("span",{style:{display:"inline-block",padding:"2px 8px",background:n,color:t,borderRadius:"var(--radius-sm)",fontSize:"var(--fs-small)",fontWeight:700,textTransform:"uppercase",letterSpacing:"0.04em"},children:e})}function qS({finding:e}){return x.jsxs(Ut.div,{initial:{opacity:0,y:8},animate:{opacity:1,y:0},exit:{opacity:0,y:-8},transition:{duration:.2},style:{background:"var(--bg-surface)",border:"1px solid var(--border-card)",borderRadius:"var(--radius-md)",padding:"var(--space-5)",borderLeft:e.severity==="critical"?"2px solid var(--severity-critical)":"1px solid var(--border-card)"},children:[x.jsxs("div",{style:{display:"flex",alignItems:"center",gap:"var(--space-3)"},children:[x.jsx(Dm,{severity:e.severity}),x.jsxs("span",{style:{color:"var(--text-muted)",fontSize:"var(--fs-small)"},children:[e.id," · ",e.cloud_provider.toUpperCase()," · ",e.domain]})]}),x.jsx("h2",{style:{margin:"var(--space-3) 0 0 0",fontSize:"var(--fs-title)",fontWeight:700},children:e.title}),x.jsx("p",{style:{marginTop:"var(--space-3)",lineHeight:1.5},children:e.description}),x.jsxs("div",{style:{marginTop:"var(--space-4)",display:"flex",gap:"var(--space-5)",flexWrap:"wrap"},children:[e.soc2_controls.length>0&&x.jsxs("div",{children:[x.jsx("div",{style:{fontSize:"var(--fs-small)",color:"var(--text-muted)",textTransform:"uppercase",letterSpacing:"0.04em",marginBottom:4},children:"SOC 2"}),x.jsx("div",{style:{color:"var(--brand-purple)",fontSize:"var(--fs-small)"},children:e.soc2_controls.join(", ")})]}),e.iso27001_controls.length>0&&x.jsxs("div",{children:[x.jsx("div",{style:{fontSize:"var(--fs-small)",color:"var(--text-muted)",textTransform:"uppercase",letterSpacing:"0.04em",marginBottom:4},children:"ISO 27001"}),x.jsx("div",{style:{color:"var(--severity-medium)",fontSize:"var(--fs-small)"},children:e.iso27001_controls.join(", ")})]}),e.hipaa_controls.length>0&&x.jsxs("div",{children:[x.jsx("div",{style:{fontSize:"var(--fs-small)",color:"var(--text-muted)",textTransform:"uppercase",letterSpacing:"0.04em",marginBottom:4},children:"HIPAA"}),x.jsx("div",{style:{color:"var(--severity-high)",fontSize:"var(--fs-small)"},children:e.hipaa_controls.join(", ")})]})]}),x.jsxs("div",{style:{marginTop:"var(--space-4)",padding:"var(--space-3)",background:"var(--bg-surface-raised)",borderRadius:"var(--radius-sm)",fontSize:"var(--fs-small)"},children:[x.jsx("strong",{style:{color:"var(--brand-yellow)"},children:"Resource:"})," ",x.jsx("span",{style:{wordBreak:"break-all",color:"var(--text-muted)"},children:e.resource_id})]}),e.remediation&&x.jsxs("div",{style:{marginTop:"var(--space-3)",color:"var(--text-primary)",fontSize:"var(--fs-small)"},children:[x.jsx("strong",{children:"Fix:"})," ",e.remediation]})]})}function ew({f:e}){const t=[];for(const n of e.soc2_controls.slice(0,2))t.push({label:`SOC 2 · ${n}`,color:"var(--brand-purple)"});for(const n of e.iso27001_controls.slice(0,1))t.push({label:`ISO · ${n}`,color:"var(--severity-medium)"});for(const n of e.hipaa_controls.slice(0,1))t.push({label:`HIPAA · ${n}`,color:"var(--severity-high)"});return x.jsx("div",{style:{display:"flex",gap:4,flexWrap:"wrap"},children:t.map(n=>x.jsx("span",{style:{fontSize:11,padding:"1px 6px",borderRadius:"var(--radius-sm)",background:"rgba(255,255,255,0.06)",color:n.color,whiteSpace:"nowrap"},children:n.label},n.label))})}function tw({findings:e}){return x.jsxs(Ut.div,{initial:{opacity:0,y:8},animate:{opacity:1,y:0},exit:{opacity:0,y:-8},transition:{duration:.2},style:{background:"var(--bg-surface)",border:"1px solid var(--border-card)",borderRadius:"var(--radius-md)",padding:"var(--space-5)"},children:[x.jsxs("h2",{style:{margin:0,fontSize:"var(--fs-heading)",fontWeight:700},children:[e.length," finding",e.length===1?"":"s"]}),x.jsx("div",{style:{display:"flex",flexDirection:"column",gap:"var(--space-2)",marginTop:"var(--space-4)"},children:e.map(t=>x.jsxs("div",{style:{display:"grid",gridTemplateColumns:"auto 1fr auto",alignItems:"center",gap:"var(--space-3)",padding:"var(--space-3)",background:"var(--bg-surface-raised)",borderRadius:"var(--radius-sm)",borderLeft:t.severity==="critical"?"2px solid var(--severity-critical)":"2px solid transparent"},children:[x.jsx(Dm,{severity:t.severity}),x.jsxs("div",{style:{minWidth:0},children:[x.jsx("div",{style:{fontWeight:500,whiteSpace:"nowrap",overflow:"hidden",textOverflow:"ellipsis"},children:t.title}),x.jsxs("div",{style:{display:"flex",gap:"var(--space-2)",alignItems:"center",marginTop:2},children:[x.jsxs("span",{style:{fontSize:"var(--fs-small)",color:"var(--text-muted)"},children:[t.cloud_provider.toUpperCase()," · ",t.domain]}),x.jsx(ew,{f:t})]})]}),x.jsx("div",{style:{fontSize:"var(--fs-small)",color:"var(--text-muted)"},children:t.status})]},t.id))})]})}const Mm={soc2:"SOC 2",iso27001:"ISO 27001",hipaa:"HIPAA",iso42001:"ISO 42001",eu_ai_act:"EU AI Act",ai_governance:"AI Gov"};function nw({s:e}){const t=Math.max(0,Math.min(100,e.score_percentage));return x.jsxs("div",{style:{display:"flex",flexDirection:"column",alignItems:"center",padding:"var(--space-3)",background:"var(--bg-surface-raised)",borderRadius:"var(--radius-sm)",position:"relative",overflow:"hidden",minHeight:180},children:[x.jsx("div",{"aria-hidden":!0,style:{position:"absolute",left:0,right:0,bottom:0,height:`${t}%`,background:"var(--brand-gradient)",opacity:.18}}),x.jsx("div",{style:{position:"relative",zIndex:1,fontSize:"var(--fs-small)",color:"var(--text-muted)",textTransform:"uppercase",letterSpacing:"0.04em"},children:Mm[e.framework]??e.framework}),x.jsxs("div",{style:{position:"relative",zIndex:1,fontSize:36,fontWeight:700,marginTop:"auto",color:"var(--text-primary)"},children:[e.score_percentage.toFixed(0),x.jsx("span",{style:{fontSize:16,color:"var(--text-muted)"},children:"%"})]}),x.jsxs("div",{style:{position:"relative",zIndex:1,fontSize:"var(--fs-small)",color:"var(--text-muted)",marginTop:4},children:["Grade ",e.grade]})]})}function rw({framework:e}){return x.jsxs("div",{style:{display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",padding:"var(--space-3)",background:"var(--bg-surface-raised)",borderRadius:"var(--radius-sm)",minHeight:180,opacity:.4},children:[x.jsx("div",{style:{fontSize:"var(--fs-small)",color:"var(--text-muted)",textTransform:"uppercase",letterSpacing:"0.04em"},children:Mm[e]??e}),x.jsx("div",{style:{fontSize:"var(--fs-small)",color:"var(--text-subtle)",marginTop:"var(--space-2)"},children:"not enabled"})]})}function iw({data:e}){const t=["soc2","iso27001","hipaa","iso42001","eu_ai_act"],n=new Map(e.frameworks.map(r=>[r.framework,r]));return x.jsxs(Ut.div,{initial:{opacity:0,y:8},animate:{opacity:1,y:0},exit:{opacity:0,y:-8},transition:{duration:.2},style:{background:"var(--bg-surface)",border:"1px solid var(--border-card)",borderRadius:"var(--radius-md)",padding:"var(--space-5)"},children:[x.jsx("h2",{style:{margin:0,fontSize:"var(--fs-heading)",fontWeight:700},children:"Compliance posture"}),x.jsx("div",{style:{marginTop:"var(--space-4)",display:"grid",gridTemplateColumns:`repeat(${t.length}, 1fr)`,gap:"var(--space-2)"},children:t.map(r=>{const i=n.get(r);return i?x.jsx(nw,{s:i},r):x.jsx(rw,{framework:r},r)})})]})}function sw({risk:e}){return x.jsxs(Ut.div,{initial:{opacity:0,y:8},animate:{opacity:1,y:0},exit:{opacity:0,y:-8},transition:{duration:.2},style:{background:"var(--bg-surface)",border:"1px solid var(--border-card)",borderRadius:"var(--radius-md)",padding:"var(--space-5)"},children:[x.jsxs("div",{style:{display:"flex",alignItems:"center",gap:"var(--space-3)"},children:[x.jsx("span",{style:{color:"var(--brand-purple)",fontWeight:700},children:e.risk_id}),x.jsxs("span",{style:{fontSize:"var(--fs-small)",color:"var(--text-muted)"},children:[e.risk_level.toUpperCase()," · score ",e.risk_score," · ",e.status]})]}),x.jsx("h2",{style:{margin:"var(--space-3) 0 0 0",fontSize:"var(--fs-title)",fontWeight:700},children:e.title}),x.jsx("p",{style:{marginTop:"var(--space-3)",lineHeight:1.5},children:e.description}),x.jsxs("div",{style:{marginTop:"var(--space-4)",display:"grid",gridTemplateColumns:"repeat(3, 1fr)",gap:"var(--space-3)",fontSize:"var(--fs-small)"},children:[x.jsxs("div",{children:[x.jsx("div",{style:{color:"var(--text-muted)"},children:"Likelihood"}),x.jsx("div",{children:e.likelihood})]}),x.jsxs("div",{children:[x.jsx("div",{style:{color:"var(--text-muted)"},children:"Impact"}),x.jsx("div",{children:e.impact})]}),x.jsxs("div",{children:[x.jsx("div",{style:{color:"var(--text-muted)"},children:"Treatment"}),x.jsx("div",{children:e.treatment})]})]}),e.treatment_plan&&x.jsxs("div",{style:{marginTop:"var(--space-4)",padding:"var(--space-3)",background:"var(--bg-surface-raised)",borderRadius:"var(--radius-sm)",fontSize:"var(--fs-small)"},children:[x.jsx("strong",{children:"Plan:"})," ",e.treatment_plan]})]})}const $f={high:"var(--severity-critical)",medium:"var(--severity-medium)",low:"var(--severity-low)"};function ow({risks:e}){return x.jsxs(Ut.div,{initial:{opacity:0,y:8},animate:{opacity:1,y:0},exit:{opacity:0,y:-8},transition:{duration:.2},style:{background:"var(--bg-surface)",border:"1px solid var(--border-card)",borderRadius:"var(--radius-md)",padding:"var(--space-5)"},children:[x.jsxs("h2",{style:{margin:0,fontSize:"var(--fs-heading)",fontWeight:700},children:[e.length," risk",e.length===1?"":"s"]}),x.jsx("div",{style:{display:"flex",flexDirection:"column",gap:"var(--space-2)",marginTop:"var(--space-4)"},children:e.map(t=>x.jsxs("div",{style:{display:"grid",gridTemplateColumns:"auto 1fr auto auto",gap:"var(--space-3)",alignItems:"center",padding:"var(--space-3)",background:"var(--bg-surface-raised)",borderRadius:"var(--radius-sm)",borderLeft:`2px solid ${$f[t.risk_level]??"transparent"}`},children:[x.jsx("span",{style:{fontSize:22,fontWeight:700,color:$f[t.risk_level]??"var(--text-primary)",minWidth:28,textAlign:"center"},children:t.risk_score}),x.jsx("span",{style:{color:"var(--text-primary)",whiteSpace:"nowrap",overflow:"hidden",textOverflow:"ellipsis"},children:t.title}),x.jsx("span",{style:{fontSize:"var(--fs-small)",color:"var(--text-muted)"},children:t.treatment}),x.jsx("span",{style:{fontSize:"var(--fs-small)",color:"var(--text-muted)"},children:t.status})]},t.risk_id))})]})}function lw(){const e=Ee(t=>t.activeCard);return x.jsx("div",{style:{height:"100%",display:"flex",alignItems:"flex-start"},children:x.jsx("div",{style:{width:"100%"},children:x.jsxs(Py,{mode:"wait",children:[e.kind==="findings_list"&&x.jsx(tw,{findings:e.data},"findings_list"),e.kind==="finding_detail"&&x.jsx(qS,{finding:e.data},`finding-${e.data.id}`),e.kind==="compliance_score"&&x.jsx(ZS,{score:e.data},`score-${e.data.framework}`),e.kind==="multi_framework"&&x.jsx(iw,{data:e.data},"multi"),e.kind==="control_summary"&&x.jsx(bS,{controls:e.data},"controls"),e.kind==="risk_list"&&x.jsx(ow,{risks:e.data},"risk_list"),e.kind==="risk_detail"&&x.jsx(sw,{risk:e.data},`risk-${e.data.risk_id}`),e.kind==="action"&&x.jsx(YS,{action:e.data},`action-${e.data.timestamp??Date.now()}`),e.kind==="none"&&x.jsx("div",{style:{color:"var(--text-subtle)",fontSize:"var(--fs-small)",padding:"var(--space-5)",textAlign:"center"},children:"Ask about findings, scores, controls, or risks to populate this panel."},"empty")]})})})}function aw(){const e=A.useRef(null),t=A.useRef(new Map),n=A.useRef(null),r=Ee(v=>v.setConnection),i=Ee(v=>v.setError),s=Ee(v=>v.appendTranscript),o=Ee(v=>v.updatePartialTranscript),l=Ee(v=>v.finalizeTranscript),a=Ee(v=>v.setActiveCard),u=Ee(v=>v.reset);A.useEffect(()=>()=>{var v;(v=e.current)==null||v.close()},[]);const c=async v=>{var k;const y=e.current;if(y)switch(v.type){case"session.created":r("connected");break;case"input_audio_buffer.speech_started":r("listening");break;case"input_audio_buffer.speech_stopped":r("thinking");break;case"conversation.item.input_audio_transcription.completed":{const p=v;s({id:`user-${p.item_id}`,who:"user",text:p.transcript,timestamp:Date.now()}),n.current=p.item_id;break}case"response.audio_transcript.delta":{const p=v,m=(t.current.get(p.item_id)??"")+p.delta;t.current.set(p.item_id,m);const S=`assistant-${p.item_id}`;Ee.getState().transcript.find(T=>T.id===S)?o(S,m):s({id:S,who:"assistant",text:m,timestamp:Date.now(),partial:!0}),r("speaking");break}case"response.audio_transcript.done":{const p=v;l(`assistant-${p.item_id}`),t.current.delete(p.item_id);break}case"response.function_call_arguments.done":{const p=v,h=await gy(p.name,p.arguments);y.send(cy(p.call_id,h.output)),y.send(fy());const m=vy(p.name,h.parsed);m&&a(m);break}case"response.done":r("connected");break;case"error":{i(((k=v.error)==null?void 0:k.message)??"Unknown realtime error");break}}},f=v=>{(v==="failed"||v==="disconnected"||v==="closed")&&r("idle")},d=async()=>{var v;i(null),r("connecting");try{const y=await hy({onEvent:c,onConnectionStateChange:f});e.current=y}catch(y){i(y instanceof Error?y.message:String(y)),(v=e.current)==null||v.close(),e.current=null}},g=()=>{var v;(v=e.current)==null||v.close(),e.current=null,u()};return x.jsxs("div",{style:{display:"flex",flexDirection:"column",height:"100vh"},children:[x.jsx(yy,{}),x.jsxs("main",{style:{flex:1,display:"grid",gridTemplateColumns:"minmax(0, 7fr) minmax(280px, 3fr)",gap:"var(--space-5)",padding:"var(--space-5)",minHeight:0},children:[x.jsx(lw,{}),x.jsx(Sy,{})]}),x.jsx("footer",{style:{padding:"var(--space-5) 0 var(--space-6) 0",display:"flex",justifyContent:"center",borderTop:"1px solid var(--border-subtle)"},children:x.jsx(xy,{onConnect:d,onDisconnect:g})})]})}Do.createRoot(document.getElementById("root")).render(x.jsx(lr.StrictMode,{children:x.jsx(aw,{})})); diff --git a/src/shasta/voice/web/dist/index.html b/src/shasta/voice/web/dist/index.html new file mode 100644 index 0000000..40311b2 --- /dev/null +++ b/src/shasta/voice/web/dist/index.html @@ -0,0 +1,16 @@ + + + + + + Shasta — Voice Console + + + + + + + +
+ + diff --git a/src/shasta/voice/web/index.html b/src/shasta/voice/web/index.html new file mode 100644 index 0000000..55d885f --- /dev/null +++ b/src/shasta/voice/web/index.html @@ -0,0 +1,15 @@ + + + + + + Shasta — Voice Console + + + + + +
+ + + diff --git a/src/shasta/voice/web/node_modules/.bin/baseline-browser-mapping b/src/shasta/voice/web/node_modules/.bin/baseline-browser-mapping new file mode 100644 index 0000000..7e4dd08 --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/baseline-browser-mapping @@ -0,0 +1,16 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*|*MINGW*|*MSYS*) + if command -v cygpath > /dev/null 2>&1; then + basedir=`cygpath -w "$basedir"` + fi + ;; +esac + +if [ -x "$basedir/node" ]; then + exec "$basedir/node" "$basedir/../baseline-browser-mapping/dist/cli.cjs" "$@" +else + exec node "$basedir/../baseline-browser-mapping/dist/cli.cjs" "$@" +fi diff --git a/src/shasta/voice/web/node_modules/.bin/baseline-browser-mapping.cmd b/src/shasta/voice/web/node_modules/.bin/baseline-browser-mapping.cmd new file mode 100644 index 0000000..724ac4d --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/baseline-browser-mapping.cmd @@ -0,0 +1,17 @@ +@ECHO off +GOTO start +:find_dp0 +SET dp0=%~dp0 +EXIT /b +:start +SETLOCAL +CALL :find_dp0 + +IF EXIST "%dp0%\node.exe" ( + SET "_prog=%dp0%\node.exe" +) ELSE ( + SET "_prog=node" + SET PATHEXT=%PATHEXT:;.JS;=;% +) + +endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\baseline-browser-mapping\dist\cli.cjs" %* diff --git a/src/shasta/voice/web/node_modules/.bin/baseline-browser-mapping.ps1 b/src/shasta/voice/web/node_modules/.bin/baseline-browser-mapping.ps1 new file mode 100644 index 0000000..049fe74 --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/baseline-browser-mapping.ps1 @@ -0,0 +1,28 @@ +#!/usr/bin/env pwsh +$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent + +$exe="" +if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { + # Fix case when both the Windows and Linux builds of Node + # are installed in the same directory + $exe=".exe" +} +$ret=0 +if (Test-Path "$basedir/node$exe") { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "$basedir/node$exe" "$basedir/../baseline-browser-mapping/dist/cli.cjs" $args + } else { + & "$basedir/node$exe" "$basedir/../baseline-browser-mapping/dist/cli.cjs" $args + } + $ret=$LASTEXITCODE +} else { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "node$exe" "$basedir/../baseline-browser-mapping/dist/cli.cjs" $args + } else { + & "node$exe" "$basedir/../baseline-browser-mapping/dist/cli.cjs" $args + } + $ret=$LASTEXITCODE +} +exit $ret diff --git a/src/shasta/voice/web/node_modules/.bin/browserslist b/src/shasta/voice/web/node_modules/.bin/browserslist new file mode 100644 index 0000000..60e71ad --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/browserslist @@ -0,0 +1,16 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*|*MINGW*|*MSYS*) + if command -v cygpath > /dev/null 2>&1; then + basedir=`cygpath -w "$basedir"` + fi + ;; +esac + +if [ -x "$basedir/node" ]; then + exec "$basedir/node" "$basedir/../browserslist/cli.js" "$@" +else + exec node "$basedir/../browserslist/cli.js" "$@" +fi diff --git a/src/shasta/voice/web/node_modules/.bin/browserslist.cmd b/src/shasta/voice/web/node_modules/.bin/browserslist.cmd new file mode 100644 index 0000000..f93c251 --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/browserslist.cmd @@ -0,0 +1,17 @@ +@ECHO off +GOTO start +:find_dp0 +SET dp0=%~dp0 +EXIT /b +:start +SETLOCAL +CALL :find_dp0 + +IF EXIST "%dp0%\node.exe" ( + SET "_prog=%dp0%\node.exe" +) ELSE ( + SET "_prog=node" + SET PATHEXT=%PATHEXT:;.JS;=;% +) + +endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\browserslist\cli.js" %* diff --git a/src/shasta/voice/web/node_modules/.bin/browserslist.ps1 b/src/shasta/voice/web/node_modules/.bin/browserslist.ps1 new file mode 100644 index 0000000..01e10a0 --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/browserslist.ps1 @@ -0,0 +1,28 @@ +#!/usr/bin/env pwsh +$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent + +$exe="" +if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { + # Fix case when both the Windows and Linux builds of Node + # are installed in the same directory + $exe=".exe" +} +$ret=0 +if (Test-Path "$basedir/node$exe") { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "$basedir/node$exe" "$basedir/../browserslist/cli.js" $args + } else { + & "$basedir/node$exe" "$basedir/../browserslist/cli.js" $args + } + $ret=$LASTEXITCODE +} else { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "node$exe" "$basedir/../browserslist/cli.js" $args + } else { + & "node$exe" "$basedir/../browserslist/cli.js" $args + } + $ret=$LASTEXITCODE +} +exit $ret diff --git a/src/shasta/voice/web/node_modules/.bin/esbuild b/src/shasta/voice/web/node_modules/.bin/esbuild new file mode 100644 index 0000000..63bb6d4 --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/esbuild @@ -0,0 +1,16 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*|*MINGW*|*MSYS*) + if command -v cygpath > /dev/null 2>&1; then + basedir=`cygpath -w "$basedir"` + fi + ;; +esac + +if [ -x "$basedir/node" ]; then + exec "$basedir/node" "$basedir/../esbuild/bin/esbuild" "$@" +else + exec node "$basedir/../esbuild/bin/esbuild" "$@" +fi diff --git a/src/shasta/voice/web/node_modules/.bin/esbuild.cmd b/src/shasta/voice/web/node_modules/.bin/esbuild.cmd new file mode 100644 index 0000000..cc920c5 --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/esbuild.cmd @@ -0,0 +1,17 @@ +@ECHO off +GOTO start +:find_dp0 +SET dp0=%~dp0 +EXIT /b +:start +SETLOCAL +CALL :find_dp0 + +IF EXIST "%dp0%\node.exe" ( + SET "_prog=%dp0%\node.exe" +) ELSE ( + SET "_prog=node" + SET PATHEXT=%PATHEXT:;.JS;=;% +) + +endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\esbuild\bin\esbuild" %* diff --git a/src/shasta/voice/web/node_modules/.bin/esbuild.ps1 b/src/shasta/voice/web/node_modules/.bin/esbuild.ps1 new file mode 100644 index 0000000..81ffbf9 --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/esbuild.ps1 @@ -0,0 +1,28 @@ +#!/usr/bin/env pwsh +$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent + +$exe="" +if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { + # Fix case when both the Windows and Linux builds of Node + # are installed in the same directory + $exe=".exe" +} +$ret=0 +if (Test-Path "$basedir/node$exe") { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "$basedir/node$exe" "$basedir/../esbuild/bin/esbuild" $args + } else { + & "$basedir/node$exe" "$basedir/../esbuild/bin/esbuild" $args + } + $ret=$LASTEXITCODE +} else { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "node$exe" "$basedir/../esbuild/bin/esbuild" $args + } else { + & "node$exe" "$basedir/../esbuild/bin/esbuild" $args + } + $ret=$LASTEXITCODE +} +exit $ret diff --git a/src/shasta/voice/web/node_modules/.bin/jsesc b/src/shasta/voice/web/node_modules/.bin/jsesc new file mode 100644 index 0000000..879c413 --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/jsesc @@ -0,0 +1,16 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*|*MINGW*|*MSYS*) + if command -v cygpath > /dev/null 2>&1; then + basedir=`cygpath -w "$basedir"` + fi + ;; +esac + +if [ -x "$basedir/node" ]; then + exec "$basedir/node" "$basedir/../jsesc/bin/jsesc" "$@" +else + exec node "$basedir/../jsesc/bin/jsesc" "$@" +fi diff --git a/src/shasta/voice/web/node_modules/.bin/jsesc.cmd b/src/shasta/voice/web/node_modules/.bin/jsesc.cmd new file mode 100644 index 0000000..eb41110 --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/jsesc.cmd @@ -0,0 +1,17 @@ +@ECHO off +GOTO start +:find_dp0 +SET dp0=%~dp0 +EXIT /b +:start +SETLOCAL +CALL :find_dp0 + +IF EXIST "%dp0%\node.exe" ( + SET "_prog=%dp0%\node.exe" +) ELSE ( + SET "_prog=node" + SET PATHEXT=%PATHEXT:;.JS;=;% +) + +endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\jsesc\bin\jsesc" %* diff --git a/src/shasta/voice/web/node_modules/.bin/jsesc.ps1 b/src/shasta/voice/web/node_modules/.bin/jsesc.ps1 new file mode 100644 index 0000000..6007e02 --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/jsesc.ps1 @@ -0,0 +1,28 @@ +#!/usr/bin/env pwsh +$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent + +$exe="" +if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { + # Fix case when both the Windows and Linux builds of Node + # are installed in the same directory + $exe=".exe" +} +$ret=0 +if (Test-Path "$basedir/node$exe") { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "$basedir/node$exe" "$basedir/../jsesc/bin/jsesc" $args + } else { + & "$basedir/node$exe" "$basedir/../jsesc/bin/jsesc" $args + } + $ret=$LASTEXITCODE +} else { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "node$exe" "$basedir/../jsesc/bin/jsesc" $args + } else { + & "node$exe" "$basedir/../jsesc/bin/jsesc" $args + } + $ret=$LASTEXITCODE +} +exit $ret diff --git a/src/shasta/voice/web/node_modules/.bin/json5 b/src/shasta/voice/web/node_modules/.bin/json5 new file mode 100644 index 0000000..abf72a4 --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/json5 @@ -0,0 +1,16 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*|*MINGW*|*MSYS*) + if command -v cygpath > /dev/null 2>&1; then + basedir=`cygpath -w "$basedir"` + fi + ;; +esac + +if [ -x "$basedir/node" ]; then + exec "$basedir/node" "$basedir/../json5/lib/cli.js" "$@" +else + exec node "$basedir/../json5/lib/cli.js" "$@" +fi diff --git a/src/shasta/voice/web/node_modules/.bin/json5.cmd b/src/shasta/voice/web/node_modules/.bin/json5.cmd new file mode 100644 index 0000000..95c137f --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/json5.cmd @@ -0,0 +1,17 @@ +@ECHO off +GOTO start +:find_dp0 +SET dp0=%~dp0 +EXIT /b +:start +SETLOCAL +CALL :find_dp0 + +IF EXIST "%dp0%\node.exe" ( + SET "_prog=%dp0%\node.exe" +) ELSE ( + SET "_prog=node" + SET PATHEXT=%PATHEXT:;.JS;=;% +) + +endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\json5\lib\cli.js" %* diff --git a/src/shasta/voice/web/node_modules/.bin/json5.ps1 b/src/shasta/voice/web/node_modules/.bin/json5.ps1 new file mode 100644 index 0000000..8700ddb --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/json5.ps1 @@ -0,0 +1,28 @@ +#!/usr/bin/env pwsh +$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent + +$exe="" +if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { + # Fix case when both the Windows and Linux builds of Node + # are installed in the same directory + $exe=".exe" +} +$ret=0 +if (Test-Path "$basedir/node$exe") { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "$basedir/node$exe" "$basedir/../json5/lib/cli.js" $args + } else { + & "$basedir/node$exe" "$basedir/../json5/lib/cli.js" $args + } + $ret=$LASTEXITCODE +} else { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "node$exe" "$basedir/../json5/lib/cli.js" $args + } else { + & "node$exe" "$basedir/../json5/lib/cli.js" $args + } + $ret=$LASTEXITCODE +} +exit $ret diff --git a/src/shasta/voice/web/node_modules/.bin/loose-envify b/src/shasta/voice/web/node_modules/.bin/loose-envify new file mode 100644 index 0000000..076f91b --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/loose-envify @@ -0,0 +1,16 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*|*MINGW*|*MSYS*) + if command -v cygpath > /dev/null 2>&1; then + basedir=`cygpath -w "$basedir"` + fi + ;; +esac + +if [ -x "$basedir/node" ]; then + exec "$basedir/node" "$basedir/../loose-envify/cli.js" "$@" +else + exec node "$basedir/../loose-envify/cli.js" "$@" +fi diff --git a/src/shasta/voice/web/node_modules/.bin/loose-envify.cmd b/src/shasta/voice/web/node_modules/.bin/loose-envify.cmd new file mode 100644 index 0000000..599576f --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/loose-envify.cmd @@ -0,0 +1,17 @@ +@ECHO off +GOTO start +:find_dp0 +SET dp0=%~dp0 +EXIT /b +:start +SETLOCAL +CALL :find_dp0 + +IF EXIST "%dp0%\node.exe" ( + SET "_prog=%dp0%\node.exe" +) ELSE ( + SET "_prog=node" + SET PATHEXT=%PATHEXT:;.JS;=;% +) + +endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\loose-envify\cli.js" %* diff --git a/src/shasta/voice/web/node_modules/.bin/loose-envify.ps1 b/src/shasta/voice/web/node_modules/.bin/loose-envify.ps1 new file mode 100644 index 0000000..eb866fc --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/loose-envify.ps1 @@ -0,0 +1,28 @@ +#!/usr/bin/env pwsh +$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent + +$exe="" +if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { + # Fix case when both the Windows and Linux builds of Node + # are installed in the same directory + $exe=".exe" +} +$ret=0 +if (Test-Path "$basedir/node$exe") { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "$basedir/node$exe" "$basedir/../loose-envify/cli.js" $args + } else { + & "$basedir/node$exe" "$basedir/../loose-envify/cli.js" $args + } + $ret=$LASTEXITCODE +} else { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "node$exe" "$basedir/../loose-envify/cli.js" $args + } else { + & "node$exe" "$basedir/../loose-envify/cli.js" $args + } + $ret=$LASTEXITCODE +} +exit $ret diff --git a/src/shasta/voice/web/node_modules/.bin/nanoid b/src/shasta/voice/web/node_modules/.bin/nanoid new file mode 100644 index 0000000..46220bd --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/nanoid @@ -0,0 +1,16 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*|*MINGW*|*MSYS*) + if command -v cygpath > /dev/null 2>&1; then + basedir=`cygpath -w "$basedir"` + fi + ;; +esac + +if [ -x "$basedir/node" ]; then + exec "$basedir/node" "$basedir/../nanoid/bin/nanoid.cjs" "$@" +else + exec node "$basedir/../nanoid/bin/nanoid.cjs" "$@" +fi diff --git a/src/shasta/voice/web/node_modules/.bin/nanoid.cmd b/src/shasta/voice/web/node_modules/.bin/nanoid.cmd new file mode 100644 index 0000000..9c40107 --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/nanoid.cmd @@ -0,0 +1,17 @@ +@ECHO off +GOTO start +:find_dp0 +SET dp0=%~dp0 +EXIT /b +:start +SETLOCAL +CALL :find_dp0 + +IF EXIST "%dp0%\node.exe" ( + SET "_prog=%dp0%\node.exe" +) ELSE ( + SET "_prog=node" + SET PATHEXT=%PATHEXT:;.JS;=;% +) + +endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\nanoid\bin\nanoid.cjs" %* diff --git a/src/shasta/voice/web/node_modules/.bin/nanoid.ps1 b/src/shasta/voice/web/node_modules/.bin/nanoid.ps1 new file mode 100644 index 0000000..d8a4d7a --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/nanoid.ps1 @@ -0,0 +1,28 @@ +#!/usr/bin/env pwsh +$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent + +$exe="" +if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { + # Fix case when both the Windows and Linux builds of Node + # are installed in the same directory + $exe=".exe" +} +$ret=0 +if (Test-Path "$basedir/node$exe") { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "$basedir/node$exe" "$basedir/../nanoid/bin/nanoid.cjs" $args + } else { + & "$basedir/node$exe" "$basedir/../nanoid/bin/nanoid.cjs" $args + } + $ret=$LASTEXITCODE +} else { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "node$exe" "$basedir/../nanoid/bin/nanoid.cjs" $args + } else { + & "node$exe" "$basedir/../nanoid/bin/nanoid.cjs" $args + } + $ret=$LASTEXITCODE +} +exit $ret diff --git a/src/shasta/voice/web/node_modules/.bin/parser b/src/shasta/voice/web/node_modules/.bin/parser new file mode 100644 index 0000000..7696ad4 --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/parser @@ -0,0 +1,16 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*|*MINGW*|*MSYS*) + if command -v cygpath > /dev/null 2>&1; then + basedir=`cygpath -w "$basedir"` + fi + ;; +esac + +if [ -x "$basedir/node" ]; then + exec "$basedir/node" "$basedir/../@babel/parser/bin/babel-parser.js" "$@" +else + exec node "$basedir/../@babel/parser/bin/babel-parser.js" "$@" +fi diff --git a/src/shasta/voice/web/node_modules/.bin/parser.cmd b/src/shasta/voice/web/node_modules/.bin/parser.cmd new file mode 100644 index 0000000..1ad5c81 --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/parser.cmd @@ -0,0 +1,17 @@ +@ECHO off +GOTO start +:find_dp0 +SET dp0=%~dp0 +EXIT /b +:start +SETLOCAL +CALL :find_dp0 + +IF EXIST "%dp0%\node.exe" ( + SET "_prog=%dp0%\node.exe" +) ELSE ( + SET "_prog=node" + SET PATHEXT=%PATHEXT:;.JS;=;% +) + +endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\@babel\parser\bin\babel-parser.js" %* diff --git a/src/shasta/voice/web/node_modules/.bin/parser.ps1 b/src/shasta/voice/web/node_modules/.bin/parser.ps1 new file mode 100644 index 0000000..8926517 --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/parser.ps1 @@ -0,0 +1,28 @@ +#!/usr/bin/env pwsh +$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent + +$exe="" +if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { + # Fix case when both the Windows and Linux builds of Node + # are installed in the same directory + $exe=".exe" +} +$ret=0 +if (Test-Path "$basedir/node$exe") { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "$basedir/node$exe" "$basedir/../@babel/parser/bin/babel-parser.js" $args + } else { + & "$basedir/node$exe" "$basedir/../@babel/parser/bin/babel-parser.js" $args + } + $ret=$LASTEXITCODE +} else { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "node$exe" "$basedir/../@babel/parser/bin/babel-parser.js" $args + } else { + & "node$exe" "$basedir/../@babel/parser/bin/babel-parser.js" $args + } + $ret=$LASTEXITCODE +} +exit $ret diff --git a/src/shasta/voice/web/node_modules/.bin/rollup b/src/shasta/voice/web/node_modules/.bin/rollup new file mode 100644 index 0000000..998fc16 --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/rollup @@ -0,0 +1,16 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*|*MINGW*|*MSYS*) + if command -v cygpath > /dev/null 2>&1; then + basedir=`cygpath -w "$basedir"` + fi + ;; +esac + +if [ -x "$basedir/node" ]; then + exec "$basedir/node" "$basedir/../rollup/dist/bin/rollup" "$@" +else + exec node "$basedir/../rollup/dist/bin/rollup" "$@" +fi diff --git a/src/shasta/voice/web/node_modules/.bin/rollup.cmd b/src/shasta/voice/web/node_modules/.bin/rollup.cmd new file mode 100644 index 0000000..b3f110b --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/rollup.cmd @@ -0,0 +1,17 @@ +@ECHO off +GOTO start +:find_dp0 +SET dp0=%~dp0 +EXIT /b +:start +SETLOCAL +CALL :find_dp0 + +IF EXIST "%dp0%\node.exe" ( + SET "_prog=%dp0%\node.exe" +) ELSE ( + SET "_prog=node" + SET PATHEXT=%PATHEXT:;.JS;=;% +) + +endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\rollup\dist\bin\rollup" %* diff --git a/src/shasta/voice/web/node_modules/.bin/rollup.ps1 b/src/shasta/voice/web/node_modules/.bin/rollup.ps1 new file mode 100644 index 0000000..10f657d --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/rollup.ps1 @@ -0,0 +1,28 @@ +#!/usr/bin/env pwsh +$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent + +$exe="" +if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { + # Fix case when both the Windows and Linux builds of Node + # are installed in the same directory + $exe=".exe" +} +$ret=0 +if (Test-Path "$basedir/node$exe") { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "$basedir/node$exe" "$basedir/../rollup/dist/bin/rollup" $args + } else { + & "$basedir/node$exe" "$basedir/../rollup/dist/bin/rollup" $args + } + $ret=$LASTEXITCODE +} else { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "node$exe" "$basedir/../rollup/dist/bin/rollup" $args + } else { + & "node$exe" "$basedir/../rollup/dist/bin/rollup" $args + } + $ret=$LASTEXITCODE +} +exit $ret diff --git a/src/shasta/voice/web/node_modules/.bin/semver b/src/shasta/voice/web/node_modules/.bin/semver new file mode 100644 index 0000000..97c5327 --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/semver @@ -0,0 +1,16 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*|*MINGW*|*MSYS*) + if command -v cygpath > /dev/null 2>&1; then + basedir=`cygpath -w "$basedir"` + fi + ;; +esac + +if [ -x "$basedir/node" ]; then + exec "$basedir/node" "$basedir/../semver/bin/semver.js" "$@" +else + exec node "$basedir/../semver/bin/semver.js" "$@" +fi diff --git a/src/shasta/voice/web/node_modules/.bin/semver.cmd b/src/shasta/voice/web/node_modules/.bin/semver.cmd new file mode 100644 index 0000000..9913fa9 --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/semver.cmd @@ -0,0 +1,17 @@ +@ECHO off +GOTO start +:find_dp0 +SET dp0=%~dp0 +EXIT /b +:start +SETLOCAL +CALL :find_dp0 + +IF EXIST "%dp0%\node.exe" ( + SET "_prog=%dp0%\node.exe" +) ELSE ( + SET "_prog=node" + SET PATHEXT=%PATHEXT:;.JS;=;% +) + +endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\semver\bin\semver.js" %* diff --git a/src/shasta/voice/web/node_modules/.bin/semver.ps1 b/src/shasta/voice/web/node_modules/.bin/semver.ps1 new file mode 100644 index 0000000..314717a --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/semver.ps1 @@ -0,0 +1,28 @@ +#!/usr/bin/env pwsh +$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent + +$exe="" +if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { + # Fix case when both the Windows and Linux builds of Node + # are installed in the same directory + $exe=".exe" +} +$ret=0 +if (Test-Path "$basedir/node$exe") { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "$basedir/node$exe" "$basedir/../semver/bin/semver.js" $args + } else { + & "$basedir/node$exe" "$basedir/../semver/bin/semver.js" $args + } + $ret=$LASTEXITCODE +} else { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "node$exe" "$basedir/../semver/bin/semver.js" $args + } else { + & "node$exe" "$basedir/../semver/bin/semver.js" $args + } + $ret=$LASTEXITCODE +} +exit $ret diff --git a/src/shasta/voice/web/node_modules/.bin/tsc b/src/shasta/voice/web/node_modules/.bin/tsc new file mode 100644 index 0000000..c4864b9 --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/tsc @@ -0,0 +1,16 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*|*MINGW*|*MSYS*) + if command -v cygpath > /dev/null 2>&1; then + basedir=`cygpath -w "$basedir"` + fi + ;; +esac + +if [ -x "$basedir/node" ]; then + exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@" +else + exec node "$basedir/../typescript/bin/tsc" "$@" +fi diff --git a/src/shasta/voice/web/node_modules/.bin/tsc.cmd b/src/shasta/voice/web/node_modules/.bin/tsc.cmd new file mode 100644 index 0000000..40bf128 --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/tsc.cmd @@ -0,0 +1,17 @@ +@ECHO off +GOTO start +:find_dp0 +SET dp0=%~dp0 +EXIT /b +:start +SETLOCAL +CALL :find_dp0 + +IF EXIST "%dp0%\node.exe" ( + SET "_prog=%dp0%\node.exe" +) ELSE ( + SET "_prog=node" + SET PATHEXT=%PATHEXT:;.JS;=;% +) + +endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\typescript\bin\tsc" %* diff --git a/src/shasta/voice/web/node_modules/.bin/tsc.ps1 b/src/shasta/voice/web/node_modules/.bin/tsc.ps1 new file mode 100644 index 0000000..112413b --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/tsc.ps1 @@ -0,0 +1,28 @@ +#!/usr/bin/env pwsh +$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent + +$exe="" +if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { + # Fix case when both the Windows and Linux builds of Node + # are installed in the same directory + $exe=".exe" +} +$ret=0 +if (Test-Path "$basedir/node$exe") { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "$basedir/node$exe" "$basedir/../typescript/bin/tsc" $args + } else { + & "$basedir/node$exe" "$basedir/../typescript/bin/tsc" $args + } + $ret=$LASTEXITCODE +} else { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "node$exe" "$basedir/../typescript/bin/tsc" $args + } else { + & "node$exe" "$basedir/../typescript/bin/tsc" $args + } + $ret=$LASTEXITCODE +} +exit $ret diff --git a/src/shasta/voice/web/node_modules/.bin/tsserver b/src/shasta/voice/web/node_modules/.bin/tsserver new file mode 100644 index 0000000..6c19ce3 --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/tsserver @@ -0,0 +1,16 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*|*MINGW*|*MSYS*) + if command -v cygpath > /dev/null 2>&1; then + basedir=`cygpath -w "$basedir"` + fi + ;; +esac + +if [ -x "$basedir/node" ]; then + exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@" +else + exec node "$basedir/../typescript/bin/tsserver" "$@" +fi diff --git a/src/shasta/voice/web/node_modules/.bin/tsserver.cmd b/src/shasta/voice/web/node_modules/.bin/tsserver.cmd new file mode 100644 index 0000000..57f851f --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/tsserver.cmd @@ -0,0 +1,17 @@ +@ECHO off +GOTO start +:find_dp0 +SET dp0=%~dp0 +EXIT /b +:start +SETLOCAL +CALL :find_dp0 + +IF EXIST "%dp0%\node.exe" ( + SET "_prog=%dp0%\node.exe" +) ELSE ( + SET "_prog=node" + SET PATHEXT=%PATHEXT:;.JS;=;% +) + +endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\typescript\bin\tsserver" %* diff --git a/src/shasta/voice/web/node_modules/.bin/tsserver.ps1 b/src/shasta/voice/web/node_modules/.bin/tsserver.ps1 new file mode 100644 index 0000000..249f417 --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/tsserver.ps1 @@ -0,0 +1,28 @@ +#!/usr/bin/env pwsh +$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent + +$exe="" +if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { + # Fix case when both the Windows and Linux builds of Node + # are installed in the same directory + $exe=".exe" +} +$ret=0 +if (Test-Path "$basedir/node$exe") { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "$basedir/node$exe" "$basedir/../typescript/bin/tsserver" $args + } else { + & "$basedir/node$exe" "$basedir/../typescript/bin/tsserver" $args + } + $ret=$LASTEXITCODE +} else { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "node$exe" "$basedir/../typescript/bin/tsserver" $args + } else { + & "node$exe" "$basedir/../typescript/bin/tsserver" $args + } + $ret=$LASTEXITCODE +} +exit $ret diff --git a/src/shasta/voice/web/node_modules/.bin/update-browserslist-db b/src/shasta/voice/web/node_modules/.bin/update-browserslist-db new file mode 100644 index 0000000..cced63c --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/update-browserslist-db @@ -0,0 +1,16 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*|*MINGW*|*MSYS*) + if command -v cygpath > /dev/null 2>&1; then + basedir=`cygpath -w "$basedir"` + fi + ;; +esac + +if [ -x "$basedir/node" ]; then + exec "$basedir/node" "$basedir/../update-browserslist-db/cli.js" "$@" +else + exec node "$basedir/../update-browserslist-db/cli.js" "$@" +fi diff --git a/src/shasta/voice/web/node_modules/.bin/update-browserslist-db.cmd b/src/shasta/voice/web/node_modules/.bin/update-browserslist-db.cmd new file mode 100644 index 0000000..2e14905 --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/update-browserslist-db.cmd @@ -0,0 +1,17 @@ +@ECHO off +GOTO start +:find_dp0 +SET dp0=%~dp0 +EXIT /b +:start +SETLOCAL +CALL :find_dp0 + +IF EXIST "%dp0%\node.exe" ( + SET "_prog=%dp0%\node.exe" +) ELSE ( + SET "_prog=node" + SET PATHEXT=%PATHEXT:;.JS;=;% +) + +endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\update-browserslist-db\cli.js" %* diff --git a/src/shasta/voice/web/node_modules/.bin/update-browserslist-db.ps1 b/src/shasta/voice/web/node_modules/.bin/update-browserslist-db.ps1 new file mode 100644 index 0000000..7abdf26 --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/update-browserslist-db.ps1 @@ -0,0 +1,28 @@ +#!/usr/bin/env pwsh +$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent + +$exe="" +if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { + # Fix case when both the Windows and Linux builds of Node + # are installed in the same directory + $exe=".exe" +} +$ret=0 +if (Test-Path "$basedir/node$exe") { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "$basedir/node$exe" "$basedir/../update-browserslist-db/cli.js" $args + } else { + & "$basedir/node$exe" "$basedir/../update-browserslist-db/cli.js" $args + } + $ret=$LASTEXITCODE +} else { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "node$exe" "$basedir/../update-browserslist-db/cli.js" $args + } else { + & "node$exe" "$basedir/../update-browserslist-db/cli.js" $args + } + $ret=$LASTEXITCODE +} +exit $ret diff --git a/src/shasta/voice/web/node_modules/.bin/vite b/src/shasta/voice/web/node_modules/.bin/vite new file mode 100644 index 0000000..014463f --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/vite @@ -0,0 +1,16 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*|*MINGW*|*MSYS*) + if command -v cygpath > /dev/null 2>&1; then + basedir=`cygpath -w "$basedir"` + fi + ;; +esac + +if [ -x "$basedir/node" ]; then + exec "$basedir/node" "$basedir/../vite/bin/vite.js" "$@" +else + exec node "$basedir/../vite/bin/vite.js" "$@" +fi diff --git a/src/shasta/voice/web/node_modules/.bin/vite.cmd b/src/shasta/voice/web/node_modules/.bin/vite.cmd new file mode 100644 index 0000000..f62e966 --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/vite.cmd @@ -0,0 +1,17 @@ +@ECHO off +GOTO start +:find_dp0 +SET dp0=%~dp0 +EXIT /b +:start +SETLOCAL +CALL :find_dp0 + +IF EXIST "%dp0%\node.exe" ( + SET "_prog=%dp0%\node.exe" +) ELSE ( + SET "_prog=node" + SET PATHEXT=%PATHEXT:;.JS;=;% +) + +endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\vite\bin\vite.js" %* diff --git a/src/shasta/voice/web/node_modules/.bin/vite.ps1 b/src/shasta/voice/web/node_modules/.bin/vite.ps1 new file mode 100644 index 0000000..a7759bc --- /dev/null +++ b/src/shasta/voice/web/node_modules/.bin/vite.ps1 @@ -0,0 +1,28 @@ +#!/usr/bin/env pwsh +$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent + +$exe="" +if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { + # Fix case when both the Windows and Linux builds of Node + # are installed in the same directory + $exe=".exe" +} +$ret=0 +if (Test-Path "$basedir/node$exe") { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "$basedir/node$exe" "$basedir/../vite/bin/vite.js" $args + } else { + & "$basedir/node$exe" "$basedir/../vite/bin/vite.js" $args + } + $ret=$LASTEXITCODE +} else { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "node$exe" "$basedir/../vite/bin/vite.js" $args + } else { + & "node$exe" "$basedir/../vite/bin/vite.js" $args + } + $ret=$LASTEXITCODE +} +exit $ret diff --git a/src/shasta/voice/web/node_modules/.package-lock.json b/src/shasta/voice/web/node_modules/.package-lock.json new file mode 100644 index 0000000..88635d8 --- /dev/null +++ b/src/shasta/voice/web/node_modules/.package-lock.json @@ -0,0 +1,1080 @@ +{ + "name": "shasta-voice-web", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", + "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.3.tgz", + "integrity": "sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.3.tgz", + "integrity": "sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.27", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.27.tgz", + "integrity": "sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001791", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", + "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.351", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.351.tgz", + "integrity": "sha512-9D7Iqx8RImSvCnOsj86rCH6eQjZFQoM04Jn6HnZVM0Nu/G58/gmKYQ1d12MZTbjQbQSTGI8nwEy07ErsA2slLA==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/framer-motion": { + "version": "11.18.2", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.18.2.tgz", + "integrity": "sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==", + "license": "MIT", + "dependencies": { + "motion-dom": "^11.18.1", + "motion-utils": "^11.18.1", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/motion-dom": { + "version": "11.18.1", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz", + "integrity": "sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==", + "license": "MIT", + "dependencies": { + "motion-utils": "^11.18.1" + } + }, + "node_modules/motion-utils": { + "version": "11.18.1", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.18.1.tgz", + "integrity": "sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.38", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.3.tgz", + "integrity": "sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.3", + "@rollup/rollup-android-arm64": "4.60.3", + "@rollup/rollup-darwin-arm64": "4.60.3", + "@rollup/rollup-darwin-x64": "4.60.3", + "@rollup/rollup-freebsd-arm64": "4.60.3", + "@rollup/rollup-freebsd-x64": "4.60.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.3", + "@rollup/rollup-linux-arm-musleabihf": "4.60.3", + "@rollup/rollup-linux-arm64-gnu": "4.60.3", + "@rollup/rollup-linux-arm64-musl": "4.60.3", + "@rollup/rollup-linux-loong64-gnu": "4.60.3", + "@rollup/rollup-linux-loong64-musl": "4.60.3", + "@rollup/rollup-linux-ppc64-gnu": "4.60.3", + "@rollup/rollup-linux-ppc64-musl": "4.60.3", + "@rollup/rollup-linux-riscv64-gnu": "4.60.3", + "@rollup/rollup-linux-riscv64-musl": "4.60.3", + "@rollup/rollup-linux-s390x-gnu": "4.60.3", + "@rollup/rollup-linux-x64-gnu": "4.60.3", + "@rollup/rollup-linux-x64-musl": "4.60.3", + "@rollup/rollup-openbsd-x64": "4.60.3", + "@rollup/rollup-openharmony-arm64": "4.60.3", + "@rollup/rollup-win32-arm64-msvc": "4.60.3", + "@rollup/rollup-win32-ia32-msvc": "4.60.3", + "@rollup/rollup-win32-x64-gnu": "4.60.3", + "@rollup/rollup-win32-x64-msvc": "4.60.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/zustand": { + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.13.tgz", + "integrity": "sha512-efI2tVaVQPqtOh114loML/Z80Y4NP3yc+Ff0fYiZJPauNeWZeIp/bRFD7I9bfmCOYBh/PHxlglQ9+wvlwnPikQ==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/src/shasta/voice/web/node_modules/.vite/deps_temp_daaa8596/chunk-373CG7ZK.js b/src/shasta/voice/web/node_modules/.vite/deps_temp_daaa8596/chunk-373CG7ZK.js new file mode 100644 index 0000000..7b47468 --- /dev/null +++ b/src/shasta/voice/web/node_modules/.vite/deps_temp_daaa8596/chunk-373CG7ZK.js @@ -0,0 +1,21626 @@ +import { + __commonJS, + require_react +} from "./chunk-REFQX4J5.js"; + +// node_modules/scheduler/cjs/scheduler.development.js +var require_scheduler_development = __commonJS({ + "node_modules/scheduler/cjs/scheduler.development.js"(exports) { + "use strict"; + if (true) { + (function() { + "use strict"; + if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== "undefined" && typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart === "function") { + __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error()); + } + var enableSchedulerDebugging = false; + var enableProfiling = false; + var frameYieldMs = 5; + function push(heap, node) { + var index = heap.length; + heap.push(node); + siftUp(heap, node, index); + } + function peek(heap) { + return heap.length === 0 ? null : heap[0]; + } + function pop(heap) { + if (heap.length === 0) { + return null; + } + var first = heap[0]; + var last = heap.pop(); + if (last !== first) { + heap[0] = last; + siftDown(heap, last, 0); + } + return first; + } + function siftUp(heap, node, i) { + var index = i; + while (index > 0) { + var parentIndex = index - 1 >>> 1; + var parent = heap[parentIndex]; + if (compare(parent, node) > 0) { + heap[parentIndex] = node; + heap[index] = parent; + index = parentIndex; + } else { + return; + } + } + } + function siftDown(heap, node, i) { + var index = i; + var length = heap.length; + var halfLength = length >>> 1; + while (index < halfLength) { + var leftIndex = (index + 1) * 2 - 1; + var left = heap[leftIndex]; + var rightIndex = leftIndex + 1; + var right = heap[rightIndex]; + if (compare(left, node) < 0) { + if (rightIndex < length && compare(right, left) < 0) { + heap[index] = right; + heap[rightIndex] = node; + index = rightIndex; + } else { + heap[index] = left; + heap[leftIndex] = node; + index = leftIndex; + } + } else if (rightIndex < length && compare(right, node) < 0) { + heap[index] = right; + heap[rightIndex] = node; + index = rightIndex; + } else { + return; + } + } + } + function compare(a, b) { + var diff = a.sortIndex - b.sortIndex; + return diff !== 0 ? diff : a.id - b.id; + } + var ImmediatePriority = 1; + var UserBlockingPriority = 2; + var NormalPriority = 3; + var LowPriority = 4; + var IdlePriority = 5; + function markTaskErrored(task, ms) { + } + var hasPerformanceNow = typeof performance === "object" && typeof performance.now === "function"; + if (hasPerformanceNow) { + var localPerformance = performance; + exports.unstable_now = function() { + return localPerformance.now(); + }; + } else { + var localDate = Date; + var initialTime = localDate.now(); + exports.unstable_now = function() { + return localDate.now() - initialTime; + }; + } + var maxSigned31BitInt = 1073741823; + var IMMEDIATE_PRIORITY_TIMEOUT = -1; + var USER_BLOCKING_PRIORITY_TIMEOUT = 250; + var NORMAL_PRIORITY_TIMEOUT = 5e3; + var LOW_PRIORITY_TIMEOUT = 1e4; + var IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt; + var taskQueue = []; + var timerQueue = []; + var taskIdCounter = 1; + var currentTask = null; + var currentPriorityLevel = NormalPriority; + var isPerformingWork = false; + var isHostCallbackScheduled = false; + var isHostTimeoutScheduled = false; + var localSetTimeout = typeof setTimeout === "function" ? setTimeout : null; + var localClearTimeout = typeof clearTimeout === "function" ? clearTimeout : null; + var localSetImmediate = typeof setImmediate !== "undefined" ? setImmediate : null; + var isInputPending = typeof navigator !== "undefined" && navigator.scheduling !== void 0 && navigator.scheduling.isInputPending !== void 0 ? navigator.scheduling.isInputPending.bind(navigator.scheduling) : null; + function advanceTimers(currentTime) { + var timer = peek(timerQueue); + while (timer !== null) { + if (timer.callback === null) { + pop(timerQueue); + } else if (timer.startTime <= currentTime) { + pop(timerQueue); + timer.sortIndex = timer.expirationTime; + push(taskQueue, timer); + } else { + return; + } + timer = peek(timerQueue); + } + } + function handleTimeout(currentTime) { + isHostTimeoutScheduled = false; + advanceTimers(currentTime); + if (!isHostCallbackScheduled) { + if (peek(taskQueue) !== null) { + isHostCallbackScheduled = true; + requestHostCallback(flushWork); + } else { + var firstTimer = peek(timerQueue); + if (firstTimer !== null) { + requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime); + } + } + } + } + function flushWork(hasTimeRemaining, initialTime2) { + isHostCallbackScheduled = false; + if (isHostTimeoutScheduled) { + isHostTimeoutScheduled = false; + cancelHostTimeout(); + } + isPerformingWork = true; + var previousPriorityLevel = currentPriorityLevel; + try { + if (enableProfiling) { + try { + return workLoop(hasTimeRemaining, initialTime2); + } catch (error) { + if (currentTask !== null) { + var currentTime = exports.unstable_now(); + markTaskErrored(currentTask, currentTime); + currentTask.isQueued = false; + } + throw error; + } + } else { + return workLoop(hasTimeRemaining, initialTime2); + } + } finally { + currentTask = null; + currentPriorityLevel = previousPriorityLevel; + isPerformingWork = false; + } + } + function workLoop(hasTimeRemaining, initialTime2) { + var currentTime = initialTime2; + advanceTimers(currentTime); + currentTask = peek(taskQueue); + while (currentTask !== null && !enableSchedulerDebugging) { + if (currentTask.expirationTime > currentTime && (!hasTimeRemaining || shouldYieldToHost())) { + break; + } + var callback = currentTask.callback; + if (typeof callback === "function") { + currentTask.callback = null; + currentPriorityLevel = currentTask.priorityLevel; + var didUserCallbackTimeout = currentTask.expirationTime <= currentTime; + var continuationCallback = callback(didUserCallbackTimeout); + currentTime = exports.unstable_now(); + if (typeof continuationCallback === "function") { + currentTask.callback = continuationCallback; + } else { + if (currentTask === peek(taskQueue)) { + pop(taskQueue); + } + } + advanceTimers(currentTime); + } else { + pop(taskQueue); + } + currentTask = peek(taskQueue); + } + if (currentTask !== null) { + return true; + } else { + var firstTimer = peek(timerQueue); + if (firstTimer !== null) { + requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime); + } + return false; + } + } + function unstable_runWithPriority(priorityLevel, eventHandler) { + switch (priorityLevel) { + case ImmediatePriority: + case UserBlockingPriority: + case NormalPriority: + case LowPriority: + case IdlePriority: + break; + default: + priorityLevel = NormalPriority; + } + var previousPriorityLevel = currentPriorityLevel; + currentPriorityLevel = priorityLevel; + try { + return eventHandler(); + } finally { + currentPriorityLevel = previousPriorityLevel; + } + } + function unstable_next(eventHandler) { + var priorityLevel; + switch (currentPriorityLevel) { + case ImmediatePriority: + case UserBlockingPriority: + case NormalPriority: + priorityLevel = NormalPriority; + break; + default: + priorityLevel = currentPriorityLevel; + break; + } + var previousPriorityLevel = currentPriorityLevel; + currentPriorityLevel = priorityLevel; + try { + return eventHandler(); + } finally { + currentPriorityLevel = previousPriorityLevel; + } + } + function unstable_wrapCallback(callback) { + var parentPriorityLevel = currentPriorityLevel; + return function() { + var previousPriorityLevel = currentPriorityLevel; + currentPriorityLevel = parentPriorityLevel; + try { + return callback.apply(this, arguments); + } finally { + currentPriorityLevel = previousPriorityLevel; + } + }; + } + function unstable_scheduleCallback(priorityLevel, callback, options) { + var currentTime = exports.unstable_now(); + var startTime2; + if (typeof options === "object" && options !== null) { + var delay = options.delay; + if (typeof delay === "number" && delay > 0) { + startTime2 = currentTime + delay; + } else { + startTime2 = currentTime; + } + } else { + startTime2 = currentTime; + } + var timeout; + switch (priorityLevel) { + case ImmediatePriority: + timeout = IMMEDIATE_PRIORITY_TIMEOUT; + break; + case UserBlockingPriority: + timeout = USER_BLOCKING_PRIORITY_TIMEOUT; + break; + case IdlePriority: + timeout = IDLE_PRIORITY_TIMEOUT; + break; + case LowPriority: + timeout = LOW_PRIORITY_TIMEOUT; + break; + case NormalPriority: + default: + timeout = NORMAL_PRIORITY_TIMEOUT; + break; + } + var expirationTime = startTime2 + timeout; + var newTask = { + id: taskIdCounter++, + callback, + priorityLevel, + startTime: startTime2, + expirationTime, + sortIndex: -1 + }; + if (startTime2 > currentTime) { + newTask.sortIndex = startTime2; + push(timerQueue, newTask); + if (peek(taskQueue) === null && newTask === peek(timerQueue)) { + if (isHostTimeoutScheduled) { + cancelHostTimeout(); + } else { + isHostTimeoutScheduled = true; + } + requestHostTimeout(handleTimeout, startTime2 - currentTime); + } + } else { + newTask.sortIndex = expirationTime; + push(taskQueue, newTask); + if (!isHostCallbackScheduled && !isPerformingWork) { + isHostCallbackScheduled = true; + requestHostCallback(flushWork); + } + } + return newTask; + } + function unstable_pauseExecution() { + } + function unstable_continueExecution() { + if (!isHostCallbackScheduled && !isPerformingWork) { + isHostCallbackScheduled = true; + requestHostCallback(flushWork); + } + } + function unstable_getFirstCallbackNode() { + return peek(taskQueue); + } + function unstable_cancelCallback(task) { + task.callback = null; + } + function unstable_getCurrentPriorityLevel() { + return currentPriorityLevel; + } + var isMessageLoopRunning = false; + var scheduledHostCallback = null; + var taskTimeoutID = -1; + var frameInterval = frameYieldMs; + var startTime = -1; + function shouldYieldToHost() { + var timeElapsed = exports.unstable_now() - startTime; + if (timeElapsed < frameInterval) { + return false; + } + return true; + } + function requestPaint() { + } + function forceFrameRate(fps) { + if (fps < 0 || fps > 125) { + console["error"]("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"); + return; + } + if (fps > 0) { + frameInterval = Math.floor(1e3 / fps); + } else { + frameInterval = frameYieldMs; + } + } + var performWorkUntilDeadline = function() { + if (scheduledHostCallback !== null) { + var currentTime = exports.unstable_now(); + startTime = currentTime; + var hasTimeRemaining = true; + var hasMoreWork = true; + try { + hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime); + } finally { + if (hasMoreWork) { + schedulePerformWorkUntilDeadline(); + } else { + isMessageLoopRunning = false; + scheduledHostCallback = null; + } + } + } else { + isMessageLoopRunning = false; + } + }; + var schedulePerformWorkUntilDeadline; + if (typeof localSetImmediate === "function") { + schedulePerformWorkUntilDeadline = function() { + localSetImmediate(performWorkUntilDeadline); + }; + } else if (typeof MessageChannel !== "undefined") { + var channel = new MessageChannel(); + var port = channel.port2; + channel.port1.onmessage = performWorkUntilDeadline; + schedulePerformWorkUntilDeadline = function() { + port.postMessage(null); + }; + } else { + schedulePerformWorkUntilDeadline = function() { + localSetTimeout(performWorkUntilDeadline, 0); + }; + } + function requestHostCallback(callback) { + scheduledHostCallback = callback; + if (!isMessageLoopRunning) { + isMessageLoopRunning = true; + schedulePerformWorkUntilDeadline(); + } + } + function requestHostTimeout(callback, ms) { + taskTimeoutID = localSetTimeout(function() { + callback(exports.unstable_now()); + }, ms); + } + function cancelHostTimeout() { + localClearTimeout(taskTimeoutID); + taskTimeoutID = -1; + } + var unstable_requestPaint = requestPaint; + var unstable_Profiling = null; + exports.unstable_IdlePriority = IdlePriority; + exports.unstable_ImmediatePriority = ImmediatePriority; + exports.unstable_LowPriority = LowPriority; + exports.unstable_NormalPriority = NormalPriority; + exports.unstable_Profiling = unstable_Profiling; + exports.unstable_UserBlockingPriority = UserBlockingPriority; + exports.unstable_cancelCallback = unstable_cancelCallback; + exports.unstable_continueExecution = unstable_continueExecution; + exports.unstable_forceFrameRate = forceFrameRate; + exports.unstable_getCurrentPriorityLevel = unstable_getCurrentPriorityLevel; + exports.unstable_getFirstCallbackNode = unstable_getFirstCallbackNode; + exports.unstable_next = unstable_next; + exports.unstable_pauseExecution = unstable_pauseExecution; + exports.unstable_requestPaint = unstable_requestPaint; + exports.unstable_runWithPriority = unstable_runWithPriority; + exports.unstable_scheduleCallback = unstable_scheduleCallback; + exports.unstable_shouldYield = shouldYieldToHost; + exports.unstable_wrapCallback = unstable_wrapCallback; + if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== "undefined" && typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop === "function") { + __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(new Error()); + } + })(); + } + } +}); + +// node_modules/scheduler/index.js +var require_scheduler = __commonJS({ + "node_modules/scheduler/index.js"(exports, module) { + "use strict"; + if (false) { + module.exports = null; + } else { + module.exports = require_scheduler_development(); + } + } +}); + +// node_modules/react-dom/cjs/react-dom.development.js +var require_react_dom_development = __commonJS({ + "node_modules/react-dom/cjs/react-dom.development.js"(exports) { + "use strict"; + if (true) { + (function() { + "use strict"; + if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== "undefined" && typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart === "function") { + __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error()); + } + var React = require_react(); + var Scheduler = require_scheduler(); + var ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; + var suppressWarning = false; + function setSuppressWarning(newSuppressWarning) { + { + suppressWarning = newSuppressWarning; + } + } + function warn(format) { + { + if (!suppressWarning) { + for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + args[_key - 1] = arguments[_key]; + } + printWarning("warn", format, args); + } + } + } + function error(format) { + { + if (!suppressWarning) { + for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { + args[_key2 - 1] = arguments[_key2]; + } + printWarning("error", format, args); + } + } + } + function printWarning(level, format, args) { + { + var ReactDebugCurrentFrame2 = ReactSharedInternals.ReactDebugCurrentFrame; + var stack = ReactDebugCurrentFrame2.getStackAddendum(); + if (stack !== "") { + format += "%s"; + args = args.concat([stack]); + } + var argsWithFormat = args.map(function(item) { + return String(item); + }); + argsWithFormat.unshift("Warning: " + format); + Function.prototype.apply.call(console[level], console, argsWithFormat); + } + } + var FunctionComponent = 0; + var ClassComponent = 1; + var IndeterminateComponent = 2; + var HostRoot = 3; + var HostPortal = 4; + var HostComponent = 5; + var HostText = 6; + var Fragment = 7; + var Mode = 8; + var ContextConsumer = 9; + var ContextProvider = 10; + var ForwardRef = 11; + var Profiler = 12; + var SuspenseComponent = 13; + var MemoComponent = 14; + var SimpleMemoComponent = 15; + var LazyComponent = 16; + var IncompleteClassComponent = 17; + var DehydratedFragment = 18; + var SuspenseListComponent = 19; + var ScopeComponent = 21; + var OffscreenComponent = 22; + var LegacyHiddenComponent = 23; + var CacheComponent = 24; + var TracingMarkerComponent = 25; + var enableClientRenderFallbackOnTextMismatch = true; + var enableNewReconciler = false; + var enableLazyContextPropagation = false; + var enableLegacyHidden = false; + var enableSuspenseAvoidThisFallback = false; + var disableCommentsAsDOMContainers = true; + var enableCustomElementPropertySupport = false; + var warnAboutStringRefs = true; + var enableSchedulingProfiler = true; + var enableProfilerTimer = true; + var enableProfilerCommitHooks = true; + var allNativeEvents = /* @__PURE__ */ new Set(); + var registrationNameDependencies = {}; + var possibleRegistrationNames = {}; + function registerTwoPhaseEvent(registrationName, dependencies) { + registerDirectEvent(registrationName, dependencies); + registerDirectEvent(registrationName + "Capture", dependencies); + } + function registerDirectEvent(registrationName, dependencies) { + { + if (registrationNameDependencies[registrationName]) { + error("EventRegistry: More than one plugin attempted to publish the same registration name, `%s`.", registrationName); + } + } + registrationNameDependencies[registrationName] = dependencies; + { + var lowerCasedName = registrationName.toLowerCase(); + possibleRegistrationNames[lowerCasedName] = registrationName; + if (registrationName === "onDoubleClick") { + possibleRegistrationNames.ondblclick = registrationName; + } + } + for (var i = 0; i < dependencies.length; i++) { + allNativeEvents.add(dependencies[i]); + } + } + var canUseDOM = !!(typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined"); + var hasOwnProperty = Object.prototype.hasOwnProperty; + function typeName(value) { + { + var hasToStringTag = typeof Symbol === "function" && Symbol.toStringTag; + var type = hasToStringTag && value[Symbol.toStringTag] || value.constructor.name || "Object"; + return type; + } + } + function willCoercionThrow(value) { + { + try { + testStringCoercion(value); + return false; + } catch (e) { + return true; + } + } + } + function testStringCoercion(value) { + return "" + value; + } + function checkAttributeStringCoercion(value, attributeName) { + { + if (willCoercionThrow(value)) { + error("The provided `%s` attribute is an unsupported type %s. This value must be coerced to a string before before using it here.", attributeName, typeName(value)); + return testStringCoercion(value); + } + } + } + function checkKeyStringCoercion(value) { + { + if (willCoercionThrow(value)) { + error("The provided key is an unsupported type %s. This value must be coerced to a string before before using it here.", typeName(value)); + return testStringCoercion(value); + } + } + } + function checkPropStringCoercion(value, propName) { + { + if (willCoercionThrow(value)) { + error("The provided `%s` prop is an unsupported type %s. This value must be coerced to a string before before using it here.", propName, typeName(value)); + return testStringCoercion(value); + } + } + } + function checkCSSPropertyStringCoercion(value, propName) { + { + if (willCoercionThrow(value)) { + error("The provided `%s` CSS property is an unsupported type %s. This value must be coerced to a string before before using it here.", propName, typeName(value)); + return testStringCoercion(value); + } + } + } + function checkHtmlStringCoercion(value) { + { + if (willCoercionThrow(value)) { + error("The provided HTML markup uses a value of unsupported type %s. This value must be coerced to a string before before using it here.", typeName(value)); + return testStringCoercion(value); + } + } + } + function checkFormFieldValueStringCoercion(value) { + { + if (willCoercionThrow(value)) { + error("Form field values (value, checked, defaultValue, or defaultChecked props) must be strings, not %s. This value must be coerced to a string before before using it here.", typeName(value)); + return testStringCoercion(value); + } + } + } + var RESERVED = 0; + var STRING = 1; + var BOOLEANISH_STRING = 2; + var BOOLEAN = 3; + var OVERLOADED_BOOLEAN = 4; + var NUMERIC = 5; + var POSITIVE_NUMERIC = 6; + var ATTRIBUTE_NAME_START_CHAR = ":A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD"; + var ATTRIBUTE_NAME_CHAR = ATTRIBUTE_NAME_START_CHAR + "\\-.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040"; + var VALID_ATTRIBUTE_NAME_REGEX = new RegExp("^[" + ATTRIBUTE_NAME_START_CHAR + "][" + ATTRIBUTE_NAME_CHAR + "]*$"); + var illegalAttributeNameCache = {}; + var validatedAttributeNameCache = {}; + function isAttributeNameSafe(attributeName) { + if (hasOwnProperty.call(validatedAttributeNameCache, attributeName)) { + return true; + } + if (hasOwnProperty.call(illegalAttributeNameCache, attributeName)) { + return false; + } + if (VALID_ATTRIBUTE_NAME_REGEX.test(attributeName)) { + validatedAttributeNameCache[attributeName] = true; + return true; + } + illegalAttributeNameCache[attributeName] = true; + { + error("Invalid attribute name: `%s`", attributeName); + } + return false; + } + function shouldIgnoreAttribute(name, propertyInfo, isCustomComponentTag) { + if (propertyInfo !== null) { + return propertyInfo.type === RESERVED; + } + if (isCustomComponentTag) { + return false; + } + if (name.length > 2 && (name[0] === "o" || name[0] === "O") && (name[1] === "n" || name[1] === "N")) { + return true; + } + return false; + } + function shouldRemoveAttributeWithWarning(name, value, propertyInfo, isCustomComponentTag) { + if (propertyInfo !== null && propertyInfo.type === RESERVED) { + return false; + } + switch (typeof value) { + case "function": + case "symbol": + return true; + case "boolean": { + if (isCustomComponentTag) { + return false; + } + if (propertyInfo !== null) { + return !propertyInfo.acceptsBooleans; + } else { + var prefix2 = name.toLowerCase().slice(0, 5); + return prefix2 !== "data-" && prefix2 !== "aria-"; + } + } + default: + return false; + } + } + function shouldRemoveAttribute(name, value, propertyInfo, isCustomComponentTag) { + if (value === null || typeof value === "undefined") { + return true; + } + if (shouldRemoveAttributeWithWarning(name, value, propertyInfo, isCustomComponentTag)) { + return true; + } + if (isCustomComponentTag) { + return false; + } + if (propertyInfo !== null) { + switch (propertyInfo.type) { + case BOOLEAN: + return !value; + case OVERLOADED_BOOLEAN: + return value === false; + case NUMERIC: + return isNaN(value); + case POSITIVE_NUMERIC: + return isNaN(value) || value < 1; + } + } + return false; + } + function getPropertyInfo(name) { + return properties.hasOwnProperty(name) ? properties[name] : null; + } + function PropertyInfoRecord(name, type, mustUseProperty, attributeName, attributeNamespace, sanitizeURL2, removeEmptyString) { + this.acceptsBooleans = type === BOOLEANISH_STRING || type === BOOLEAN || type === OVERLOADED_BOOLEAN; + this.attributeName = attributeName; + this.attributeNamespace = attributeNamespace; + this.mustUseProperty = mustUseProperty; + this.propertyName = name; + this.type = type; + this.sanitizeURL = sanitizeURL2; + this.removeEmptyString = removeEmptyString; + } + var properties = {}; + var reservedProps = [ + "children", + "dangerouslySetInnerHTML", + // TODO: This prevents the assignment of defaultValue to regular + // elements (not just inputs). Now that ReactDOMInput assigns to the + // defaultValue property -- do we need this? + "defaultValue", + "defaultChecked", + "innerHTML", + "suppressContentEditableWarning", + "suppressHydrationWarning", + "style" + ]; + reservedProps.forEach(function(name) { + properties[name] = new PropertyInfoRecord( + name, + RESERVED, + false, + // mustUseProperty + name, + // attributeName + null, + // attributeNamespace + false, + // sanitizeURL + false + ); + }); + [["acceptCharset", "accept-charset"], ["className", "class"], ["htmlFor", "for"], ["httpEquiv", "http-equiv"]].forEach(function(_ref) { + var name = _ref[0], attributeName = _ref[1]; + properties[name] = new PropertyInfoRecord( + name, + STRING, + false, + // mustUseProperty + attributeName, + // attributeName + null, + // attributeNamespace + false, + // sanitizeURL + false + ); + }); + ["contentEditable", "draggable", "spellCheck", "value"].forEach(function(name) { + properties[name] = new PropertyInfoRecord( + name, + BOOLEANISH_STRING, + false, + // mustUseProperty + name.toLowerCase(), + // attributeName + null, + // attributeNamespace + false, + // sanitizeURL + false + ); + }); + ["autoReverse", "externalResourcesRequired", "focusable", "preserveAlpha"].forEach(function(name) { + properties[name] = new PropertyInfoRecord( + name, + BOOLEANISH_STRING, + false, + // mustUseProperty + name, + // attributeName + null, + // attributeNamespace + false, + // sanitizeURL + false + ); + }); + [ + "allowFullScreen", + "async", + // Note: there is a special case that prevents it from being written to the DOM + // on the client side because the browsers are inconsistent. Instead we call focus(). + "autoFocus", + "autoPlay", + "controls", + "default", + "defer", + "disabled", + "disablePictureInPicture", + "disableRemotePlayback", + "formNoValidate", + "hidden", + "loop", + "noModule", + "noValidate", + "open", + "playsInline", + "readOnly", + "required", + "reversed", + "scoped", + "seamless", + // Microdata + "itemScope" + ].forEach(function(name) { + properties[name] = new PropertyInfoRecord( + name, + BOOLEAN, + false, + // mustUseProperty + name.toLowerCase(), + // attributeName + null, + // attributeNamespace + false, + // sanitizeURL + false + ); + }); + [ + "checked", + // Note: `option.selected` is not updated if `select.multiple` is + // disabled with `removeAttribute`. We have special logic for handling this. + "multiple", + "muted", + "selected" + // NOTE: if you add a camelCased prop to this list, + // you'll need to set attributeName to name.toLowerCase() + // instead in the assignment below. + ].forEach(function(name) { + properties[name] = new PropertyInfoRecord( + name, + BOOLEAN, + true, + // mustUseProperty + name, + // attributeName + null, + // attributeNamespace + false, + // sanitizeURL + false + ); + }); + [ + "capture", + "download" + // NOTE: if you add a camelCased prop to this list, + // you'll need to set attributeName to name.toLowerCase() + // instead in the assignment below. + ].forEach(function(name) { + properties[name] = new PropertyInfoRecord( + name, + OVERLOADED_BOOLEAN, + false, + // mustUseProperty + name, + // attributeName + null, + // attributeNamespace + false, + // sanitizeURL + false + ); + }); + [ + "cols", + "rows", + "size", + "span" + // NOTE: if you add a camelCased prop to this list, + // you'll need to set attributeName to name.toLowerCase() + // instead in the assignment below. + ].forEach(function(name) { + properties[name] = new PropertyInfoRecord( + name, + POSITIVE_NUMERIC, + false, + // mustUseProperty + name, + // attributeName + null, + // attributeNamespace + false, + // sanitizeURL + false + ); + }); + ["rowSpan", "start"].forEach(function(name) { + properties[name] = new PropertyInfoRecord( + name, + NUMERIC, + false, + // mustUseProperty + name.toLowerCase(), + // attributeName + null, + // attributeNamespace + false, + // sanitizeURL + false + ); + }); + var CAMELIZE = /[\-\:]([a-z])/g; + var capitalize = function(token) { + return token[1].toUpperCase(); + }; + [ + "accent-height", + "alignment-baseline", + "arabic-form", + "baseline-shift", + "cap-height", + "clip-path", + "clip-rule", + "color-interpolation", + "color-interpolation-filters", + "color-profile", + "color-rendering", + "dominant-baseline", + "enable-background", + "fill-opacity", + "fill-rule", + "flood-color", + "flood-opacity", + "font-family", + "font-size", + "font-size-adjust", + "font-stretch", + "font-style", + "font-variant", + "font-weight", + "glyph-name", + "glyph-orientation-horizontal", + "glyph-orientation-vertical", + "horiz-adv-x", + "horiz-origin-x", + "image-rendering", + "letter-spacing", + "lighting-color", + "marker-end", + "marker-mid", + "marker-start", + "overline-position", + "overline-thickness", + "paint-order", + "panose-1", + "pointer-events", + "rendering-intent", + "shape-rendering", + "stop-color", + "stop-opacity", + "strikethrough-position", + "strikethrough-thickness", + "stroke-dasharray", + "stroke-dashoffset", + "stroke-linecap", + "stroke-linejoin", + "stroke-miterlimit", + "stroke-opacity", + "stroke-width", + "text-anchor", + "text-decoration", + "text-rendering", + "underline-position", + "underline-thickness", + "unicode-bidi", + "unicode-range", + "units-per-em", + "v-alphabetic", + "v-hanging", + "v-ideographic", + "v-mathematical", + "vector-effect", + "vert-adv-y", + "vert-origin-x", + "vert-origin-y", + "word-spacing", + "writing-mode", + "xmlns:xlink", + "x-height" + // NOTE: if you add a camelCased prop to this list, + // you'll need to set attributeName to name.toLowerCase() + // instead in the assignment below. + ].forEach(function(attributeName) { + var name = attributeName.replace(CAMELIZE, capitalize); + properties[name] = new PropertyInfoRecord( + name, + STRING, + false, + // mustUseProperty + attributeName, + null, + // attributeNamespace + false, + // sanitizeURL + false + ); + }); + [ + "xlink:actuate", + "xlink:arcrole", + "xlink:role", + "xlink:show", + "xlink:title", + "xlink:type" + // NOTE: if you add a camelCased prop to this list, + // you'll need to set attributeName to name.toLowerCase() + // instead in the assignment below. + ].forEach(function(attributeName) { + var name = attributeName.replace(CAMELIZE, capitalize); + properties[name] = new PropertyInfoRecord( + name, + STRING, + false, + // mustUseProperty + attributeName, + "http://www.w3.org/1999/xlink", + false, + // sanitizeURL + false + ); + }); + [ + "xml:base", + "xml:lang", + "xml:space" + // NOTE: if you add a camelCased prop to this list, + // you'll need to set attributeName to name.toLowerCase() + // instead in the assignment below. + ].forEach(function(attributeName) { + var name = attributeName.replace(CAMELIZE, capitalize); + properties[name] = new PropertyInfoRecord( + name, + STRING, + false, + // mustUseProperty + attributeName, + "http://www.w3.org/XML/1998/namespace", + false, + // sanitizeURL + false + ); + }); + ["tabIndex", "crossOrigin"].forEach(function(attributeName) { + properties[attributeName] = new PropertyInfoRecord( + attributeName, + STRING, + false, + // mustUseProperty + attributeName.toLowerCase(), + // attributeName + null, + // attributeNamespace + false, + // sanitizeURL + false + ); + }); + var xlinkHref = "xlinkHref"; + properties[xlinkHref] = new PropertyInfoRecord( + "xlinkHref", + STRING, + false, + // mustUseProperty + "xlink:href", + "http://www.w3.org/1999/xlink", + true, + // sanitizeURL + false + ); + ["src", "href", "action", "formAction"].forEach(function(attributeName) { + properties[attributeName] = new PropertyInfoRecord( + attributeName, + STRING, + false, + // mustUseProperty + attributeName.toLowerCase(), + // attributeName + null, + // attributeNamespace + true, + // sanitizeURL + true + ); + }); + var isJavaScriptProtocol = /^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*\:/i; + var didWarn = false; + function sanitizeURL(url) { + { + if (!didWarn && isJavaScriptProtocol.test(url)) { + didWarn = true; + error("A future version of React will block javascript: URLs as a security precaution. Use event handlers instead if you can. If you need to generate unsafe HTML try using dangerouslySetInnerHTML instead. React was passed %s.", JSON.stringify(url)); + } + } + } + function getValueForProperty(node, name, expected, propertyInfo) { + { + if (propertyInfo.mustUseProperty) { + var propertyName = propertyInfo.propertyName; + return node[propertyName]; + } else { + { + checkAttributeStringCoercion(expected, name); + } + if (propertyInfo.sanitizeURL) { + sanitizeURL("" + expected); + } + var attributeName = propertyInfo.attributeName; + var stringValue = null; + if (propertyInfo.type === OVERLOADED_BOOLEAN) { + if (node.hasAttribute(attributeName)) { + var value = node.getAttribute(attributeName); + if (value === "") { + return true; + } + if (shouldRemoveAttribute(name, expected, propertyInfo, false)) { + return value; + } + if (value === "" + expected) { + return expected; + } + return value; + } + } else if (node.hasAttribute(attributeName)) { + if (shouldRemoveAttribute(name, expected, propertyInfo, false)) { + return node.getAttribute(attributeName); + } + if (propertyInfo.type === BOOLEAN) { + return expected; + } + stringValue = node.getAttribute(attributeName); + } + if (shouldRemoveAttribute(name, expected, propertyInfo, false)) { + return stringValue === null ? expected : stringValue; + } else if (stringValue === "" + expected) { + return expected; + } else { + return stringValue; + } + } + } + } + function getValueForAttribute(node, name, expected, isCustomComponentTag) { + { + if (!isAttributeNameSafe(name)) { + return; + } + if (!node.hasAttribute(name)) { + return expected === void 0 ? void 0 : null; + } + var value = node.getAttribute(name); + { + checkAttributeStringCoercion(expected, name); + } + if (value === "" + expected) { + return expected; + } + return value; + } + } + function setValueForProperty(node, name, value, isCustomComponentTag) { + var propertyInfo = getPropertyInfo(name); + if (shouldIgnoreAttribute(name, propertyInfo, isCustomComponentTag)) { + return; + } + if (shouldRemoveAttribute(name, value, propertyInfo, isCustomComponentTag)) { + value = null; + } + if (isCustomComponentTag || propertyInfo === null) { + if (isAttributeNameSafe(name)) { + var _attributeName = name; + if (value === null) { + node.removeAttribute(_attributeName); + } else { + { + checkAttributeStringCoercion(value, name); + } + node.setAttribute(_attributeName, "" + value); + } + } + return; + } + var mustUseProperty = propertyInfo.mustUseProperty; + if (mustUseProperty) { + var propertyName = propertyInfo.propertyName; + if (value === null) { + var type = propertyInfo.type; + node[propertyName] = type === BOOLEAN ? false : ""; + } else { + node[propertyName] = value; + } + return; + } + var attributeName = propertyInfo.attributeName, attributeNamespace = propertyInfo.attributeNamespace; + if (value === null) { + node.removeAttribute(attributeName); + } else { + var _type = propertyInfo.type; + var attributeValue; + if (_type === BOOLEAN || _type === OVERLOADED_BOOLEAN && value === true) { + attributeValue = ""; + } else { + { + { + checkAttributeStringCoercion(value, attributeName); + } + attributeValue = "" + value; + } + if (propertyInfo.sanitizeURL) { + sanitizeURL(attributeValue.toString()); + } + } + if (attributeNamespace) { + node.setAttributeNS(attributeNamespace, attributeName, attributeValue); + } else { + node.setAttribute(attributeName, attributeValue); + } + } + } + var REACT_ELEMENT_TYPE = Symbol.for("react.element"); + var REACT_PORTAL_TYPE = Symbol.for("react.portal"); + var REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"); + var REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode"); + var REACT_PROFILER_TYPE = Symbol.for("react.profiler"); + var REACT_PROVIDER_TYPE = Symbol.for("react.provider"); + var REACT_CONTEXT_TYPE = Symbol.for("react.context"); + var REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref"); + var REACT_SUSPENSE_TYPE = Symbol.for("react.suspense"); + var REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list"); + var REACT_MEMO_TYPE = Symbol.for("react.memo"); + var REACT_LAZY_TYPE = Symbol.for("react.lazy"); + var REACT_SCOPE_TYPE = Symbol.for("react.scope"); + var REACT_DEBUG_TRACING_MODE_TYPE = Symbol.for("react.debug_trace_mode"); + var REACT_OFFSCREEN_TYPE = Symbol.for("react.offscreen"); + var REACT_LEGACY_HIDDEN_TYPE = Symbol.for("react.legacy_hidden"); + var REACT_CACHE_TYPE = Symbol.for("react.cache"); + var REACT_TRACING_MARKER_TYPE = Symbol.for("react.tracing_marker"); + var MAYBE_ITERATOR_SYMBOL = Symbol.iterator; + var FAUX_ITERATOR_SYMBOL = "@@iterator"; + function getIteratorFn(maybeIterable) { + if (maybeIterable === null || typeof maybeIterable !== "object") { + return null; + } + var maybeIterator = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL]; + if (typeof maybeIterator === "function") { + return maybeIterator; + } + return null; + } + var assign = Object.assign; + var disabledDepth = 0; + var prevLog; + var prevInfo; + var prevWarn; + var prevError; + var prevGroup; + var prevGroupCollapsed; + var prevGroupEnd; + function disabledLog() { + } + disabledLog.__reactDisabledLog = true; + function disableLogs() { + { + if (disabledDepth === 0) { + prevLog = console.log; + prevInfo = console.info; + prevWarn = console.warn; + prevError = console.error; + prevGroup = console.group; + prevGroupCollapsed = console.groupCollapsed; + prevGroupEnd = console.groupEnd; + var props = { + configurable: true, + enumerable: true, + value: disabledLog, + writable: true + }; + Object.defineProperties(console, { + info: props, + log: props, + warn: props, + error: props, + group: props, + groupCollapsed: props, + groupEnd: props + }); + } + disabledDepth++; + } + } + function reenableLogs() { + { + disabledDepth--; + if (disabledDepth === 0) { + var props = { + configurable: true, + enumerable: true, + writable: true + }; + Object.defineProperties(console, { + log: assign({}, props, { + value: prevLog + }), + info: assign({}, props, { + value: prevInfo + }), + warn: assign({}, props, { + value: prevWarn + }), + error: assign({}, props, { + value: prevError + }), + group: assign({}, props, { + value: prevGroup + }), + groupCollapsed: assign({}, props, { + value: prevGroupCollapsed + }), + groupEnd: assign({}, props, { + value: prevGroupEnd + }) + }); + } + if (disabledDepth < 0) { + error("disabledDepth fell below zero. This is a bug in React. Please file an issue."); + } + } + } + var ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher; + var prefix; + function describeBuiltInComponentFrame(name, source, ownerFn) { + { + if (prefix === void 0) { + try { + throw Error(); + } catch (x) { + var match = x.stack.trim().match(/\n( *(at )?)/); + prefix = match && match[1] || ""; + } + } + return "\n" + prefix + name; + } + } + var reentry = false; + var componentFrameCache; + { + var PossiblyWeakMap = typeof WeakMap === "function" ? WeakMap : Map; + componentFrameCache = new PossiblyWeakMap(); + } + function describeNativeComponentFrame(fn, construct) { + if (!fn || reentry) { + return ""; + } + { + var frame = componentFrameCache.get(fn); + if (frame !== void 0) { + return frame; + } + } + var control; + reentry = true; + var previousPrepareStackTrace = Error.prepareStackTrace; + Error.prepareStackTrace = void 0; + var previousDispatcher; + { + previousDispatcher = ReactCurrentDispatcher.current; + ReactCurrentDispatcher.current = null; + disableLogs(); + } + try { + if (construct) { + var Fake = function() { + throw Error(); + }; + Object.defineProperty(Fake.prototype, "props", { + set: function() { + throw Error(); + } + }); + if (typeof Reflect === "object" && Reflect.construct) { + try { + Reflect.construct(Fake, []); + } catch (x) { + control = x; + } + Reflect.construct(fn, [], Fake); + } else { + try { + Fake.call(); + } catch (x) { + control = x; + } + fn.call(Fake.prototype); + } + } else { + try { + throw Error(); + } catch (x) { + control = x; + } + fn(); + } + } catch (sample) { + if (sample && control && typeof sample.stack === "string") { + var sampleLines = sample.stack.split("\n"); + var controlLines = control.stack.split("\n"); + var s = sampleLines.length - 1; + var c = controlLines.length - 1; + while (s >= 1 && c >= 0 && sampleLines[s] !== controlLines[c]) { + c--; + } + for (; s >= 1 && c >= 0; s--, c--) { + if (sampleLines[s] !== controlLines[c]) { + if (s !== 1 || c !== 1) { + do { + s--; + c--; + if (c < 0 || sampleLines[s] !== controlLines[c]) { + var _frame = "\n" + sampleLines[s].replace(" at new ", " at "); + if (fn.displayName && _frame.includes("")) { + _frame = _frame.replace("", fn.displayName); + } + { + if (typeof fn === "function") { + componentFrameCache.set(fn, _frame); + } + } + return _frame; + } + } while (s >= 1 && c >= 0); + } + break; + } + } + } + } finally { + reentry = false; + { + ReactCurrentDispatcher.current = previousDispatcher; + reenableLogs(); + } + Error.prepareStackTrace = previousPrepareStackTrace; + } + var name = fn ? fn.displayName || fn.name : ""; + var syntheticFrame = name ? describeBuiltInComponentFrame(name) : ""; + { + if (typeof fn === "function") { + componentFrameCache.set(fn, syntheticFrame); + } + } + return syntheticFrame; + } + function describeClassComponentFrame(ctor, source, ownerFn) { + { + return describeNativeComponentFrame(ctor, true); + } + } + function describeFunctionComponentFrame(fn, source, ownerFn) { + { + return describeNativeComponentFrame(fn, false); + } + } + function shouldConstruct(Component) { + var prototype = Component.prototype; + return !!(prototype && prototype.isReactComponent); + } + function describeUnknownElementTypeFrameInDEV(type, source, ownerFn) { + if (type == null) { + return ""; + } + if (typeof type === "function") { + { + return describeNativeComponentFrame(type, shouldConstruct(type)); + } + } + if (typeof type === "string") { + return describeBuiltInComponentFrame(type); + } + switch (type) { + case REACT_SUSPENSE_TYPE: + return describeBuiltInComponentFrame("Suspense"); + case REACT_SUSPENSE_LIST_TYPE: + return describeBuiltInComponentFrame("SuspenseList"); + } + if (typeof type === "object") { + switch (type.$$typeof) { + case REACT_FORWARD_REF_TYPE: + return describeFunctionComponentFrame(type.render); + case REACT_MEMO_TYPE: + return describeUnknownElementTypeFrameInDEV(type.type, source, ownerFn); + case REACT_LAZY_TYPE: { + var lazyComponent = type; + var payload = lazyComponent._payload; + var init = lazyComponent._init; + try { + return describeUnknownElementTypeFrameInDEV(init(payload), source, ownerFn); + } catch (x) { + } + } + } + } + return ""; + } + function describeFiber(fiber) { + var owner = fiber._debugOwner ? fiber._debugOwner.type : null; + var source = fiber._debugSource; + switch (fiber.tag) { + case HostComponent: + return describeBuiltInComponentFrame(fiber.type); + case LazyComponent: + return describeBuiltInComponentFrame("Lazy"); + case SuspenseComponent: + return describeBuiltInComponentFrame("Suspense"); + case SuspenseListComponent: + return describeBuiltInComponentFrame("SuspenseList"); + case FunctionComponent: + case IndeterminateComponent: + case SimpleMemoComponent: + return describeFunctionComponentFrame(fiber.type); + case ForwardRef: + return describeFunctionComponentFrame(fiber.type.render); + case ClassComponent: + return describeClassComponentFrame(fiber.type); + default: + return ""; + } + } + function getStackByFiberInDevAndProd(workInProgress2) { + try { + var info = ""; + var node = workInProgress2; + do { + info += describeFiber(node); + node = node.return; + } while (node); + return info; + } catch (x) { + return "\nError generating stack: " + x.message + "\n" + x.stack; + } + } + function getWrappedName(outerType, innerType, wrapperName) { + var displayName = outerType.displayName; + if (displayName) { + return displayName; + } + var functionName = innerType.displayName || innerType.name || ""; + return functionName !== "" ? wrapperName + "(" + functionName + ")" : wrapperName; + } + function getContextName(type) { + return type.displayName || "Context"; + } + function getComponentNameFromType(type) { + if (type == null) { + return null; + } + { + if (typeof type.tag === "number") { + error("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."); + } + } + if (typeof type === "function") { + return type.displayName || type.name || null; + } + if (typeof type === "string") { + return type; + } + switch (type) { + case REACT_FRAGMENT_TYPE: + return "Fragment"; + case REACT_PORTAL_TYPE: + return "Portal"; + case REACT_PROFILER_TYPE: + return "Profiler"; + case REACT_STRICT_MODE_TYPE: + return "StrictMode"; + case REACT_SUSPENSE_TYPE: + return "Suspense"; + case REACT_SUSPENSE_LIST_TYPE: + return "SuspenseList"; + } + if (typeof type === "object") { + switch (type.$$typeof) { + case REACT_CONTEXT_TYPE: + var context = type; + return getContextName(context) + ".Consumer"; + case REACT_PROVIDER_TYPE: + var provider = type; + return getContextName(provider._context) + ".Provider"; + case REACT_FORWARD_REF_TYPE: + return getWrappedName(type, type.render, "ForwardRef"); + case REACT_MEMO_TYPE: + var outerName = type.displayName || null; + if (outerName !== null) { + return outerName; + } + return getComponentNameFromType(type.type) || "Memo"; + case REACT_LAZY_TYPE: { + var lazyComponent = type; + var payload = lazyComponent._payload; + var init = lazyComponent._init; + try { + return getComponentNameFromType(init(payload)); + } catch (x) { + return null; + } + } + } + } + return null; + } + function getWrappedName$1(outerType, innerType, wrapperName) { + var functionName = innerType.displayName || innerType.name || ""; + return outerType.displayName || (functionName !== "" ? wrapperName + "(" + functionName + ")" : wrapperName); + } + function getContextName$1(type) { + return type.displayName || "Context"; + } + function getComponentNameFromFiber(fiber) { + var tag = fiber.tag, type = fiber.type; + switch (tag) { + case CacheComponent: + return "Cache"; + case ContextConsumer: + var context = type; + return getContextName$1(context) + ".Consumer"; + case ContextProvider: + var provider = type; + return getContextName$1(provider._context) + ".Provider"; + case DehydratedFragment: + return "DehydratedFragment"; + case ForwardRef: + return getWrappedName$1(type, type.render, "ForwardRef"); + case Fragment: + return "Fragment"; + case HostComponent: + return type; + case HostPortal: + return "Portal"; + case HostRoot: + return "Root"; + case HostText: + return "Text"; + case LazyComponent: + return getComponentNameFromType(type); + case Mode: + if (type === REACT_STRICT_MODE_TYPE) { + return "StrictMode"; + } + return "Mode"; + case OffscreenComponent: + return "Offscreen"; + case Profiler: + return "Profiler"; + case ScopeComponent: + return "Scope"; + case SuspenseComponent: + return "Suspense"; + case SuspenseListComponent: + return "SuspenseList"; + case TracingMarkerComponent: + return "TracingMarker"; + case ClassComponent: + case FunctionComponent: + case IncompleteClassComponent: + case IndeterminateComponent: + case MemoComponent: + case SimpleMemoComponent: + if (typeof type === "function") { + return type.displayName || type.name || null; + } + if (typeof type === "string") { + return type; + } + break; + } + return null; + } + var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame; + var current = null; + var isRendering = false; + function getCurrentFiberOwnerNameInDevOrNull() { + { + if (current === null) { + return null; + } + var owner = current._debugOwner; + if (owner !== null && typeof owner !== "undefined") { + return getComponentNameFromFiber(owner); + } + } + return null; + } + function getCurrentFiberStackInDev() { + { + if (current === null) { + return ""; + } + return getStackByFiberInDevAndProd(current); + } + } + function resetCurrentFiber() { + { + ReactDebugCurrentFrame.getCurrentStack = null; + current = null; + isRendering = false; + } + } + function setCurrentFiber(fiber) { + { + ReactDebugCurrentFrame.getCurrentStack = fiber === null ? null : getCurrentFiberStackInDev; + current = fiber; + isRendering = false; + } + } + function getCurrentFiber() { + { + return current; + } + } + function setIsRendering(rendering) { + { + isRendering = rendering; + } + } + function toString(value) { + return "" + value; + } + function getToStringValue(value) { + switch (typeof value) { + case "boolean": + case "number": + case "string": + case "undefined": + return value; + case "object": + { + checkFormFieldValueStringCoercion(value); + } + return value; + default: + return ""; + } + } + var hasReadOnlyValue = { + button: true, + checkbox: true, + image: true, + hidden: true, + radio: true, + reset: true, + submit: true + }; + function checkControlledValueProps(tagName, props) { + { + if (!(hasReadOnlyValue[props.type] || props.onChange || props.onInput || props.readOnly || props.disabled || props.value == null)) { + error("You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`."); + } + if (!(props.onChange || props.readOnly || props.disabled || props.checked == null)) { + error("You provided a `checked` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultChecked`. Otherwise, set either `onChange` or `readOnly`."); + } + } + } + function isCheckable(elem) { + var type = elem.type; + var nodeName = elem.nodeName; + return nodeName && nodeName.toLowerCase() === "input" && (type === "checkbox" || type === "radio"); + } + function getTracker(node) { + return node._valueTracker; + } + function detachTracker(node) { + node._valueTracker = null; + } + function getValueFromNode(node) { + var value = ""; + if (!node) { + return value; + } + if (isCheckable(node)) { + value = node.checked ? "true" : "false"; + } else { + value = node.value; + } + return value; + } + function trackValueOnNode(node) { + var valueField = isCheckable(node) ? "checked" : "value"; + var descriptor = Object.getOwnPropertyDescriptor(node.constructor.prototype, valueField); + { + checkFormFieldValueStringCoercion(node[valueField]); + } + var currentValue = "" + node[valueField]; + if (node.hasOwnProperty(valueField) || typeof descriptor === "undefined" || typeof descriptor.get !== "function" || typeof descriptor.set !== "function") { + return; + } + var get2 = descriptor.get, set2 = descriptor.set; + Object.defineProperty(node, valueField, { + configurable: true, + get: function() { + return get2.call(this); + }, + set: function(value) { + { + checkFormFieldValueStringCoercion(value); + } + currentValue = "" + value; + set2.call(this, value); + } + }); + Object.defineProperty(node, valueField, { + enumerable: descriptor.enumerable + }); + var tracker = { + getValue: function() { + return currentValue; + }, + setValue: function(value) { + { + checkFormFieldValueStringCoercion(value); + } + currentValue = "" + value; + }, + stopTracking: function() { + detachTracker(node); + delete node[valueField]; + } + }; + return tracker; + } + function track(node) { + if (getTracker(node)) { + return; + } + node._valueTracker = trackValueOnNode(node); + } + function updateValueIfChanged(node) { + if (!node) { + return false; + } + var tracker = getTracker(node); + if (!tracker) { + return true; + } + var lastValue = tracker.getValue(); + var nextValue = getValueFromNode(node); + if (nextValue !== lastValue) { + tracker.setValue(nextValue); + return true; + } + return false; + } + function getActiveElement(doc) { + doc = doc || (typeof document !== "undefined" ? document : void 0); + if (typeof doc === "undefined") { + return null; + } + try { + return doc.activeElement || doc.body; + } catch (e) { + return doc.body; + } + } + var didWarnValueDefaultValue = false; + var didWarnCheckedDefaultChecked = false; + var didWarnControlledToUncontrolled = false; + var didWarnUncontrolledToControlled = false; + function isControlled(props) { + var usesChecked = props.type === "checkbox" || props.type === "radio"; + return usesChecked ? props.checked != null : props.value != null; + } + function getHostProps(element, props) { + var node = element; + var checked = props.checked; + var hostProps = assign({}, props, { + defaultChecked: void 0, + defaultValue: void 0, + value: void 0, + checked: checked != null ? checked : node._wrapperState.initialChecked + }); + return hostProps; + } + function initWrapperState(element, props) { + { + checkControlledValueProps("input", props); + if (props.checked !== void 0 && props.defaultChecked !== void 0 && !didWarnCheckedDefaultChecked) { + error("%s contains an input of type %s with both checked and defaultChecked props. Input elements must be either controlled or uncontrolled (specify either the checked prop, or the defaultChecked prop, but not both). Decide between using a controlled or uncontrolled input element and remove one of these props. More info: https://reactjs.org/link/controlled-components", getCurrentFiberOwnerNameInDevOrNull() || "A component", props.type); + didWarnCheckedDefaultChecked = true; + } + if (props.value !== void 0 && props.defaultValue !== void 0 && !didWarnValueDefaultValue) { + error("%s contains an input of type %s with both value and defaultValue props. Input elements must be either controlled or uncontrolled (specify either the value prop, or the defaultValue prop, but not both). Decide between using a controlled or uncontrolled input element and remove one of these props. More info: https://reactjs.org/link/controlled-components", getCurrentFiberOwnerNameInDevOrNull() || "A component", props.type); + didWarnValueDefaultValue = true; + } + } + var node = element; + var defaultValue = props.defaultValue == null ? "" : props.defaultValue; + node._wrapperState = { + initialChecked: props.checked != null ? props.checked : props.defaultChecked, + initialValue: getToStringValue(props.value != null ? props.value : defaultValue), + controlled: isControlled(props) + }; + } + function updateChecked(element, props) { + var node = element; + var checked = props.checked; + if (checked != null) { + setValueForProperty(node, "checked", checked, false); + } + } + function updateWrapper(element, props) { + var node = element; + { + var controlled = isControlled(props); + if (!node._wrapperState.controlled && controlled && !didWarnUncontrolledToControlled) { + error("A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components"); + didWarnUncontrolledToControlled = true; + } + if (node._wrapperState.controlled && !controlled && !didWarnControlledToUncontrolled) { + error("A component is changing a controlled input to be uncontrolled. This is likely caused by the value changing from a defined to undefined, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components"); + didWarnControlledToUncontrolled = true; + } + } + updateChecked(element, props); + var value = getToStringValue(props.value); + var type = props.type; + if (value != null) { + if (type === "number") { + if (value === 0 && node.value === "" || // We explicitly want to coerce to number here if possible. + // eslint-disable-next-line + node.value != value) { + node.value = toString(value); + } + } else if (node.value !== toString(value)) { + node.value = toString(value); + } + } else if (type === "submit" || type === "reset") { + node.removeAttribute("value"); + return; + } + { + if (props.hasOwnProperty("value")) { + setDefaultValue(node, props.type, value); + } else if (props.hasOwnProperty("defaultValue")) { + setDefaultValue(node, props.type, getToStringValue(props.defaultValue)); + } + } + { + if (props.checked == null && props.defaultChecked != null) { + node.defaultChecked = !!props.defaultChecked; + } + } + } + function postMountWrapper(element, props, isHydrating2) { + var node = element; + if (props.hasOwnProperty("value") || props.hasOwnProperty("defaultValue")) { + var type = props.type; + var isButton = type === "submit" || type === "reset"; + if (isButton && (props.value === void 0 || props.value === null)) { + return; + } + var initialValue = toString(node._wrapperState.initialValue); + if (!isHydrating2) { + { + if (initialValue !== node.value) { + node.value = initialValue; + } + } + } + { + node.defaultValue = initialValue; + } + } + var name = node.name; + if (name !== "") { + node.name = ""; + } + { + node.defaultChecked = !node.defaultChecked; + node.defaultChecked = !!node._wrapperState.initialChecked; + } + if (name !== "") { + node.name = name; + } + } + function restoreControlledState(element, props) { + var node = element; + updateWrapper(node, props); + updateNamedCousins(node, props); + } + function updateNamedCousins(rootNode, props) { + var name = props.name; + if (props.type === "radio" && name != null) { + var queryRoot = rootNode; + while (queryRoot.parentNode) { + queryRoot = queryRoot.parentNode; + } + { + checkAttributeStringCoercion(name, "name"); + } + var group = queryRoot.querySelectorAll("input[name=" + JSON.stringify("" + name) + '][type="radio"]'); + for (var i = 0; i < group.length; i++) { + var otherNode = group[i]; + if (otherNode === rootNode || otherNode.form !== rootNode.form) { + continue; + } + var otherProps = getFiberCurrentPropsFromNode(otherNode); + if (!otherProps) { + throw new Error("ReactDOMInput: Mixing React and non-React radio inputs with the same `name` is not supported."); + } + updateValueIfChanged(otherNode); + updateWrapper(otherNode, otherProps); + } + } + } + function setDefaultValue(node, type, value) { + if ( + // Focused number inputs synchronize on blur. See ChangeEventPlugin.js + type !== "number" || getActiveElement(node.ownerDocument) !== node + ) { + if (value == null) { + node.defaultValue = toString(node._wrapperState.initialValue); + } else if (node.defaultValue !== toString(value)) { + node.defaultValue = toString(value); + } + } + } + var didWarnSelectedSetOnOption = false; + var didWarnInvalidChild = false; + var didWarnInvalidInnerHTML = false; + function validateProps(element, props) { + { + if (props.value == null) { + if (typeof props.children === "object" && props.children !== null) { + React.Children.forEach(props.children, function(child) { + if (child == null) { + return; + } + if (typeof child === "string" || typeof child === "number") { + return; + } + if (!didWarnInvalidChild) { + didWarnInvalidChild = true; + error("Cannot infer the option value of complex children. Pass a `value` prop or use a plain string as children to