diff --git a/.github/workflows/deploy-site.yml b/.github/workflows/deploy-site.yml new file mode 100644 index 00000000..9efca242 --- /dev/null +++ b/.github/workflows/deploy-site.yml @@ -0,0 +1,111 @@ +name: Deploy Static Site + +# Single source of truth for the gh-pages branch (served at +# diagrams.mingrammer.com). Builds BOTH the Docusaurus docs and the +# playground, assembles them into one tree (docs at the root, playground +# under /playground/), and deploys the whole thing. Because everything is +# rebuilt and published together, the docs and the playground can never +# overwrite each other — this replaces the old manual `website/publish.sh` +# and the separate playground deploy. +# +# Safety: the deploy step only runs if every build step succeeds, so a failed +# build leaves the live gh-pages branch untouched. + +on: + push: + branches: [master] + paths: + - "website/**" + - "playground/**" + - "diagrams/**" + - "resources/**" + - ".github/workflows/deploy-site.yml" + workflow_dispatch: + +permissions: + contents: write + +concurrency: + group: deploy-site + cancel-in-progress: true + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + # --- Docs (Docusaurus v1) -> website/build/diagrams/ (includes CNAME) --- + - name: Build docs + working-directory: website + env: + # Docusaurus 1.x uses a legacy webpack/OpenSSL path that needs this + # on Node 17+. + NODE_OPTIONS: --openssl-legacy-provider + run: | + npm install --no-audit --no-fund + npm run build + + # --- Playground (Pyodide assets + Vite) -> playground/dist/ --- + - name: Install diagrams (for asset generation) + run: pip install . + - name: Build playground + run: | + cd playground && npm ci && cd .. + python3 playground/scripts/gen_catalog.py --repo-root . --out playground/public + cd playground && npm run build + + # --- Assemble the combined site --- + - name: Assemble site + run: | + rm -rf _site + cp -a website/build/diagrams _site + mkdir -p _site/playground + cp -a playground/dist/. _site/playground/ + test -f _site/CNAME || echo "diagrams.mingrammer.com" > _site/CNAME + # Docusaurus only lists its own pages, so add the playground to the + # sitemap it generated. + python3 - <<'PY' + from pathlib import Path + + sitemap = Path("_site/sitemap.xml") + entry = ( + "https://diagrams.mingrammer.com/playground/" + "weekly0.8" + ) + xml = sitemap.read_text() + if "/playground/" not in xml: + sitemap.write_text(xml.replace("", entry + "")) + print("added playground to sitemap") + else: + print("playground already in sitemap") + + # Google Search Console ownership check. Docusaurus 1.x has no + # config hook for arbitrary tags, and the verifier reads the + # served HTML without running JS, so the tag is injected into the + # built homepage here. + verification = ( + '' + ) + home = Path("_site/index.html") + html = home.read_text() + if "google-site-verification" not in html: + home.write_text(html.replace("", "" + verification, 1)) + print("added Search Console verification to homepage") + else: + print("verification tag already present") + PY + + - name: Deploy to gh-pages + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: _site + force_orphan: true diff --git a/.github/workflows/playground.yml b/.github/workflows/playground.yml new file mode 100644 index 00000000..caf4dd9e --- /dev/null +++ b/.github/workflows/playground.yml @@ -0,0 +1,38 @@ +name: Playground + +on: + pull_request: + paths: + - "playground/**" + - "diagrams/**" + - "resources/**" + - ".github/workflows/playground.yml" + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - uses: actions/setup-node@v4 + with: + node-version: "20" + - name: Install python deps + run: pip install . pytest + - name: Python tests (catalog + shim) + run: python3 -m pytest playground/scripts/ -v + - name: Generate assets + run: python3 playground/scripts/gen_catalog.py --repo-root . --out playground/public + - name: npm install & unit tests & build + working-directory: playground + run: | + npm ci + npm test + npm run build + - name: E2E smoke + working-directory: playground + run: | + npx playwright install chromium --with-deps + npm run e2e diff --git a/.isort.cfg b/.isort.cfg index 8112570b..688c7715 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -2,4 +2,4 @@ line_length = 120 multi_line_output = 3 include_trailing_comma = True -known_third_party = graphviz,jinja2 +known_third_party = graphviz,jinja2,pytest diff --git a/README.md b/README.md index 18ad5634..203bd8a5 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,8 @@ Diagrams lets you draw the cloud system architecture **in Python code**. It was ## Getting Started +> Want to try it first? The [**online playground**](https://diagrams.mingrammer.com/playground/) runs **diagrams** right in your browser — no installation required. + It requires **Python 3.9** or higher, check your Python version first. It uses [Graphviz](https://www.graphviz.org/) to render the diagram, so you need to [install Graphviz](https://graphviz.gitlab.io/download/) to use **diagrams**. After installing graphviz (or already have it), install the **diagrams**. @@ -60,6 +62,12 @@ $ poetry add diagrams You can start with [quick start](https://diagrams.mingrammer.com/docs/getting-started/installation#quick-start). Check out [guides](https://diagrams.mingrammer.com/docs/guides/diagram) for more details, and you can find all available nodes list in [here](https://diagrams.mingrammer.com/docs/nodes/aws). +## Playground + +[**diagrams.mingrammer.com/playground**](https://diagrams.mingrammer.com/playground/) + +A free online editor for **diagrams**: write Diagram as Code in Python and see the rendered architecture diagram instantly, without installing Python or Graphviz. It runs the real **diagrams** package in your browser via [Pyodide](https://pyodide.org), and supports node search, autocompletion, PNG/SVG/JPEG export, and shareable links. See the [playground guide](https://diagrams.mingrammer.com/docs/getting-started/playground) for details. + ## Examples | Event Processing | Stateful Architecture | Advanced Web Service | diff --git a/autogen.sh b/autogen.sh index c8e6922d..263ae41a 100755 --- a/autogen.sh +++ b/autogen.sh @@ -2,7 +2,6 @@ echo "starting" app_root_dir="diagrams" -# NOTE: azure icon set is not latest version providers=( "onprem" "aws" diff --git a/config.py b/config.py index e3fc7a68..2b1db813 100644 --- a/config.py +++ b/config.py @@ -290,6 +290,10 @@ ALIASES = { "KubernetesServices": "AKS", "VMScaleSet": "VMSS" }, + # Upstream ships this icon as promethus.svg; expose the correct spelling too. + "newicons": { + "Promethus": "Prometheus", + }, }, "gcp": { "analytics": { diff --git a/diagrams/aws/compute.py b/diagrams/aws/compute.py index 2dc85cd9..019c754d 100644 --- a/diagrams/aws/compute.py +++ b/diagrams/aws/compute.py @@ -20,6 +20,10 @@ class Batch(_Compute): _icon = "batch.png" +class Bottlerocket(_Compute): + _icon = "bottlerocket.png" + + class ComputeOptimizer(_Compute): _icon = "compute-optimizer.png" diff --git a/diagrams/aws/management.py b/diagrams/aws/management.py index 3de22625..8721f0d6 100644 --- a/diagrams/aws/management.py +++ b/diagrams/aws/management.py @@ -92,6 +92,10 @@ class ControlTower(_Management): _icon = "control-tower.png" +class DevopsAgent(_Management): + _icon = "devops-agent.png" + + class LicenseManager(_Management): _icon = "license-manager.png" diff --git a/diagrams/aws/security.py b/diagrams/aws/security.py index 13ca73d3..6db5ba0e 100644 --- a/diagrams/aws/security.py +++ b/diagrams/aws/security.py @@ -160,6 +160,10 @@ class SingleSignOn(_Security): _icon = "single-sign-on.png" +class VerifiedPermissions(_Security): + _icon = "verified-permissions.png" + + class WAFFilteringRule(_Security): _icon = "waf-filtering-rule.png" diff --git a/diagrams/azure/aimachinelearning.py b/diagrams/azure/aimachinelearning.py index 2c18af48..f45279ff 100644 --- a/diagrams/azure/aimachinelearning.py +++ b/diagrams/azure/aimachinelearning.py @@ -64,6 +64,10 @@ class ContentModerators(_Aimachinelearning): _icon = "content-moderators.png" +class ContentSafety(_Aimachinelearning): + _icon = "content-safety.png" + + class CustomVision(_Aimachinelearning): _icon = "custom-vision.png" diff --git a/diagrams/azure/compute.py b/diagrams/azure/compute.py index ffc3e1bf..0ab11412 100644 --- a/diagrams/azure/compute.py +++ b/diagrams/azure/compute.py @@ -8,6 +8,10 @@ class _Compute(_Azure): _icon_dir = "resources/azure/compute" +class AksAutomatic(_Compute): + _icon = "aks-automatic.png" + + class AppServices(_Compute): _icon = "app-services.png" @@ -52,6 +56,10 @@ class CloudsimpleVirtualMachines(_Compute): _icon = "cloudsimple-virtual-machines.png" +class ComputeFleet(_Compute): + _icon = "compute-fleet.png" + + class ContainerApps(_Compute): _icon = "container-apps.png" diff --git a/diagrams/azure/devops.py b/diagrams/azure/devops.py index 35dbc5ee..521cb06a 100644 --- a/diagrams/azure/devops.py +++ b/diagrams/azure/devops.py @@ -68,6 +68,10 @@ class LoadTesting(_Devops): _icon = "load-testing.png" +class ManagedDevopsPools(_Devops): + _icon = "managed-devops-pools.png" + + class Pipelines(_Devops): _icon = "pipelines.png" @@ -80,4 +84,8 @@ class TestPlans(_Devops): _icon = "test-plans.png" +class WorkspaceGateway(_Devops): + _icon = "workspace-gateway.png" + + # Aliases diff --git a/diagrams/azure/hybridmulticloud.py b/diagrams/azure/hybridmulticloud.py index e2839d30..f0b443bd 100644 --- a/diagrams/azure/hybridmulticloud.py +++ b/diagrams/azure/hybridmulticloud.py @@ -8,6 +8,10 @@ class _Hybridmulticloud(_Azure): _icon_dir = "resources/azure/hybridmulticloud" +class AzureMonitorPipeline(_Hybridmulticloud): + _icon = "azure-monitor-pipeline.png" + + class AzureOperator5GCore(_Hybridmulticloud): _icon = "azure-operator-5g-core.png" diff --git a/diagrams/azure/identity.py b/diagrams/azure/identity.py index 6c71928d..47cc67fd 100644 --- a/diagrams/azure/identity.py +++ b/diagrams/azure/identity.py @@ -96,6 +96,14 @@ class EnterpriseApplications(_Identity): _icon = "enterprise-applications.png" +class EntraConnectHealth(_Identity): + _icon = "entra-connect-health.png" + + +class EntraConnectSync(_Identity): + _icon = "entra-connect-sync.png" + + class EntraConnect(_Identity): _icon = "entra-connect.png" @@ -104,14 +112,38 @@ class EntraDomainServices(_Identity): _icon = "entra-domain-services.png" +class EntraGlobalSecureAccess(_Identity): + _icon = "entra-global-secure-access.png" + + class EntraIDProtection(_Identity): _icon = "entra-id-protection.png" +class EntraIdentityCustomRoles(_Identity): + _icon = "entra-identity-custom-roles.png" + + +class EntraIdentityLicenses(_Identity): + _icon = "entra-identity-licenses.png" + + +class EntraIdentityRolesAndAdministrators(_Identity): + _icon = "entra-identity-roles-and-administrators.png" + + +class EntraInternetAccess(_Identity): + _icon = "entra-internet-access.png" + + class EntraManagedIdentities(_Identity): _icon = "entra-managed-identities.png" +class EntraPrivateAccess(_Identity): + _icon = "entra-private-access.png" + + class EntraPrivlegedIdentityManagement(_Identity): _icon = "entra-privleged-identity-management.png" @@ -148,6 +180,10 @@ class ManagedIdentities(_Identity): _icon = "managed-identities.png" +class MultiFactorAuthentication(_Identity): + _icon = "multi-factor-authentication.png" + + class PrivateAccess(_Identity): _icon = "private-access.png" @@ -172,4 +208,8 @@ class VerifiableCredentials(_Identity): _icon = "verifiable-credentials.png" +class VerificationAsAService(_Identity): + _icon = "verification-as-a-service.png" + + # Aliases diff --git a/diagrams/azure/integration.py b/diagrams/azure/integration.py index e89931fd..e589cb80 100644 --- a/diagrams/azure/integration.py +++ b/diagrams/azure/integration.py @@ -52,6 +52,10 @@ class AzureStackEdge(_Integration): _icon = "azure-stack-edge.png" +class BusinessProcessTracking(_Integration): + _icon = "business-process-tracking.png" + + class DataCatalog(_Integration): _icon = "data-catalog.png" diff --git a/diagrams/azure/intune.py b/diagrams/azure/intune.py index c11fe61e..a0b27aff 100644 --- a/diagrams/azure/intune.py +++ b/diagrams/azure/intune.py @@ -48,6 +48,10 @@ class Ebooks(_Intune): _icon = "ebooks.png" +class EntraIdentityRolesAndAdministrators(_Intune): + _icon = "entra-identity-roles-and-administrators.png" + + class ExchangeAccess(_Intune): _icon = "exchange-access.png" diff --git a/diagrams/azure/iot.py b/diagrams/azure/iot.py index afdbad52..4d77885b 100644 --- a/diagrams/azure/iot.py +++ b/diagrams/azure/iot.py @@ -24,6 +24,10 @@ class AzureMapsAccounts(_Iot): _icon = "azure-maps-accounts.png" +class AzureStackHciSizer(_Iot): + _icon = "azure-stack-hci-sizer.png" + + class AzureStack(_Iot): _icon = "azure-stack.png" diff --git a/diagrams/azure/networking.py b/diagrams/azure/networking.py index 90c93852..ffc062e7 100644 --- a/diagrams/azure/networking.py +++ b/diagrams/azure/networking.py @@ -8,6 +8,10 @@ class _Networking(_Azure): _icon_dir = "resources/azure/networking" +class ApplicationGatewayContainers(_Networking): + _icon = "application-gateway-containers.png" + + class ApplicationGateways(_Networking): _icon = "application-gateways.png" diff --git a/diagrams/azure/newicons.py b/diagrams/azure/newicons.py index 19b27aab..dc5a6b3b 100644 --- a/diagrams/azure/newicons.py +++ b/diagrams/azure/newicons.py @@ -8,14 +8,66 @@ class _Newicons(_Azure): _icon_dir = "resources/azure/newicons" +class AIAtEdge(_Newicons): + _icon = "ai-at-edge.png" + + +class AksNetworkPolicy(_Newicons): + _icon = "aks-network-policy.png" + + +class AzureAppTesting(_Newicons): + _icon = "azure-app-testing.png" + + +class AzureConsumptionCommitment(_Newicons): + _icon = "azure-consumption-commitment.png" + + +class AzureContainerStorage(_Newicons): + _icon = "azure-container-storage.png" + + +class AzureLinux(_Newicons): + _icon = "azure-linux.png" + + +class AzureLocal(_Newicons): + _icon = "azure-local.png" + + +class AzureManagedRedis(_Newicons): + _icon = "azure-managed-redis.png" + + class AzureSustainability(_Newicons): _icon = "azure-sustainability.png" +class Breeze(_Newicons): + _icon = "breeze.png" + + class ConnectedVehiclePlatform(_Newicons): _icon = "connected-vehicle-platform.png" +class DataVirtualization(_Newicons): + _icon = "data-virtualization.png" + + +class EdgeActions(_Newicons): + _icon = "edge-actions.png" + + +class EdgeStorageAccelerator(_Newicons): + _icon = "edge-storage-accelerator.png" + + +class EngageCenterConnect(_Newicons): + _icon = "engage-center-connect.png" + + class EntraConnectHealth(_Newicons): _icon = "entra-connect-health.png" @@ -24,16 +76,122 @@ class EntraConnectSync(_Newicons): _icon = "entra-connect-sync.png" +class ExternalIDModified(_Newicons): + _icon = "external-id-modified.png" + + +class ExternalID(_Newicons): + _icon = "external-id.png" + + +class FrdQa(_Newicons): + _icon = "frd-qa.png" + + +class HybridConnectivityHub(_Newicons): + _icon = "hybrid-connectivity-hub.png" + + class IcmTroubleshooting(_Newicons): _icon = "icm-troubleshooting.png" +class KubernetesHub(_Newicons): + _icon = "kubernetes-hub.png" + + +class LandingZone(_Newicons): + _icon = "landing-zone.png" + + +class LoadBalancerHub(_Newicons): + _icon = "load-balancer-hub.png" + + +class LogicAppsTemplate(_Newicons): + _icon = "logic-apps-template.png" + + +class MicrosoftDiscovery(_Newicons): + _icon = "microsoft-discovery.png" + + +class MonitorIssues(_Newicons): + _icon = "monitor-issues.png" + + +class NetworkFoundationHub(_Newicons): + _icon = "network-foundation-hub.png" + + +class NetworkSecurityHub(_Newicons): + _icon = "network-security-hub.png" + + +class OperationCenter(_Newicons): + _icon = "operation-center.png" + + class Osconfig(_Newicons): _icon = "osconfig.png" +class PlanetaryComputerPro(_Newicons): + _icon = "planetary-computer-pro.png" + + +class Promethus(_Newicons): + _icon = "promethus.png" + + +class Pubsub(_Newicons): + _icon = "pubsub.png" + + +class ScheduledActions(_Newicons): + _icon = "scheduled-actions.png" + + +class ServiceGroupRelationships(_Newicons): + _icon = "service-group-relationships.png" + + +class ServiceGroups(_Newicons): + _icon = "service-groups.png" + + +class SQLDatabaseFleetManager(_Newicons): + _icon = "sql-database-fleet-manager.png" + + +class StageMaps(_Newicons): + _icon = "stage-maps.png" + + class StorageActions(_Newicons): _icon = "storage-actions.png" +class StorageHubs(_Newicons): + _icon = "storage-hubs.png" + + +class ToolchainOrchestrator(_Newicons): + _icon = "toolchain-orchestrator.png" + + +class VnetAppliance(_Newicons): + _icon = "vnet-appliance.png" + + +class Vpnclientwindows(_Newicons): + _icon = "vpnclientwindows.png" + + +class WorkloadOrchestration(_Newicons): + _icon = "workload-orchestration.png" + + # Aliases + +Prometheus = Promethus diff --git a/diagrams/azure/other.py b/diagrams/azure/other.py index 7219718a..23d6ac63 100644 --- a/diagrams/azure/other.py +++ b/diagrams/azure/other.py @@ -136,6 +136,10 @@ class AzureSupportCenterBlue(_Other): _icon = "azure-support-center-blue.png" +class AzureSustainability(_Other): + _icon = "azure-sustainability.png" + + class AzureVideoIndexer(_Other): _icon = "azure-video-indexer.png" @@ -192,6 +196,10 @@ class ConfidentialLedgers(_Other): _icon = "confidential-ledgers.png" +class ConnectedVehiclePlatform(_Other): + _icon = "connected-vehicle-platform.png" + + class ContainerAppsEnvironments(_Other): _icon = "container-apps-environments.png" @@ -328,6 +336,10 @@ class ElasticSan(_Other): _icon = "elastic-san.png" +class EntraIdentityLicenses(_Other): + _icon = "entra-identity-licenses.png" + + class ExchangeOnPremisesAccess(_Other): _icon = "exchange-on-premises-access.png" @@ -352,6 +364,10 @@ class HdiAksCluster(_Other): _icon = "hdi-aks-cluster.png" +class IcmTroubleshooting(_Other): + _icon = "icm-troubleshooting.png" + + class InstancePools(_Other): _icon = "instance-pools.png" @@ -396,6 +412,10 @@ class ModularDataCenter(_Other): _icon = "modular-data-center.png" +class MonitorHealthModels(_Other): + _icon = "monitor-health-models.png" + + class NetworkManagers(_Other): _icon = "network-managers.png" @@ -408,6 +428,10 @@ class OpenSupplyChainPlatform(_Other): _icon = "open-supply-chain-platform.png" +class Osconfig(_Other): + _icon = "osconfig.png" + + class PeeringService(_Other): _icon = "peering-service.png" @@ -500,6 +524,10 @@ class VMImageVersion(_Other): _icon = "vm-image-version.png" +class WacInstaller(_Other): + _icon = "wac-installer.png" + + class Wac(_Other): _icon = "wac.png" diff --git a/diagrams/azure/security.py b/diagrams/azure/security.py index e2c529d4..4d52b32f 100644 --- a/diagrams/azure/security.py +++ b/diagrams/azure/security.py @@ -52,6 +52,14 @@ class Detonation(_Security): _icon = "detonation.png" +class EntraIdentityRiskySignins(_Security): + _icon = "entra-identity-risky-signins.png" + + +class EntraIdentityRiskyUsers(_Security): + _icon = "entra-identity-risky-users.png" + + class ExtendedSecurityUpdates(_Security): _icon = "extended-security-updates.png" diff --git a/diagrams/azure/storage.py b/diagrams/azure/storage.py index fca288b1..6e02c00b 100644 --- a/diagrams/azure/storage.py +++ b/diagrams/azure/storage.py @@ -72,6 +72,10 @@ class ImportExportJobs(_Storage): _icon = "import-export-jobs.png" +class ManagedFileShares(_Storage): + _icon = "managed-file-shares.png" + + class NetappFiles(_Storage): _icon = "netapp-files.png" @@ -92,6 +96,10 @@ class StorageAccounts(_Storage): _icon = "storage-accounts.png" +class StorageActions(_Storage): + _icon = "storage-actions.png" + + class StorageExplorer(_Storage): _icon = "storage-explorer.png" diff --git a/diagrams/azure/web.py b/diagrams/azure/web.py index 4f5be166..95fbf275 100644 --- a/diagrams/azure/web.py +++ b/diagrams/azure/web.py @@ -40,6 +40,10 @@ class AppServices(_Web): _icon = "app-services.png" +class AppSpaceComponent(_Web): + _icon = "app-space-component.png" + + class AppSpace(_Web): _icon = "app-space.png" diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index cdbc86f8..f108e33c 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -3,6 +3,8 @@ id: installation title: Installation --- +> Prefer to try it without installing? The [**online playground**](/playground/) runs **diagrams** in your browser — see the [playground guide](/docs/getting-started/playground). + **diagrams** requires **Python 3.7** or higher, check your Python version first. **diagrams** uses [Graphviz](https://www.graphviz.org/) to render the diagram, so you need to [install Graphviz](https://graphviz.gitlab.io/download/) to use it. diff --git a/docs/getting-started/playground.md b/docs/getting-started/playground.md new file mode 100644 index 00000000..75784c66 --- /dev/null +++ b/docs/getting-started/playground.md @@ -0,0 +1,43 @@ +--- +id: playground +title: Online Playground +--- + +The **Diagrams Playground** is a free online editor for the **diagrams** library. Write Diagram as Code in Python and see the rendered architecture diagram appear next to it — in your web browser, with nothing to install. + +> **[Open the Playground →](/playground/)** + +## Why use it + +Installing **diagrams** locally means installing Python *and* [Graphviz](https://www.graphviz.org/). The playground skips both, which makes it handy when you want to: + +- try **diagrams** before installing anything, +- sketch a cloud architecture diagram from a laptop that isn't set up for Python, +- share a working diagram with a teammate as a single link, +- look up which node classes exist for AWS, Azure, GCP, Kubernetes and the other providers. + +## What it can do + +- **Live preview** — the diagram re-renders as you type. +- **Node browser** — search every available node, or browse the full provider → category tree, and click to insert the matching `import`. +- **Autocompletion** — import paths and node names complete as you type, with signature hints for `Diagram`, `Cluster` and `Edge`. +- **Export** — download the result as PNG, SVG or JPEG at a size you choose, or copy the image straight to your clipboard. +- **Share links** — the whole diagram is encoded into the URL, so sending the link is enough. Nothing is stored on a server. +- **Examples** — start from a ready-made diagram instead of a blank editor. + +## How it works + +The playground runs the real **diagrams** package — not a reimplementation — inside your browser using [Pyodide](https://pyodide.org), which is CPython compiled to WebAssembly. Graph layout is done by a WebAssembly build of Graphviz. + +Everything executes locally in the browser tab: your code is never uploaded, and there is no backend. The first visit downloads the Python runtime, so it takes a few seconds; after that it is cached. + +## When to install instead + +The playground is for quick work and sharing. Install the library ([installation guide](/docs/getting-started/installation)) when you want to: + +- keep diagrams in version control next to your code, +- generate diagrams in CI or a build script, +- use custom local icons, or +- work offline. + +The Python code is identical either way, so anything you write in the playground runs unchanged after `pip install diagrams`. diff --git a/docs/nodes/aws.md b/docs/nodes/aws.md index 3f12e18f..f40e340b 100644 --- a/docs/nodes/aws.md +++ b/docs/nodes/aws.md @@ -146,6 +146,9 @@ Node classes list of the aws provider. Batch **diagrams.aws.compute.Batch** +Bottlerocket +**diagrams.aws.compute.Bottlerocket** + ComputeOptimizer **diagrams.aws.compute.ComputeOptimizer** @@ -995,6 +998,9 @@ Node classes list of the aws provider. ControlTower **diagrams.aws.management.ControlTower** +DevopsAgent +**diagrams.aws.management.DevopsAgent** + LicenseManager **diagrams.aws.management.LicenseManager** @@ -1592,6 +1598,9 @@ Node classes list of the aws provider. SingleSignOn **diagrams.aws.security.SingleSignOn** +VerifiedPermissions +**diagrams.aws.security.VerifiedPermissions** + WAFFilteringRule **diagrams.aws.security.WAFFilteringRule** diff --git a/docs/nodes/azure.md b/docs/nodes/azure.md index 570f537e..a738654c 100644 --- a/docs/nodes/azure.md +++ b/docs/nodes/azure.md @@ -3,7 +3,7 @@ id: azure title: Azure --- -Node classes list of azure provider. +Node classes list of the azure provider. ## azure.aimachinelearning @@ -50,6 +50,9 @@ Node classes list of azure provider. ContentModerators **diagrams.azure.aimachinelearning.ContentModerators** +ContentSafety +**diagrams.azure.aimachinelearning.ContentSafety** + CustomVision **diagrams.azure.aimachinelearning.CustomVision** @@ -254,6 +257,9 @@ Node classes list of azure provider. ## azure.compute +AksAutomatic +**diagrams.azure.compute.AksAutomatic** + AppServices **diagrams.azure.compute.AppServices** @@ -287,6 +293,9 @@ Node classes list of azure provider. CloudsimpleVirtualMachines **diagrams.azure.compute.CloudsimpleVirtualMachines** +ComputeFleet +**diagrams.azure.compute.ComputeFleet** + ContainerApps **diagrams.azure.compute.ContainerApps** @@ -644,6 +653,9 @@ Node classes list of azure provider. LoadTesting **diagrams.azure.devops.LoadTesting** +ManagedDevopsPools +**diagrams.azure.devops.ManagedDevopsPools** + Pipelines **diagrams.azure.devops.Pipelines** @@ -653,6 +665,9 @@ Node classes list of azure provider. TestPlans **diagrams.azure.devops.TestPlans** +WorkspaceGateway +**diagrams.azure.devops.WorkspaceGateway** + ## azure.general @@ -1001,6 +1016,9 @@ Node classes list of azure provider. ## azure.hybridmulticloud +AzureMonitorPipeline +**diagrams.azure.hybridmulticloud.AzureMonitorPipeline** + AzureOperator5GCore **diagrams.azure.hybridmulticloud.AzureOperator5GCore** @@ -1085,18 +1103,42 @@ Node classes list of azure provider. EnterpriseApplications **diagrams.azure.identity.EnterpriseApplications** +EntraConnectHealth +**diagrams.azure.identity.EntraConnectHealth** + +EntraConnectSync +**diagrams.azure.identity.EntraConnectSync** + EntraConnect **diagrams.azure.identity.EntraConnect** EntraDomainServices **diagrams.azure.identity.EntraDomainServices** +EntraGlobalSecureAccess +**diagrams.azure.identity.EntraGlobalSecureAccess** + EntraIDProtection **diagrams.azure.identity.EntraIDProtection** +EntraIdentityCustomRoles +**diagrams.azure.identity.EntraIdentityCustomRoles** + +EntraIdentityLicenses +**diagrams.azure.identity.EntraIdentityLicenses** + +EntraIdentityRolesAndAdministrators +**diagrams.azure.identity.EntraIdentityRolesAndAdministrators** + +EntraInternetAccess +**diagrams.azure.identity.EntraInternetAccess** + EntraManagedIdentities **diagrams.azure.identity.EntraManagedIdentities** +EntraPrivateAccess +**diagrams.azure.identity.EntraPrivateAccess** + EntraPrivlegedIdentityManagement **diagrams.azure.identity.EntraPrivlegedIdentityManagement** @@ -1124,6 +1166,9 @@ Node classes list of azure provider. ManagedIdentities **diagrams.azure.identity.ManagedIdentities** +MultiFactorAuthentication +**diagrams.azure.identity.MultiFactorAuthentication** + PrivateAccess **diagrams.azure.identity.PrivateAccess** @@ -1142,6 +1187,9 @@ Node classes list of azure provider. VerifiableCredentials **diagrams.azure.identity.VerifiableCredentials** +VerificationAsAService +**diagrams.azure.identity.VerificationAsAService** + ## azure.integration @@ -1178,6 +1226,9 @@ Node classes list of azure provider. AzureStackEdge **diagrams.azure.integration.AzureStackEdge** +BusinessProcessTracking +**diagrams.azure.integration.BusinessProcessTracking** + DataCatalog **diagrams.azure.integration.DataCatalog** @@ -1280,6 +1331,9 @@ Node classes list of azure provider. Ebooks **diagrams.azure.intune.Ebooks** +EntraIdentityRolesAndAdministrators +**diagrams.azure.intune.EntraIdentityRolesAndAdministrators** + ExchangeAccess **diagrams.azure.intune.ExchangeAccess** @@ -1319,6 +1373,9 @@ Node classes list of azure provider. AzureMapsAccounts **diagrams.azure.iot.AzureMapsAccounts** +AzureStackHciSizer +**diagrams.azure.iot.AzureStackHciSizer** + AzureStack **diagrams.azure.iot.AzureStack** @@ -1643,6 +1700,9 @@ Node classes list of azure provider. LogAnalyticsWorkspaces **diagrams.azure.monitor.LogAnalyticsWorkspaces** +Logs +**diagrams.azure.monitor.Logs** + Metrics **diagrams.azure.monitor.Metrics** @@ -1742,6 +1802,9 @@ Node classes list of azure provider. ## azure.networking +ApplicationGatewayContainers +**diagrams.azure.networking.ApplicationGatewayContainers** + ApplicationGateways **diagrams.azure.networking.ApplicationGateways** @@ -1898,27 +1961,144 @@ Node classes list of azure provider. ## azure.newicons +AIAtEdge +**diagrams.azure.newicons.AIAtEdge** + +AksNetworkPolicy +**diagrams.azure.newicons.AksNetworkPolicy** + +AzureAppTesting +**diagrams.azure.newicons.AzureAppTesting** + +AzureConsumptionCommitment +**diagrams.azure.newicons.AzureConsumptionCommitment** + +AzureContainerStorage +**diagrams.azure.newicons.AzureContainerStorage** + +AzureLinux +**diagrams.azure.newicons.AzureLinux** + +AzureLocal +**diagrams.azure.newicons.AzureLocal** + +AzureManagedRedis +**diagrams.azure.newicons.AzureManagedRedis** + AzureSustainability **diagrams.azure.newicons.AzureSustainability** +Breeze +**diagrams.azure.newicons.Breeze** + ConnectedVehiclePlatform **diagrams.azure.newicons.ConnectedVehiclePlatform** +DataVirtualization +**diagrams.azure.newicons.DataVirtualization** + +EdgeActions +**diagrams.azure.newicons.EdgeActions** + +EdgeStorageAccelerator +**diagrams.azure.newicons.EdgeStorageAccelerator** + +EngageCenterConnect +**diagrams.azure.newicons.EngageCenterConnect** + EntraConnectHealth **diagrams.azure.newicons.EntraConnectHealth** EntraConnectSync **diagrams.azure.newicons.EntraConnectSync** +ExternalIDModified +**diagrams.azure.newicons.ExternalIDModified** + +ExternalID +**diagrams.azure.newicons.ExternalID** + +FrdQa +**diagrams.azure.newicons.FrdQa** + +HybridConnectivityHub +**diagrams.azure.newicons.HybridConnectivityHub** + IcmTroubleshooting **diagrams.azure.newicons.IcmTroubleshooting** +KubernetesHub +**diagrams.azure.newicons.KubernetesHub** + +LandingZone +**diagrams.azure.newicons.LandingZone** + +LoadBalancerHub +**diagrams.azure.newicons.LoadBalancerHub** + +LogicAppsTemplate +**diagrams.azure.newicons.LogicAppsTemplate** + +MicrosoftDiscovery +**diagrams.azure.newicons.MicrosoftDiscovery** + +MonitorIssues +**diagrams.azure.newicons.MonitorIssues** + +NetworkFoundationHub +**diagrams.azure.newicons.NetworkFoundationHub** + +NetworkSecurityHub +**diagrams.azure.newicons.NetworkSecurityHub** + +OperationCenter +**diagrams.azure.newicons.OperationCenter** + Osconfig **diagrams.azure.newicons.Osconfig** +PlanetaryComputerPro +**diagrams.azure.newicons.PlanetaryComputerPro** + +Promethus +**diagrams.azure.newicons.Promethus**, **Prometheus** (alias) + +Pubsub +**diagrams.azure.newicons.Pubsub** + +ScheduledActions +**diagrams.azure.newicons.ScheduledActions** + +ServiceGroupRelationships +**diagrams.azure.newicons.ServiceGroupRelationships** + +ServiceGroups +**diagrams.azure.newicons.ServiceGroups** + +SQLDatabaseFleetManager +**diagrams.azure.newicons.SQLDatabaseFleetManager** + +StageMaps +**diagrams.azure.newicons.StageMaps** + StorageActions **diagrams.azure.newicons.StorageActions** +StorageHubs +**diagrams.azure.newicons.StorageHubs** + +ToolchainOrchestrator +**diagrams.azure.newicons.ToolchainOrchestrator** + +VnetAppliance +**diagrams.azure.newicons.VnetAppliance** + +Vpnclientwindows +**diagrams.azure.newicons.Vpnclientwindows** + +WorkloadOrchestration +**diagrams.azure.newicons.WorkloadOrchestration** + ## azure.other @@ -2018,6 +2198,9 @@ Node classes list of azure provider. AzureSupportCenterBlue **diagrams.azure.other.AzureSupportCenterBlue** +AzureSustainability +**diagrams.azure.other.AzureSustainability** + AzureVideoIndexer **diagrams.azure.other.AzureVideoIndexer** @@ -2060,6 +2243,9 @@ Node classes list of azure provider. ConfidentialLedgers **diagrams.azure.other.ConfidentialLedgers** +ConnectedVehiclePlatform +**diagrams.azure.other.ConnectedVehiclePlatform** + ContainerAppsEnvironments **diagrams.azure.other.ContainerAppsEnvironments** @@ -2162,6 +2348,9 @@ Node classes list of azure provider. ElasticSan **diagrams.azure.other.ElasticSan** +EntraIdentityLicenses +**diagrams.azure.other.EntraIdentityLicenses** + ExchangeOnPremisesAccess **diagrams.azure.other.ExchangeOnPremisesAccess** @@ -2180,6 +2369,9 @@ Node classes list of azure provider. HdiAksCluster **diagrams.azure.other.HdiAksCluster** +IcmTroubleshooting +**diagrams.azure.other.IcmTroubleshooting** + InstancePools **diagrams.azure.other.InstancePools** @@ -2213,6 +2405,9 @@ Node classes list of azure provider. ModularDataCenter **diagrams.azure.other.ModularDataCenter** +MonitorHealthModels +**diagrams.azure.other.MonitorHealthModels** + NetworkManagers **diagrams.azure.other.NetworkManagers** @@ -2222,6 +2417,9 @@ Node classes list of azure provider. OpenSupplyChainPlatform **diagrams.azure.other.OpenSupplyChainPlatform** +Osconfig +**diagrams.azure.other.Osconfig** + PeeringService **diagrams.azure.other.PeeringService** @@ -2291,6 +2489,9 @@ Node classes list of azure provider. VMImageVersion **diagrams.azure.other.VMImageVersion** +WacInstaller +**diagrams.azure.other.WacInstaller** + Wac **diagrams.azure.other.Wac** @@ -2342,6 +2543,12 @@ Node classes list of azure provider. Detonation **diagrams.azure.security.Detonation** +EntraIdentityRiskySignins +**diagrams.azure.security.EntraIdentityRiskySignins** + +EntraIdentityRiskyUsers +**diagrams.azure.security.EntraIdentityRiskyUsers** + ExtendedSecurityUpdates **diagrams.azure.security.ExtendedSecurityUpdates** @@ -2426,6 +2633,9 @@ Node classes list of azure provider. ImportExportJobs **diagrams.azure.storage.ImportExportJobs** +ManagedFileShares +**diagrams.azure.storage.ManagedFileShares** + NetappFiles **diagrams.azure.storage.NetappFiles** @@ -2441,6 +2651,9 @@ Node classes list of azure provider. StorageAccounts **diagrams.azure.storage.StorageAccounts** +StorageActions +**diagrams.azure.storage.StorageActions** + StorageExplorer **diagrams.azure.storage.StorageExplorer** @@ -2483,6 +2696,9 @@ Node classes list of azure provider. AppServices **diagrams.azure.web.AppServices** +AppSpaceComponent +**diagrams.azure.web.AppSpaceComponent** + AppSpace **diagrams.azure.web.AppSpace** diff --git a/playground/.gitignore b/playground/.gitignore new file mode 100644 index 00000000..c7f06306 --- /dev/null +++ b/playground/.gitignore @@ -0,0 +1,12 @@ +node_modules/ +dist/ +public/icons/ +public/wheels/ +public/catalog.json +test-results/ +playwright-report/ +*.tsbuildinfo +vite.config.js +vite.config.d.ts +.omc/ +.vite/ diff --git a/playground/e2e/playground.spec.ts b/playground/e2e/playground.spec.ts new file mode 100644 index 00000000..c0603b12 --- /dev/null +++ b/playground/e2e/playground.spec.ts @@ -0,0 +1,57 @@ +import { expect, test } from "@playwright/test"; + +test.beforeEach(async ({ page }) => { + await page.goto("/"); + // wait for Pyodide init + the default example to finish rendering + await expect(page.getByTestId("preview").locator("svg")).toBeVisible({ timeout: 150_000 }); +}); + +test("renders the default example with icons", async ({ page }) => { + const preview = page.getByTestId("preview"); + await expect(preview.locator("svg image").first()).toHaveAttribute("xlink:href", /icons\/aws\//); +}); + +test("autocompletes EC2 from the aws compute module", async ({ page }) => { + const editor = page.getByTestId("editor").locator(".cm-content"); + await editor.click(); + await page.keyboard.press("ControlOrMeta+a"); + await page.keyboard.type("from diagrams.aws.compute import EC2A"); + await expect(page.locator(".cm-tooltip-autocomplete")).toContainText("EC2AutoScaling", { timeout: 10_000 }); +}); + +test("share link roundtrips code", async ({ page, context }) => { + const editor = page.getByTestId("editor").locator(".cm-content"); + await editor.click(); + await page.keyboard.press("ControlOrMeta+a"); + await page.keyboard.type('from diagrams import Diagram\nwith Diagram("Shared", show=False):\n pass'); + await page.getByTestId("share-button").click(); + await expect(page.getByTestId("share-button")).toContainText("Link copied!"); + const url = page.url(); + expect(url).toContain("#code="); + const second = await context.newPage(); + await second.goto(url); + await expect(second.getByTestId("editor")).toContainText("Shared", { timeout: 150_000 }); +}); + +test("python errors keep the previous preview", async ({ page }) => { + const editor = page.getByTestId("editor").locator(".cm-content"); + await editor.click(); + await page.keyboard.press("ControlOrMeta+a"); + await page.keyboard.type("1/0"); + await expect(page.getByTestId("error-panel")).toContainText("ZeroDivisionError", { timeout: 30_000 }); + await expect(page.getByTestId("preview").locator("svg")).toBeVisible(); +}); + +test("exports PNG", async ({ page }) => { + const downloadPromise = page.waitForEvent("download"); + // Background White is the ExportBar's default; WIDTH/HEIGHT left as + // "auto" fall back to the default-2x output size. + await page.getByRole("button", { name: "PNG" }).click(); + const download = await downloadPromise; + expect(download.suggestedFilename()).toMatch(/\.png$/); +}); + +test("copies image to clipboard", async ({ page }) => { + await page.getByRole("button", { name: "Copy Image" }).click(); + await expect(page.getByText("Copied!")).toBeVisible(); +}); diff --git a/playground/index.html b/playground/index.html new file mode 100644 index 00000000..282ecb37 --- /dev/null +++ b/playground/index.html @@ -0,0 +1,121 @@ + + + + + + Diagrams Playground — Online Python Diagram Editor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Diagrams Playground

+

+ Write diagrams code in Python and see the rendered cloud architecture diagram + instantly — right in your browser, with nothing to install. +

+

+ Loading the playground… +

+ +

+ Documentation + · + GitHub +

+
+
+ + + diff --git a/playground/package-lock.json b/playground/package-lock.json new file mode 100644 index 00000000..20b74ffe --- /dev/null +++ b/playground/package-lock.json @@ -0,0 +1,3418 @@ +{ + "name": "diagrams-playground", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "diagrams-playground", + "version": "0.1.0", + "dependencies": { + "@codemirror/autocomplete": "^6.18.0", + "@codemirror/commands": "^6.10.4", + "@codemirror/lang-python": "^6.1.6", + "@codemirror/language": "^6.12.4", + "@codemirror/state": "^6.4.1", + "@codemirror/view": "^6.34.0", + "@hpcc-js/wasm-graphviz": "^1.7.0", + "@lezer/highlight": "^1.2.3", + "codemirror": "^6.0.1", + "dompurify": "^3.1.6", + "pako": "^2.1.0", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@playwright/test": "^1.47.0", + "@testing-library/react": "^16.0.1", + "@types/pako": "^2.0.3", + "@types/react": "^18.3.5", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "jsdom": "^25.0.0", + "typescript": "~5.6.2", + "vite": "^5.4.3", + "vitest": "^2.1.0" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@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.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@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.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "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.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@codemirror/autocomplete": { + "version": "6.20.3", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.3.tgz", + "integrity": "sha512-tlosUqb+3BbxCxZdu4tKeRghPFC+QM7q4X5YhKV2eCmPG+1r2F3f4AaSz5sCrFqUtX4Jh20VFTKecl16MgiV9g==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.4.tgz", + "integrity": "sha512-Ryk9y9T0FFVF0cUGhAknveAyUOl/A1qReTFi+qPKtOh2Z9F4AUBz3XOrYD4ZEgZirdugVzHvd/2/Wcwy5OliTg==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.7.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/lang-python": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-python/-/lang-python-6.2.1.tgz", + "integrity": "sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.3.2", + "@codemirror/language": "^6.8.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.2.1", + "@lezer/python": "^1.1.4" + } + }, + "node_modules/@codemirror/language": { + "version": "6.12.4", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.4.tgz", + "integrity": "sha512-1q4PaT+o6PbgpkJt4Q8Fv5XJxTy4FUZ4MWETtyiDw3J0Pyr9E2vqcKL+k9wcvjNTIsauxvE7OfmWj3FRPHQ76A==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.7.tgz", + "integrity": "sha512-28/+iWLYxKxsvGYhSYL7zaCZqLz5+FFFDq9tVsvGv9kv8RY4fFAchJ5WX9M3YrrRlTIsECjsXPqeNgnSmNP2dg==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.42.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/search": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.7.1.tgz", + "integrity": "sha512-uMe5UO6PamJtSHrXhhHOzSX3ReWtiJrva6GnPMwSOrZtiExb5X5eExhr2OUZQVvdxPsKpY3Ro2mFbQadpPWmHA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.37.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.7.1.tgz", + "integrity": "sha512-9QzNDgE4EYDnAHfrTlR2lwiPciiOymLtwKK+8yHQzCc7GXhAP9xdEbEJFy2IWB1j9UGUl9BsgMmTo/ImA02T7A==", + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.43.7", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.7.tgz", + "integrity": "sha512-FZsExxkoxnAN+d9TgqXLg5g4A1oQwzX9WlkOT5i2PKkcW7xx3Bmu0vs90g6fo9Mpdsb/l96dnAraQ8932aO4/g==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.7.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "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/@hpcc-js/wasm-graphviz": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@hpcc-js/wasm-graphviz/-/wasm-graphviz-1.28.0.tgz", + "integrity": "sha512-KqvPy7ckxPc1xv8Kt7H3bo9cj8U4gfOXxQ8grLZvDZxyQQjnXsKxrweJLLJtEoWWtQqAk4biUB0g2qsjkTiMVA==", + "license": "Apache-2.0" + }, + "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/@lezer/common": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.2.tgz", + "integrity": "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==", + "license": "MIT" + }, + "node_modules/@lezer/highlight": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", + "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.3.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.10.tgz", + "integrity": "sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/python": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@lezer/python/-/python-1.1.19.tgz", + "integrity": "sha512-MhQIURHRytsNzP/YXnqpYKW6la6voAH3kyplTOOiCdjyFY6cWWGFVmYVdHIPrElqSDf4iCDktQCockB9FxuhzQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.3.tgz", + "integrity": "sha512-FY+MKLBoTsLNJF/eLWaOsXGdz6uh3Iu1axjPf6TUq92IYumcTcXWHoS747JARLkcdlJ/Waiaxc5wQfFO8jC6NA==", + "license": "MIT" + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@playwright/test": { + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.62.1.tgz", + "integrity": "sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.62.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "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-android-arm-eabi": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.4.tgz", + "integrity": "sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.4.tgz", + "integrity": "sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.4.tgz", + "integrity": "sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.4.tgz", + "integrity": "sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.4.tgz", + "integrity": "sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.4.tgz", + "integrity": "sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.4.tgz", + "integrity": "sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.4.tgz", + "integrity": "sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.4.tgz", + "integrity": "sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.4.tgz", + "integrity": "sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.4.tgz", + "integrity": "sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.4.tgz", + "integrity": "sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.4.tgz", + "integrity": "sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.4.tgz", + "integrity": "sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.4.tgz", + "integrity": "sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.4.tgz", + "integrity": "sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.4.tgz", + "integrity": "sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.4.tgz", + "integrity": "sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.4.tgz", + "integrity": "sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.4.tgz", + "integrity": "sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.4.tgz", + "integrity": "sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.4.tgz", + "integrity": "sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.4.tgz", + "integrity": "sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.4.tgz", + "integrity": "sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.4.tgz", + "integrity": "sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "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.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/pako": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz", + "integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==", + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.31", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz", + "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", + "dev": 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/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, + "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/@vitest/expect": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", + "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", + "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", + "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", + "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "2.1.9", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", + "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", + "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", + "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.12", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.12.tgz", + "integrity": "sha512-r7WnVImvVCeFpf2DOXfy41aPWzeNg3H/A2X4dKmy1QL0MSyyk/e7z8ihJ3N6Nn2PsdhkVlqnEfnUE4a05P2aTA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz", + "integrity": "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==", + "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.44", + "caniuse-lite": "^1.0.30001806", + "electron-to-chromium": "^1.5.393", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001806", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", + "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/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/codemirror": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", + "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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/crelt": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.7.tgz", + "integrity": "sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA==", + "license": "MIT" + }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cssstyle/node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "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/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/dompurify": { + "version": "3.4.13", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.13.tgz", + "integrity": "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.400", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.400.tgz", + "integrity": "sha512-96EWDNjM59SYflgeV5Ylsf4EMiq1a25YjCnJH7cxn/AF2H3pILRweaUnoLax0yKHWdpOzY6JKEu45e8irqZIHA==", + "dev": true, + "license": "ISC" + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "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/jsdom": { + "version": "25.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz", + "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.1.0", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.12", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.7.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "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/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "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/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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.17", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.17.tgz", + "integrity": "sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==", + "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.52", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.52.tgz", + "integrity": "sha512-MRlTqhAfoMx/4mhEbPo3Hi02g9LJZaJkka69V6h67Cb1gjrAG0jsTE4CZX1eptNx+VCAwJmfpnDIF4P0Nh1A7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/nwsapi": { + "version": "2.2.24", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.24.tgz", + "integrity": "sha512-7YRhZ3jS45LwmSCT4b2sVFHt/WuovaktDU07QrtOBY2PXskss5a9jfmR9jptyumwXST+rFjrmppMY1KT/yn35A==", + "dev": true, + "license": "MIT" + }, + "node_modules/pako": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.2.0.tgz", + "integrity": "sha512-zJq6RP/5q+TO2OpFV3FHzlPnFjmkb7Nc99a5SNjJE+uu/PkpChs+NIZSSzbBoD+6kjiISXjfYdwj1ZRQ81dz/w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "(MIT AND Zlib)" + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "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/playwright": { + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.62.1.tgz", + "integrity": "sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.62.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.62.1.tgz", + "integrity": "sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/postcss": { + "version": "8.5.25", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", + "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", + "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.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "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.62.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.4.tgz", + "integrity": "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.62.4", + "@rollup/rollup-android-arm64": "4.62.4", + "@rollup/rollup-darwin-arm64": "4.62.4", + "@rollup/rollup-darwin-x64": "4.62.4", + "@rollup/rollup-freebsd-arm64": "4.62.4", + "@rollup/rollup-freebsd-x64": "4.62.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.4", + "@rollup/rollup-linux-arm-musleabihf": "4.62.4", + "@rollup/rollup-linux-arm64-gnu": "4.62.4", + "@rollup/rollup-linux-arm64-musl": "4.62.4", + "@rollup/rollup-linux-loong64-gnu": "4.62.4", + "@rollup/rollup-linux-loong64-musl": "4.62.4", + "@rollup/rollup-linux-ppc64-gnu": "4.62.4", + "@rollup/rollup-linux-ppc64-musl": "4.62.4", + "@rollup/rollup-linux-riscv64-gnu": "4.62.4", + "@rollup/rollup-linux-riscv64-musl": "4.62.4", + "@rollup/rollup-linux-s390x-gnu": "4.62.4", + "@rollup/rollup-linux-x64-gnu": "4.62.4", + "@rollup/rollup-linux-x64-musl": "4.62.4", + "@rollup/rollup-openbsd-x64": "4.62.4", + "@rollup/rollup-openharmony-arm64": "4.62.4", + "@rollup/rollup-win32-arm64-msvc": "4.62.4", + "@rollup/rollup-win32-ia32-msvc": "4.62.4", + "@rollup/rollup-win32-x64-gnu": "4.62.4", + "@rollup/rollup-win32-x64-msvc": "4.62.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "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/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "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/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/style-mod": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", + "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", + "license": "MIT" + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "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/vite-node": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz", + "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/vitest": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", + "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.9", + "@vitest/mocker": "2.1.9", + "@vitest/pretty-format": "^2.1.9", + "@vitest/runner": "2.1.9", + "@vitest/snapshot": "2.1.9", + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", + "pathe": "^1.1.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.9", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.9", + "@vitest/ui": "2.1.9", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ws": { + "version": "8.21.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.2.tgz", + "integrity": "sha512-54dMVAo4WIe6SKy3vBgN+9bJZqqQ8IMRevAkOLQALhi49qkkQDQfWdAZ8KQlXiEabw88ARXXdUrlvtbKQX+aKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "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" + } + } +} diff --git a/playground/package.json b/playground/package.json new file mode 100644 index 00000000..4734540d --- /dev/null +++ b/playground/package.json @@ -0,0 +1,41 @@ +{ + "name": "diagrams-playground", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "assets": "cd .. && python3 playground/scripts/gen_catalog.py --repo-root . --out playground/public", + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview --port 4173", + "test": "vitest run", + "e2e": "playwright test" + }, + "dependencies": { + "@codemirror/autocomplete": "^6.18.0", + "@codemirror/commands": "^6.10.4", + "@codemirror/lang-python": "^6.1.6", + "@codemirror/language": "^6.12.4", + "@codemirror/state": "^6.4.1", + "@codemirror/view": "^6.34.0", + "@lezer/highlight": "^1.2.3", + "@hpcc-js/wasm-graphviz": "^1.7.0", + "codemirror": "^6.0.1", + "dompurify": "^3.1.6", + "pako": "^2.1.0", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@playwright/test": "^1.47.0", + "@testing-library/react": "^16.0.1", + "@types/pako": "^2.0.3", + "@types/react": "^18.3.5", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "jsdom": "^25.0.0", + "typescript": "~5.6.2", + "vite": "^5.4.3", + "vitest": "^2.1.0" + } +} diff --git a/playground/playwright.config.ts b/playground/playwright.config.ts new file mode 100644 index 00000000..327f6e45 --- /dev/null +++ b/playground/playwright.config.ts @@ -0,0 +1,27 @@ +import { defineConfig } from "@playwright/test"; + +export default defineConfig({ + testDir: "e2e", + timeout: 180_000, // pyodide 초기 로드 포함 + // Conservative default: each spec cold-loads Pyodide (runtime + wheel + // install) in its own context, so parallel workers mean simultaneous + // large downloads/WASM boots competing for CPU and network — a known + // flakiness source on constrained CI runners, though parallel runs do + // pass locally. Serial trades wall-clock time for determinism. + fullyParallel: false, + workers: 1, + use: { + baseURL: "http://localhost:4173", + // Headless Chromium does not grant clipboard-write by default, so + // navigator.clipboard.writeText() rejects with NotAllowedError unless + // explicitly granted here (needed for the "share link" test's + // writeText().then(...) success path). + permissions: ["clipboard-read", "clipboard-write"], + }, + webServer: { + command: "npm run preview", + port: 4173, + reuseExistingServer: true, + timeout: 30_000, + }, +}); diff --git a/playground/public/diagrams-logo.png b/playground/public/diagrams-logo.png new file mode 100644 index 00000000..7a4de090 Binary files /dev/null and b/playground/public/diagrams-logo.png differ diff --git a/playground/public/og-image.png b/playground/public/og-image.png new file mode 100644 index 00000000..ddb61afd Binary files /dev/null and b/playground/public/og-image.png differ diff --git a/playground/scripts/gen_catalog.py b/playground/scripts/gen_catalog.py new file mode 100644 index 00000000..3f9dd592 --- /dev/null +++ b/playground/scripts/gen_catalog.py @@ -0,0 +1,120 @@ +"""Generate playground build assets from the diagrams package. + +Outputs (under --out): + catalog.json node classes / aliases / icons / constructor signatures + icons/** copy of resources/ for the preview tags + wheels/*.whl slim diagrams wheel (resources stripped; not needed at + runtime because Node._load_icon only builds path strings) + wheels/manifest.json {"wheel": ""} for the worker to locate it +""" + +import argparse +import importlib +import inspect +import json +import pkgutil +import shutil +import subprocess +import sys +import tempfile +import zipfile +from pathlib import Path + + +def build_catalog(repo_root: Path) -> dict: + sys.path.insert(0, str(repo_root)) + import diagrams + from diagrams import Cluster, Diagram, Edge, Node + + modules = {} + for info in pkgutil.walk_packages([str(repo_root / "diagrams")], prefix="diagrams."): + if any(part.startswith("_") for part in info.name.split(".")): + continue + mod = importlib.import_module(info.name) + classes, aliases = {}, {} + for attr, val in vars(mod).items(): + if attr.startswith("_") or not inspect.isclass(val): + continue + if not issubclass(val, Node) or val.__module__ != info.name: + continue + if getattr(val, "_icon", None) is None: + continue + # Check if icon file exists on disk + icon_rel = "/".join([*Path(val._icon_dir).parts[1:], val._icon]) + if not (repo_root / "resources" / icon_rel).exists(): + print( + f"warning: skipping {info.name}.{val.__name__} — missing icon resources/{icon_rel}", file=sys.stderr + ) + continue + if attr == val.__name__: + classes[attr] = val + else: # module-level alias assignment (e.g. ECS = ElasticContainerService) + aliases.setdefault(val.__name__, []).append(attr) + if classes: + modules[info.name] = [ + { + "name": name, + "aliases": sorted(aliases.get(name, [])), + # _icon_dir is "resources/aws/compute" — strip leading segment + "icon": "/".join([*Path(cls._icon_dir).parts[1:], cls._icon]), + } + for name, cls in sorted(classes.items()) + ] + + def signature_params(fn) -> list: + return [str(p) for p in list(inspect.signature(fn).parameters.values())[1:]] + + return { + "modules": modules, + "signatures": { + "Diagram": signature_params(Diagram.__init__), + "Cluster": signature_params(Cluster.__init__), + "Edge": signature_params(Edge.__init__), + }, + } + + +def build_slim_wheel(repo_root: Path, out_dir: Path) -> Path: + out_dir.mkdir(parents=True, exist_ok=True) + with tempfile.TemporaryDirectory() as tmp: + subprocess.run( + [sys.executable, "-m", "pip", "wheel", "--no-deps", "-w", tmp, str(repo_root)], + check=True, + ) + src = next(Path(tmp).glob("diagrams-*.whl")) + dst = out_dir / src.name + with zipfile.ZipFile(src) as zin, zipfile.ZipFile(dst, "w", zipfile.ZIP_DEFLATED) as zout: + for item in zin.infolist(): + if item.filename.startswith("resources/"): + continue + data = zin.read(item.filename) + if item.filename.endswith(".dist-info/RECORD"): + lines = [l for l in data.decode().splitlines() if not l.startswith("resources/")] + data = ("\n".join(lines) + "\n").encode() + zout.writestr(item, data) + return dst + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--repo-root", type=Path, required=True) + parser.add_argument("--out", type=Path, required=True) + args = parser.parse_args() + repo_root, out = args.repo_root.resolve(), args.out.resolve() + + catalog = build_catalog(repo_root) + out.mkdir(parents=True, exist_ok=True) + (out / "catalog.json").write_text(json.dumps(catalog)) + print(f"catalog.json: {sum(len(v) for v in catalog['modules'].values())} classes") + + icons_dir = out / "icons" + shutil.copytree(repo_root / "resources", icons_dir, dirs_exist_ok=True) + print(f"icons: copied to {icons_dir}") + + wheel = build_slim_wheel(repo_root, out / "wheels") + (out / "wheels" / "manifest.json").write_text(json.dumps({"wheel": wheel.name})) + print(f"wheel: {wheel.name} ({wheel.stat().st_size // 1024} KiB)") + + +if __name__ == "__main__": + main() diff --git a/playground/scripts/test_gen_catalog.py b/playground/scripts/test_gen_catalog.py new file mode 100644 index 00000000..eda44f3f --- /dev/null +++ b/playground/scripts/test_gen_catalog.py @@ -0,0 +1,60 @@ +import importlib.util +import zipfile +from pathlib import Path + +# Load the sibling gen_catalog.py by file path rather than a bare +# `from gen_catalog import ...` after a sys.path hack — the latter forces an +# import that isort keeps reordering above the path setup (breaking it) and +# that seed-isort-config misclassifies as third-party. +_spec = importlib.util.spec_from_file_location("gen_catalog", Path(__file__).parent / "gen_catalog.py") +gen_catalog = importlib.util.module_from_spec(_spec) +_spec.loader.exec_module(gen_catalog) +build_catalog = gen_catalog.build_catalog +build_slim_wheel = gen_catalog.build_slim_wheel + +REPO_ROOT = Path(__file__).resolve().parents[2] + + +def test_catalog_contains_ec2_with_icon(): + catalog = build_catalog(REPO_ROOT) + compute = catalog["modules"]["diagrams.aws.compute"] + ec2 = next(c for c in compute if c["name"] == "EC2") + assert ec2["icon"] == "aws/compute/ec2.png" + assert (REPO_ROOT / "resources" / ec2["icon"]).exists() + + +def test_catalog_records_aliases(): + catalog = build_catalog(REPO_ROOT) + compute = catalog["modules"]["diagrams.aws.compute"] + ecs = next(c for c in compute if c["name"] == "ElasticContainerService") + assert "ECS" in ecs["aliases"] + + +def test_catalog_signatures_have_core_classes(): + catalog = build_catalog(REPO_ROOT) + assert any(p.startswith("name") for p in catalog["signatures"]["Diagram"]) + assert any(p.startswith("label") for p in catalog["signatures"]["Cluster"]) + assert any(p.startswith("forward") for p in catalog["signatures"]["Edge"]) + + +def test_all_catalog_icons_exist_on_disk(): + catalog = build_catalog(REPO_ROOT) + missing = [ + c["icon"] + for classes in catalog["modules"].values() + for c in classes + if not (REPO_ROOT / "resources" / c["icon"]).exists() + ] + assert missing == [] + + +def test_slim_wheel_has_no_resources(tmp_path): + wheel = build_slim_wheel(REPO_ROOT, tmp_path) + with zipfile.ZipFile(wheel) as zf: + names = zf.namelist() + assert not [n for n in names if n.startswith("resources/")] + assert [n for n in names if n.startswith("diagrams/")] + record = next(n for n in names if n.endswith(".dist-info/RECORD")) + record_body = zf.read(record).decode() + assert "resources/" not in record_body + assert wheel.stat().st_size < 3_000_000 # confirms the 38MB resources are stripped diff --git a/playground/scripts/test_shim.py b/playground/scripts/test_shim.py new file mode 100644 index 00000000..2176cff0 --- /dev/null +++ b/playground/scripts/test_shim.py @@ -0,0 +1,128 @@ +import json +import sys +from pathlib import Path + +import pytest + +REPO_ROOT = Path(__file__).resolve().parents[2] +SHIM = REPO_ROOT / "playground" / "src" / "worker" / "shim.py" +sys.path.insert(0, str(REPO_ROOT)) + + +# Fixture to restore json module after each test +@pytest.fixture(autouse=True) +def restore_json_module(): + import json.encoder as je + + original_dumps = json.dumps + original_encode_basestring_ascii = je.encode_basestring_ascii + original_encode_basestring = je.encode_basestring + original_c_encode_basestring_ascii = je.c_encode_basestring_ascii + original_c_make_encoder = je.c_make_encoder + yield + json.dumps = original_dumps + je.encode_basestring_ascii = original_encode_basestring_ascii + je.encode_basestring = original_encode_basestring + je.c_encode_basestring_ascii = original_c_encode_basestring_ascii + je.c_make_encoder = original_c_make_encoder + + +namespace = {} +exec(compile(SHIM.read_text(), str(SHIM), "exec"), namespace) +run_user_code = namespace["run_user_code"] + +SAMPLE = """ +from diagrams import Diagram +from diagrams.aws.compute import EC2 +from diagrams.aws.network import ELB + +with Diagram("Web Service", show=False): + ELB("lb") >> EC2("web") +""" + + +def test_captures_dot_source(): + result = json.loads(run_user_code(SAMPLE)) + assert result["error"] is None + assert len(result["dots"]) == 1 + assert result["dots"][0]["name"] == "Web Service" + assert "elastic-load-balancing.png" in result["dots"][0]["source"] + assert "digraph" in result["dots"][0]["source"] + + +def test_no_output_files_written(tmp_path, monkeypatch): + monkeypatch.chdir(tmp_path) + json.loads(run_user_code(SAMPLE)) + assert list(tmp_path.iterdir()) == [] + + +def test_captures_multiple_diagrams(): + code = SAMPLE + '\nwith Diagram("Second", show=False):\n EC2("solo")\n' + result = json.loads(run_user_code(code)) + assert [d["name"] for d in result["dots"]] == ["Web Service", "Second"] + + +def test_explicit_render_call_not_duplicated(): + code = """ +from diagrams import Diagram +from diagrams.aws.compute import EC2 +with Diagram("D", show=False) as d: + EC2("a") + d.render() +""" + result = json.loads(run_user_code(code)) + assert len(result["dots"]) == 1 + + +def test_error_returns_clean_traceback(): + result = json.loads(run_user_code("from diagrams import Diagram\n1/0\n")) + assert result["dots"] == [] + assert "ZeroDivisionError" in result["error"] + assert "line 2" in result["error"] + assert "shim.py" not in result["error"] + + +def test_stdout_captured(): + result = json.loads(run_user_code('print("hello")')) + assert result["stdout"] == "hello\n" + + +def test_exception_inside_diagram_block_not_captured(): + code = """ +from diagrams import Diagram +from diagrams.aws.compute import EC2 +try: + with Diagram("Broken", show=False): + EC2("a") + raise RuntimeError("boom") +except RuntimeError: + pass +with Diagram("After", show=False): + EC2("b") +""" + result = json.loads(run_user_code(code)) + assert [d["name"] for d in result["dots"]] == ["After"] + assert result["error"] is None + + +def test_json_sabotage_still_returns_json(): + code = "import json\njson.dumps = None\nprint('ok')" + result = json.loads(run_user_code(code)) + assert result["error"] is None + assert result["stdout"] == "ok\n" + + +def test_json_encoder_sabotage_still_returns_json(): + code = """ +import json.encoder as je +def evil(*a, **k): + raise RuntimeError("pwned") +je.encode_basestring_ascii = evil +je.encode_basestring = evil +je.c_encode_basestring_ascii = None +je.c_make_encoder = None +""" + result = json.loads(run_user_code(code)) + assert result["dots"] == [] + assert "Internal error serializing result" in result["error"] + assert "pwned" in result["error"] diff --git a/playground/src/App.tsx b/playground/src/App.tsx new file mode 100644 index 00000000..f66246c0 --- /dev/null +++ b/playground/src/App.tsx @@ -0,0 +1,241 @@ +import { autocompletion } from "@codemirror/autocomplete"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import DragHandle from "./components/DragHandle"; +import EditorPane from "./components/EditorPane"; +import ErrorPanel from "./components/ErrorPanel"; +import ExamplesGallery from "./components/ExamplesGallery"; +import ExportBar from "./components/ExportBar"; +import NodeSearch from "./components/NodeSearch"; +import PreviewPane from "./components/PreviewPane"; +import Toolbar from "./components/Toolbar"; +import { diagramsCompletions } from "./completions/imports"; +import { signatureTooltip } from "./completions/signature"; +import { DEFAULT_CODE, EXAMPLES } from "./examples"; +import { renderDot } from "./renderer/render"; +import { decodeShare, encodeShare } from "./share/codec"; +import type { Catalog } from "./types"; +import { debounce } from "./utils/debounce"; +import { clampRatio, clampSidebarWidth, DEFAULT_RATIO, DEFAULT_SIDEBAR_WIDTH } from "./utils/layout"; +import { renameDiagramInCode } from "./utils/rename"; +import { initTheme } from "./utils/theme"; +import { PyClient, SupersededError, TimeoutError } from "./worker/client"; + +const SPLIT_STORAGE_KEY = "dgp-split"; +const SIDEBAR_STORAGE_KEY = "dgp-sidebar"; + +function loadStoredRatio(): number { + const stored = Number(localStorage.getItem(SPLIT_STORAGE_KEY)); + return clampRatio(Number.isFinite(stored) && stored !== 0 ? stored : DEFAULT_RATIO); +} + +function loadStoredSidebarWidth(): number { + const stored = Number(localStorage.getItem(SIDEBAR_STORAGE_KEY)); + return clampSidebarWidth(Number.isFinite(stored) && stored !== 0 ? stored : DEFAULT_SIDEBAR_WIDTH); +} + +const STATUS_BY_STAGE: Record = { + pyodide: "Loading Python runtime… (first visit only)", + packages: "Installing diagrams package…", + ready: "Ready", +}; + +export default function App() { + const clientRef = useRef(); + const replaceCodeRef = useRef<(code: string) => void>(); + const codeRef = useRef(decodeShare(window.location.hash) ?? DEFAULT_CODE); + + const [catalog, setCatalog] = useState(null); + const [catalogError, setCatalogError] = useState(null); + const [status, setStatus] = useState("Starting…"); + const [ready, setReady] = useState(false); + const [svgs, setSvgs] = useState<{ name: string; svg: string }[]>([]); + const [error, setError] = useState(null); + const [rendering, setRendering] = useState(false); + const [shared, setShared] = useState(false); + const [splitRatio, setSplitRatio] = useState(loadStoredRatio); + const [sidebarWidth, setSidebarWidth] = useState(loadStoredSidebarWidth); + const [lineCount, setLineCount] = useState(() => codeRef.current.split("\n").length); + // A share-link boot loads its code straight into the editor, so no example + // pill should read as "active" until the user explicitly picks one. + const [activeExample, setActiveExample] = useState(() => + decodeShare(window.location.hash) !== null ? null : EXAMPLES[0].title + ); + const [renderMs, setRenderMs] = useState(null); + const splitContainerRef = useRef(null); + const mainRef = useRef(null); + + useEffect(() => { + initTheme(); + }, []); + + const handleSplitChange = useCallback((ratio: number) => { + const clamped = clampRatio(ratio); + setSplitRatio(clamped); + localStorage.setItem(SPLIT_STORAGE_KEY, String(clamped)); + }, []); + + const handleSidebarChange = useCallback((width: number) => { + const clamped = clampSidebarWidth(width); + setSidebarWidth(clamped); + localStorage.setItem(SIDEBAR_STORAGE_KEY, String(clamped)); + }, []); + + const execute = useCallback(async (code: string) => { + codeRef.current = code; + const client = clientRef.current; + if (!client) return; + setRendering(true); + const startedAt = performance.now(); + try { + const result = await client.run(code); + if (result.error) { + setError(result.error); // keep the last successful preview on screen + } else { + const rendered = await Promise.all( + result.dots.map(async (d) => ({ name: d.name, svg: await renderDot(d.source) })) + ); + setSvgs(rendered); + setError(null); + setRenderMs(Math.round(performance.now() - startedAt)); + } + } catch (err) { + if (err instanceof SupersededError) return; + if (err instanceof TimeoutError) { + setError("Execution timed out after 10s. The Python runtime was restarted (infinite loop?)."); + } else { + setError(String(err)); + } + } finally { + setRendering(false); + } + }, []); + + const debouncedExecute = useMemo(() => debounce(execute, 500), [execute]); + + // codeRef must track EVERY keystroke immediately — handleShare/insertImport + // read it synchronously; only the execution is debounced. + const handleEditorChange = useCallback( + (code: string) => { + codeRef.current = code; + setLineCount(code.split("\n").length); + debouncedExecute(code); + }, + [debouncedExecute] + ); + + useEffect(() => { + fetch("catalog.json") + .then((res) => { + if (!res.ok) throw new Error(`HTTP ${res.status}`); + return res.json(); + }) + .then(setCatalog) + .catch((err) => { + setCatalog(null); + setCatalogError( + `Node catalog failed to load (${err instanceof Error ? err.message : String(err)}) — autocomplete and node search are disabled.` + ); + }); + + const client = new PyClient(); + clientRef.current = client; + client + .init((stage) => { + if (clientRef.current === client) setStatus(STATUS_BY_STAGE[stage] ?? stage); + }) + .then(() => { + if (clientRef.current !== client) return; + setReady(true); + void execute(codeRef.current); + }) + .catch((err) => { + if (String(err).includes("disposed")) return; // our own cleanup + if (clientRef.current === client) setStatus(`Failed to start: ${err}`); + }); + return () => client.dispose(); + }, [execute]); + + const editorExtensions = useMemo(() => { + if (!catalog) return []; + return [ + autocompletion({ override: [diagramsCompletions(catalog)] }), + signatureTooltip(catalog.signatures), + ]; + }, [catalog]); + + function handleShare() { + const hash = `#code=${encodeShare(codeRef.current)}`; + window.history.replaceState(null, "", hash); + navigator.clipboard + .writeText(window.location.href) + .then(() => { + setShared(true); + setTimeout(() => setShared(false), 2000); + }) + .catch(() => setShared(false)); + } + + function loadCode(code: string) { + replaceCodeRef.current?.(code); + void execute(code); + } + + function insertImport(importStmt: string) { + loadCode(`${importStmt}\n${codeRef.current}`); + } + + function handleSelectExample(example: { title: string; code: string }) { + setActiveExample(example.title); + loadCode(example.code); + } + + function handleRenameDiagram(index: number, name: string) { + const next = renameDiagramInCode(codeRef.current, index, name); + if (next) loadCode(next); + } + + return ( +
+ + +
+ + clampSidebarWidth(clientX - rect.left)} + resetValue={DEFAULT_SIDEBAR_WIDTH} + /> +
+
+ (replaceCodeRef.current = fn)} + onRunNow={() => void execute(codeRef.current)} + onShare={handleShare} + /> + + +
+ + rect.width === 0 ? DEFAULT_RATIO : clampRatio(((clientX - rect.left) / rect.width) * 100) + } + resetValue={DEFAULT_RATIO} + /> + + + +
+
+
+ ); +} diff --git a/playground/src/app.css b/playground/src/app.css new file mode 100644 index 00000000..59bff561 --- /dev/null +++ b/playground/src/app.css @@ -0,0 +1,1308 @@ +/* ============================================================ + Diagrams Playground — light-first soft modern SaaS theme + ============================================================ */ + +/* ---------- Theme-agnostic tokens ---------- */ +:root { + --font-mono: "IBM Plex Mono", ui-monospace, "SFMono-Regular", Menlo, monospace; + --font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + + --radius-sm: 10px; /* buttons / inputs */ + --radius-md: 12px; /* cards / panels */ + --radius-lg: 16px; /* diagram card */ + + --accent: #6366f1; + --accent-hover: #4f46e5; + --accent-soft: #eef2ff; +} + +/* ---------- Light theme (DEFAULT) ---------- */ +:root[data-theme="light"] { + --page-bg: #ffffff; + --panel-bg: #ffffff; + --panel-alt: #f8f9fb; + --input-bg: #f3f4f6; + --border: #e5e7eb; + --text: #111827; + --text-muted: #6b7280; + + /* Accordion surfaces: provider rows sit on --panel-bg at rest; --surface-1 + tints a provider row's header on hover and while expanded + (aria-expanded), and --surface-2 tints that header's count badge in the + same states, kept distinct from --surface-1 so the badge still reads as + a separate pill. */ + --surface-1: #f1f2f5; + --surface-2: #fafbfc; + + --shadow-1: 0 1px 2px rgba(17, 24, 39, 0.04), 0 1px 3px rgba(17, 24, 39, 0.06); + --shadow-2: 0 4px 24px rgba(17, 24, 39, 0.08); + + --status-ready-bg: #dcfce7; + --status-ready-fg: #15803d; + --status-busy-bg: #fef3c7; + --status-busy-fg: #b45309; + --status-error-bg: #fee2e2; + --status-error-fg: #b91c1c; + + --share-bg: #111827; + --share-fg: #ffffff; + + --canvas-bg: #fafafb; + --canvas-dot: #d1d5db; + + --error-bg: #fef2f2; + --error-border: #fecaca; + --error-fg: #b91c1c; + + --chip-bg: rgba(255, 255, 255, 0.92); + --chip-fg: var(--text-muted); + + --cm-bg: #ffffff; + --cm-fg: var(--text); + --cm-caret: #111827; + --cm-selection: rgba(99, 102, 241, 0.18); + --cm-active-line: #f8f9fb; + --cm-gutter-bg: #ffffff; + --cm-gutter-fg: var(--text-muted); + --cm-gutter-border: var(--border); + --cm-active-gutter-bg: #f3f4f6; + --cm-active-gutter-fg: var(--text); + --cm-selection-match: rgba(99, 102, 241, 0.15); + --cm-matching-bracket-bg: rgba(99, 102, 241, 0.15); + --cm-matching-bracket-outline: var(--accent); + --cm-tooltip-bg: #ffffff; + --cm-tooltip-border: var(--border); + --cm-tooltip-fg: var(--text); + --cm-autocomplete-selected-bg: var(--accent); + --cm-autocomplete-selected-fg: #ffffff; + + --syn-keyword: #7c3aed; + --syn-string: #c2410c; + --syn-comment: #9ca3af; + --syn-function: #2563eb; + --syn-number: #0d9488; + --syn-operator: var(--text); + --syn-variable: var(--text); + --syn-property: #2563eb; + --syn-bool: #7c3aed; + --syn-atom: #7c3aed; +} + +/* ---------- Dark theme (neutral grays, indigo accent stays) ---------- */ +:root[data-theme="dark"] { + --page-bg: #0f1115; + --panel-bg: #16181d; + --panel-alt: #1b1e24; + --input-bg: #1e2127; + --border: #2a2d34; + --text: #e5e7eb; + --text-muted: #9ca3af; + + /* Accordion surfaces — dark equivalents, see light theme comment above. */ + --surface-1: #1e2128; + --surface-2: #1a1c22; + + --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.3), 0 2px 8px rgba(0, 0, 0, 0.24); + --shadow-2: 0 4px 24px rgba(0, 0, 0, 0.4); + + --status-ready-bg: rgba(34, 197, 94, 0.16); + --status-ready-fg: #4ade80; + --status-busy-bg: rgba(245, 158, 11, 0.16); + --status-busy-fg: #fbbf24; + --status-error-bg: rgba(239, 68, 68, 0.16); + --status-error-fg: #f87171; + + --share-bg: #ffffff; + --share-fg: #111827; + + --canvas-bg: #131519; + --canvas-dot: #2a2d34; + + --error-bg: rgba(239, 68, 68, 0.1); + --error-border: rgba(239, 68, 68, 0.35); + --error-fg: #f87171; + + --chip-bg: rgba(22, 24, 29, 0.85); + --chip-fg: var(--text-muted); + + --cm-bg: #16181d; + --cm-fg: var(--text); + --cm-caret: #e5e7eb; + --cm-selection: rgba(99, 102, 241, 0.32); + --cm-active-line: #1b1e24; + --cm-gutter-bg: #16181d; + --cm-gutter-fg: var(--text-muted); + --cm-gutter-border: var(--border); + --cm-active-gutter-bg: #1e2127; + --cm-active-gutter-fg: var(--text); + --cm-selection-match: rgba(99, 102, 241, 0.25); + --cm-matching-bracket-bg: rgba(99, 102, 241, 0.25); + --cm-matching-bracket-outline: var(--accent); + --cm-tooltip-bg: #1b1e24; + --cm-tooltip-border: var(--border); + --cm-tooltip-fg: var(--text); + --cm-autocomplete-selected-bg: var(--accent); + --cm-autocomplete-selected-fg: #ffffff; + + --syn-keyword: #a78bfa; + --syn-string: #fb923c; + --syn-comment: #6b7280; + --syn-function: #60a5fa; + --syn-number: #2dd4bf; + --syn-operator: var(--text); + --syn-variable: var(--text); + --syn-property: #60a5fa; + --syn-bool: #a78bfa; + --syn-atom: #a78bfa; +} + +/* ---------- Reset & base ---------- */ +* { + box-sizing: border-box; + margin: 0; +} +html, +body, +#root, +.app { + height: 100%; + font-family: var(--font-sans); + background: var(--page-bg); + color: var(--text); + -webkit-font-smoothing: antialiased; +} +.app { + display: flex; + flex-direction: column; + transition: background-color 160ms ease, color 160ms ease; +} + +/* ---------- Scrollbars ---------- */ +* { + scrollbar-width: thin; + scrollbar-color: var(--border) transparent; +} +*::-webkit-scrollbar { + width: 10px; + height: 10px; +} +*::-webkit-scrollbar-track { + background: transparent; +} +*::-webkit-scrollbar-thumb { + background: var(--border); + border-radius: 6px; + border: 2px solid transparent; + background-clip: padding-box; +} +*::-webkit-scrollbar-thumb:hover { + background: var(--accent); + background-clip: padding-box; +} + +/* ---------- Motion ---------- */ +@keyframes fade-slide { + from { + opacity: 0; + transform: translateY(6px); + } + to { + opacity: 1; + transform: translateY(0); + } +} +@keyframes loading-sweep { + 0% { + transform: translateX(-100%); + } + 100% { + transform: translateX(100%); + } +} +@media (prefers-reduced-motion: reduce) { + * { + animation-duration: 0.001ms !important; + animation-iteration-count: 1 !important; + } +} + +/* ---------- Buttons (shared baseline) ---------- */ +.app button { + font-family: var(--font-sans); + font-size: 13px; + font-weight: 500; + cursor: pointer; + border-radius: var(--radius-sm); + transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease, transform 120ms ease, + box-shadow 120ms ease; +} +.app button:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 1px; +} + +/* Ghost bordered button — used for Theme / GitHub / Docs. Fixed height so + the GitHub button (whose inner star pill would otherwise inflate it) + lines up exactly with its plain siblings. */ +.btn-ghost { + display: inline-flex; + align-items: center; + gap: 6px; + height: 32px; + box-sizing: border-box; + color: var(--text); + background: var(--panel-bg); + border: 1px solid var(--border); + padding: 0 12px; + text-decoration: none; + font-size: 13px; + font-weight: 500; + border-radius: var(--radius-sm); + transition: border-color 120ms ease, color 120ms ease, background-color 120ms ease; +} +.btn-ghost:hover, +.btn-ghost:focus-visible { + border-color: var(--accent); + color: var(--accent); +} +.btn-ghost:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 1px; +} + +/* Star-count badge inside the GitHub ghost button (logo + "GitHub" + count). */ +.gh-stars { + display: inline-flex; + align-items: center; + gap: 3px; + margin-left: 2px; + padding: 2px 7px; + border-radius: 999px; + background: var(--panel-alt); + border: 1px solid var(--border); + color: var(--text-muted); + font-family: var(--font-mono); + font-size: 11px; + line-height: 1; +} +.gh-stars svg { + color: #f5b400; +} + +/* Filled dark "Share" button — same fixed height as the ghost buttons. */ +.btn-share { + display: inline-flex; + align-items: center; + height: 32px; + box-sizing: border-box; + color: var(--share-fg); + background: var(--share-bg); + border: 1px solid var(--share-bg); + padding: 0 16px; + font-weight: 600; + border-radius: var(--radius-sm); +} +.btn-share:hover { + opacity: 0.9; +} + +/* ---------- Header (single row) ---------- */ +.toolbar { + position: relative; + background: var(--page-bg); + border-bottom: 1px solid var(--border); + padding: 12px 20px; + display: flex; + align-items: center; + gap: 14px; + animation: fade-slide 420ms ease-out both; +} +.toolbar::after { + content: ""; + position: absolute; + left: 0; + right: 0; + bottom: -1px; + height: 2px; + background: var(--accent); + opacity: 0; + transform: translateX(-100%); + pointer-events: none; +} +.toolbar.is-loading::after { + opacity: 1; + animation: loading-sweep 1.1s ease-in-out infinite; +} +/* White chip in BOTH themes — the logo's dark strokes need a light ground. */ +.app-mark { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + flex-shrink: 0; + border-radius: 8px; + background: #ffffff; +} +.toolbar-title { + font-family: var(--font-sans); + font-weight: 600; + font-size: 16px; + color: var(--text); + letter-spacing: -0.01em; +} +.status-pill { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 4px 10px 4px 8px; + border-radius: 999px; + font-family: var(--font-sans); + font-size: 12px; + font-weight: 500; +} +.status-dot { + width: 6px; + height: 6px; + border-radius: 50%; + flex-shrink: 0; +} +.status-pill--ready { + background: var(--status-ready-bg); + color: var(--status-ready-fg); +} +.status-pill--ready .status-dot { + background: var(--status-ready-fg); +} +.status-pill--busy { + background: var(--status-busy-bg); + color: var(--status-busy-fg); +} +.status-pill--busy .status-dot { + background: var(--status-busy-fg); +} +.status-pill--error { + background: var(--status-error-bg); + color: var(--status-error-fg); +} +.status-pill--error .status-dot { + background: var(--status-error-fg); +} +.toolbar-spacer { + flex: 1; +} +.toolbar-actions { + display: inline-flex; + align-items: center; + gap: 8px; +} + +/* ---------- Examples row ---------- */ +.examples { + position: relative; + background: var(--page-bg); + border-bottom: 1px solid var(--border); + padding: 10px 20px; + display: flex; + gap: 8px; + align-items: center; + flex-wrap: wrap; + animation: fade-slide 420ms ease-out both; + animation-delay: 30ms; +} +.examples-label { + font-family: var(--font-sans); + text-transform: uppercase; + letter-spacing: 0.06em; + font-size: 11px; + font-weight: 600; + color: var(--text-muted); + margin-right: 4px; +} +.example-pill { + font-size: 13px; + font-weight: 500; + color: var(--text); + background: var(--panel-bg); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + padding: 6px 12px; +} +.example-pill:hover, +.example-pill:focus-visible { + border-color: var(--accent); + color: var(--accent); +} +.example-pill.is-active { + background: var(--accent-soft); + border-color: var(--accent); + color: var(--accent-hover); +} + +/* ---------- Main row ---------- */ +/* Flat layout (mockup): panels butt together and are separated by 1px + hairlines only — no gaps, no per-panel cards/shadows/rounding. */ +.main { + flex: 1; + display: flex; + min-height: 0; + gap: 0; + padding: 0; + background: var(--panel-bg); +} + +/* ---------- Split container (editor + handle + preview) ---------- */ +.split-container { + flex: 1; + display: flex; + min-width: 0; + min-height: 0; +} + +/* ---------- Node search sidebar ---------- */ +/* Width is set inline (resizable via DragHandle); 260px is the CSS + fallback. The right divider line is the DragHandle itself. */ +.node-search { + width: 260px; + flex-shrink: 0; + background: var(--panel-bg); + overflow: hidden; + display: flex; + flex-direction: column; + animation: fade-slide 420ms ease-out both; +} +.search-input-wrap { + position: relative; + margin: 12px; +} +.search-icon { + position: absolute; + left: 10px; + top: 50%; + transform: translateY(-50%); + color: var(--text-muted); + pointer-events: none; + display: inline-flex; +} +.node-search input { + width: 100%; + padding: 8px 10px 8px 32px; + background: var(--input-bg); + border: 1px solid transparent; + border-radius: var(--radius-md); + color: var(--text); + font-family: var(--font-sans); + font-size: 13px; + outline: none; + transition: border-color 120ms ease, box-shadow 120ms ease, background-color 120ms ease; +} +.node-search input::placeholder { + color: var(--text-muted); +} +.node-search input:focus { + background: var(--panel-bg); + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-soft); +} +.node-search-results { + flex: 1; + overflow-y: auto; + list-style: none; + padding: 0 8px 12px; + font-size: 13px; +} + +/* Shared class row (flat search results + expanded-category tree rows). + Fixed layout that never shifts — no hover-revealed controls live here; + actions are a right-click context menu instead (see .ctx-menu below). + Row hover/focus is a subtle background tint only. + Horizontal padding lives on the row itself (not on an ancestor